BAHome / BAWKWebView-WebP

WKWebView 显示 HTML 中的 GIF 动图、WebP 无损图片,最简单、最方便的接入!
MIT License
51 stars 10 forks source link

已增加webp动图支持,附代码 #6

Open XiFengLang opened 4 years ago

XiFengLang commented 4 years ago

iOS14的WebKit已经支持webp,所以只要在iOS13及下面的版本做兼容即可。

我没有详细取每一帧的duration,用的duration是平均值,遇到极个别动图会很慢,大部分图看不出什么异样。

增加或者修改下面的代码

  pod 'SDWebImage','>= 5.9'
  pod 'SDWebImageWebPCoder'

#import <MobileCoreServices/MobileCoreServices.h>

- (void)ba_registerURLProtocol
{
    if (@available(iOS 14.0, *)) { } else {
        [NSURLProtocol registerClass:NSClassFromString(@"BAURLSessionProtocol")];
        // 注册registerScheme使得WKWebView支持NSURLProtocol
        [NSURLProtocol ba_web_registerScheme:@"http"];
        [NSURLProtocol ba_web_registerScheme:@"https"];
    }
}

- (void)dealloc
{
    if (@available(iOS 14.0, *)) {} else {
        [NSURLProtocol unregisterClass:NSClassFromString(@"BAURLSessionProtocol")];
        // 移除 registerScheme
        [NSURLProtocol ba_web_unregisterScheme:@"http"];
        [NSURLProtocol ba_web_unregisterScheme:@"https"];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error{

    if (error) {
        [self.client URLProtocol:self didFailWithError:error];
    }else{
        if ([task.currentRequest.URL.absoluteString hasSuffix:@"webp"]) {
            // 如果判断是需要处理的webp图片,就对图片进行转换
            NSLog(@"webp---%@",task.currentRequest.URL);
            UIImage *imgData = [UIImage sd_imageWithWebPData:self.imageData];
            NSArray * images = imgData.images;

            if (images.count > 1) {
                NSTimeInterval duration = imgData.duration / images.count;

                NSString * fileName = [[task.currentRequest.URL URLByDeletingPathExtension] lastPathComponent];
                NSString *cashPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.gif",fileName]];
                NSURL *url = [NSURL fileURLWithPath:cashPath];

                // 设置gif的彩色空间格式、颜色深度、执行次数
                NSDictionary *gifDic = @{
                    (NSString *)kCGImagePropertyGIFLoopCount:@(0),
                    (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelRGB,
                    (NSString *)kCGImagePropertyDepth:@(16)
                };
                NSDictionary *gifProperties = @{(NSString *)kCGImagePropertyGIFDictionary:gifDic};
                CGImageDestinationRef destination = CGImageDestinationCreateWithURL((CFURLRef)url, kUTTypeGIF, images.count, NULL);
                CGImageDestinationSetProperties(destination, (CFDictionaryRef)gifProperties);

                for (int32_t index = 0; index < images.count; index ++) {
                    UIImage * frame = [images objectAtIndex:index];
                    NSDictionary * gifDict = @{
                        (NSString *)kCGImagePropertyGIFDelayTime:@(duration)
                    };
                    NSDictionary * frameProperties = @{
                        (NSString *)kCGImagePropertyGIFDictionary:gifDict
                    };
                    CGImageDestinationAddImage(destination, frame.CGImage, (CFDictionaryRef)frameProperties);
                }

                CGImageDestinationFinalize(destination);
                CFRelease(destination);
                NSData *transData = [NSData dataWithContentsOfFile:cashPath];
                self.beginAppendData = NO;
                self.imageData = nil;
                [self.client URLProtocol:self didLoadData:transData];
            } else {
                NSData *transData = UIImageJPEGRepresentation(imgData, 0.8f);
                self.beginAppendData = NO;
                self.imageData = nil;
                [self.client URLProtocol:self didLoadData:transData];
            }
        }
        [self.client URLProtocolDidFinishLoading:self];
    }
}
Jing2684 commented 3 years ago

iOS 13好像webp的动图还是不可以显示,例如:https://isparta.github.io/compare-webp/image/gif_webp/webp/1.webp

XiFengLang commented 3 years ago

iOS 13好像webp的动图还是不可以显示,例如:https ://isparta.github.io/compare-webp/image/gif_webp/webp/1.webp

@Jing2684 我们这边已经放弃代理WebKit里面的http/https请求,遇到了post请求丢失body的情况。我们主要改了后端和运维,后台上传图片时就保存jpg/gif/webp多种格式文件,nginx根据UserAgent判断是不是iOS14以下的iOS系统,如果是的,将请求指向jpg或者gif文件。这样就多端兼容了,不然只在APP内部解决,分享到微信和浏览器还是一样的。

XiFengLang commented 3 years ago

@Jing2684 当然你也可以继续试试下面的代码,我后面参考SDWebImage加了点处理

////
////  BAURLSessionProtocol.m
////  BAWKWebView-WebP
////
////  Created by 海洋唐 on 2017/7/28.
////  Copyright © 2017年 boai. All rights reserved.
////
//
//#import "BAURLSessionProtocol.h"
//#import <UIKit/UIKit.h>
//#import <SDWebImageWebPCoder/UIImage+WebP.h>
//#import <SDWebImage/SDWebImage.h>
//#import <MobileCoreServices/MobileCoreServices.h>
//
//#if __has_include("webp/decode.h") && __has_include("webp/encode.h") && __has_include("webp/demux.h") && __has_include("webp/mux.h")
//#import "webp/decode.h"
//#import "webp/encode.h"
//#import "webp/demux.h"
//#import "webp/mux.h"
//#elif __has_include(<libwebp/decode.h>) && __has_include(<libwebp/encode.h>) && __has_include(<libwebp/demux.h>) && __has_include(<libwebp/mux.h>)
//#import <libwebp/decode.h>
//#import <libwebp/encode.h>
//#import <libwebp/demux.h>
//#import <libwebp/mux.h>
//#else
//@import libwebp;
//#endif
//
//static NSString *URLProtocolHandledKey = @"URLHasHandle";
//
//@interface BAURLSessionProtocol()<NSURLSessionDelegate,NSURLSessionDataDelegate>
//
//@property (nonatomic, strong) NSURLSession *session;
//@property (nonatomic, strong) NSMutableData *imageData;
//@property (nonatomic, assign) BOOL beginAppendData;
//
//@end
//
//@implementation BAURLSessionProtocol
//
//#pragma mark 初始化请求
///**
// 判断是否启用SD_WEBP 并且图片格式为webp 如果为YES 则标记请求需要自行处理并且防止无限循环 为NO则不处理
// */
//+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
//    NSString *scheme = [[request URL] scheme];
//    BOOL commonScheme = ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
//                         [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame);
//
//    
//    /// 只处理webp结尾的请求,避免重定向所有的请求,特别是POST请求(body丢失的问题)
//    if (commonScheme && [request.URL.absoluteString.lowercaseString containsString:@".webp"]) {
//        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
//            return NO;
//        }
//        return YES;
//    }
//    return NO;
//}
//
//+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
//    return request;
//}
//
//#pragma mark 通信协议内容实现
//// 开始
//- (void)startLoading {
//    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//    
//    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
//    self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]];
//    [[self.session dataTaskWithRequest:mutableReqeust] resume];
//}
//
//// 停止
//- (void)stopLoading {
//    [self.session invalidateAndCancel];
//}
//
//#pragma mark - dataDelegate
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
//willPerformHTTPRedirection:(NSHTTPURLResponse *)response
//        newRequest:(NSURLRequest *)newRequest
// completionHandler:(void (^)(NSURLRequest *))completionHandler {
//    
//    NSMutableURLRequest * redirectRequest;
//    
//    redirectRequest = [newRequest mutableCopy];
//    [[self class] removePropertyForKey:URLProtocolHandledKey inRequest:redirectRequest];
//    
//    [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
//    
//    [self.session invalidateAndCancel];
//    NSError * error = [NSError errorWithDomain:NSCocoaErrorDomain
//                                          code:NSUserCancelledError userInfo:nil];
//    [[self client] URLProtocol:self didFailWithError:error];
//}
//
//- (void)URLSession:(NSURLSession *)session
//          dataTask:(NSURLSessionDataTask *)dataTask
//didReceiveResponse:(NSURLResponse *)response
// completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//    
//    [self.client URLProtocol:self didReceiveResponse:response
//          cacheStoragePolicy:NSURLCacheStorageNotAllowed];
//    NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
//    self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
//    
//    if (completionHandler) {
//        completionHandler(NSURLSessionResponseAllow);
//    }
//}
//
//- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
//    didReceiveData:(NSData *)data{
//    
//    self.beginAppendData = YES;
//    [self.imageData appendData:data];
//    if (!_beginAppendData) {
//        [self.client URLProtocol:self didLoadData:data];
//    }
//}
//
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error{
//    
//    if (error) {
//        [self.client URLProtocol:self didFailWithError:error];
//        return;
//    }
//    SDImageFormat format = [NSData sd_imageFormatForImageData:self.imageData];
//    if (format == SDImageFormatWebP) {
//        UIImage *imgData = [UIImage sd_imageWithWebPData:self.imageData];
//        NSArray * images = imgData.images;
//
//        if (images.count > 1) {
//            /// 转换成gif
//            NSString * fileName = [[task.currentRequest.URL URLByDeletingPathExtension] lastPathComponent];
//            fileName = [NSString stringWithFormat:@"%@.gif",fileName];
//            NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
//            [self handleAnimatedImage:imgData images:images cachePath:cachePath];
//        } else {
//            [self handleStaticImage:imgData];
//        }
//        
//        [self.client URLProtocolDidFinishLoading:self];
//    } else {
//        [self finishWithData:self.imageData];
//        [self.client URLProtocolDidFinishLoading:self];
//    }
//}
//
//- (void)finishWithData:(NSData *)imageData {
//    self.beginAppendData = NO;
//    self.imageData = nil;
//    [self.client URLProtocol:self didLoadData:imageData];
//}
//
//
//- (void)handleStaticImage:(UIImage *)image {
//    NSData *transData = UIImageJPEGRepresentation(image, 0.9f);
//    [self finishWithData:transData];
//}
//
//- (void)handleAnimatedImage:(UIImage *)image
//                     images:(NSArray <UIImage *>*)images
//                  cachePath:(NSString *)cachePath {
//    
//    if (image.sd_imageFormat == SDImageFormatGIF) {
//        [self finishWithData:[self.imageData copy]];
//        return;
//    }
//    
//    NSAssert(image.sd_imageFormat == SDImageFormatWebP, @"请校验格式");
//    
//    
//    WebPData webpData;
//    WebPDataInit(&webpData);
//    webpData.bytes = self.imageData.bytes;
//    webpData.size = self.imageData.length;
//    WebPDemuxer *demuxer = WebPDemux(&webpData);
//    if (!demuxer) {
//        [self finishWithData:[self.imageData copy]];
//        return;
//    }
//
//    // for animated webp image
//    WebPIterator iter;
//    // libwebp's index start with 1
//    if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
//        WebPDemuxReleaseIterator(&iter);
//        WebPDemuxDelete(demuxer);
//        [self finishWithData:[self.imageData copy]];
//        return;
//    }
//    
//    /// 获取每一帧的时长
//    NSMutableArray<NSNumber *> *frames = [NSMutableArray array];
//    do {
//        @autoreleasepool {
//            int duration = iter.duration;
//            if (duration <= 10) {
//                duration = 100;
//            }
//            [frames addObject:[NSNumber numberWithDouble:duration / 1000.0]];
//        }
//    } while (WebPDemuxNextFrame(&iter));
//    WebPDemuxReleaseIterator(&iter);
//    WebPDemuxDelete(demuxer);
//    
//    
//    NSURL * cacheURL = [NSURL fileURLWithPath:cachePath];
//    
//    // 设置gif的彩色空间格式、颜色深度、执行次数
//    NSDictionary *gifDic = @{
//        (NSString *)kCGImagePropertyGIFLoopCount:@(image.sd_imageLoopCount),
//        (NSString *)kCGImagePropertyColorModel:(NSString *)kCGImagePropertyColorModelRGB,
//        (NSString *)kCGImagePropertyDepth:@(16)
//    };
//    NSDictionary *gifProperties = @{(NSString *)kCGImagePropertyGIFDictionary:gifDic};
//    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((CFURLRef)cacheURL,
//                                                                        kUTTypeGIF,
//                                                                        images.count,
//                                                                        NULL);
//    CGImageDestinationSetProperties(destination, (CFDictionaryRef)gifProperties);
//    
//
//    NSUInteger repeatCount = images.count / frames.count;
//    for (int32_t index = 0; index < frames.count; index ++) {
//        /// 对于大部分动图而言,取平均值 image.duration / images.count 即可
//        /// 但是也有遇到过 异常卡顿的动图,详细对比后发现,frames只取到了58帧,但是images有348帧
//        /// 相当于重复了6次,所以要算出重复的次数,取image的时候跳着取
//        ///
//        /// 具体原因看 [SDImageCoderHelper animatedImageWithFrames:frames] 方法
//        /// 里面有求最大公约数 NSUInteger const gcd = gcdArray(frameCount, durations);
//        /// 然后按最大公约数gcd重复插入同一帧gcd次  for (size_t i = 0; i < repeatCount; ++i) {
//        /// 正常情况下gcd是1,每1帧插入1次生成新的动图(animatedImage)
//        /// 异常情况下gcd会大于1,就会出现同一帧重复插入的情况,会导致总时长累加gcd倍
//        /// 出现肉眼可见的卡顿,单个gif的文件大小也会很大,
//        /// 所以这里要跳着取 (repeatCount = gcd),即可正常显示
//        
//        NSUInteger imageIndex = index * repeatCount;
//        NSNumber * duration = frames[index];
//        
//        UIImage * frameImage = [images objectAtIndex:imageIndex];
//        NSDictionary * gifDict = @{
//            (NSString *)kCGImagePropertyGIFDelayTime:duration
//        };
//        NSDictionary * frameProperties = @{
//            (NSString *)kCGImagePropertyGIFDictionary:gifDict
//        };
//        CGImageDestinationAddImage(destination, frameImage.CGImage, (CFDictionaryRef)frameProperties);
//    }
//
//    CGImageDestinationFinalize(destination);
//    CFRelease(destination);
//    
//    NSData *transData = [NSData dataWithContentsOfFile:cachePath];
//    [self finishWithData:transData];
//}
//
//@end
Jing2684 commented 3 years ago

我已经部分使用了你们的代码,解决了问题,我没有直接拦截https,而是使用的自定义协议头,不过还是谢谢回复------------------ 原始邮件 ------------------ @.> 发送时间: 2021年4月8日(星期四) 上午10:54 @.>; @.**@.>; 主题: Re: [BAHome/BAWKWebView-WebP] 已增加webp动图支持,附代码 (#6)