注册

移动端如何使用WebP

发布时间:2017-04-30 10:10:26 点击:4617

图片编解码原理

图片编码过程是将 YUV 或者 RGB 像素格式的原始图片编码为压缩格式如 WebP 或者 JPG。图片的解码过程是将压缩格式还原为 YUV 或者 RGB 的原始图片。图片在控件或者屏幕上的渲染展示只能使用原始像素格式,也就是压缩格式的图片必须解码之后才能展示。

WebP解码

WebP 作为一种更高效的图片编码格式,平均大小比 PNG/JPG/ GIF/动态 GIF格式减少 70%(对比测试页面),且质量没有明显的差别,是其他图片格式极佳的替代者。

我们参考 iOS-WebP 工程,简单了解一下 WebP 的解码过程。

//UIImage+WebP.m

+ (UIImage *)imageWithWebPData:(NSData *)imgData error:(NSError **)error {

    // `WebPGetInfo` weill return image width and height

    int width = 0, height = 0;

    if(!WebPGetInfo([imgData bytes], [imgData length], &width, &height)) {

        ...

        return nil;

    }

    WebPDecoderConfig * config = malloc(sizeof(WebPDecoderConfig));

    if(!WebPInitDecoderConfig(config)) {

        ...

        return nil;

    }

    config->options.no_fancy_upsampling = 1;

    config->options.bypass_filtering = 1;

    config->options.use_threads = 1;

    config->output.colorspace = MODE_RGBA;

    // Decode the WebP image data into a RGBA value array

    VP8StatusCode decodeStatus = WebPDecode([imgData bytes], [imgData length], config);

    if (decodeStatus != VP8_STATUS_OK) {

        ...

        return nil;

    }

    // Construct UIImage from the decoded RGBA value array

    uint8_t *data = WebPDecodeRGBA([imgData bytes], [imgData length], &width, &height);

    ...

    CGImageRef imageRef = CGImageCreate(width, height, 8, 32, 4 * width, colorSpaceRef, bitmapInfo, provider, NULL, YES, renderingIntent);

    UIImage *result = [UIImage imageWithCGImage:imageRef];

    ...

    return result;

}

可见这个解码函数的作用是将 WebP 图片 Data 转换成 UIImage。整个解码功能的实现是依赖于 libwebp 库。解码第一步是调用 WebPGetInfo 函数从 WebpData 中读取图片长宽等信息。第二步是配置解码参数 WebPDecoderConfig ,其中一个重要的参数是 output.colorspace ,即设置解码之后图片的像素格式。示例代码中设置为 MODE_RGBA,从 enum WEBP_CSP_MODE 类型的定义看,解码设置支持多种的 RGB 和 YUV 类型格式。另外的还可以配置图片剪裁、缩放等参数,具体可看考 libwebp API。 接下来,第三步将 WebP 数据解码为 RGBA 数据, 第四步将 RGBA 组装为 UIImage。

编码过程与解码相似,需要注意的是 WebP 的有损压缩方式只支持压缩 YUV 数据,如果原始格式是 RGB 可以通过转换为 YUV 数据之后再进行压缩编码。libwebp 也内置了一个转换函数可供使用。

使用 UIImageView 展示 WebP 图片

在 UIImageView 中使用 WebP 格式图片的最佳实践是依赖 SDWebImage 库。

安装 SDWebImage(支持 WebP):

pod'SDWebImage/WebP'

使用方法:

#import <SDWebImage/UIImageView+WebCache.h>

- (void)viewDidLoad {

    [super viewDidLoad];

    

    //动图类型的 WebP

    NSString *animatedWebpurl = @"https://p.upyun.com/demo/webp/animated-gif/0.gif!/format/webp";

    [self.imageView1 sd_setImageWithURL:[NSURL URLWithString:animatedWebpurl]];

    

    //静图类型的 WebP

    NSString *webpurl = @"https://p.upyun.com/demo/webp/jpg/5.jpg!/format/webp";

    [self.imageView2 sd_setImageWithURL:[NSURL URLWithString:webpurl]];

}

SDWebImage 安装之后内置 libwebp 源码,并在 UIImage+WebP.m 封装实现了 WebP 格式的解码功能。通过 SDWebImage 我们可以非常方便的在 UIImageView 中使用 WebP 格式的图片。

使用 UIWebView 展示 WebP 图片

在以 Native 方式开发的 App 中也会大量使用的 UIWebView 来展示一些简单页面,然而 Safari 及 UIWebView 当前并不支持 WebP 格式。若是想在 UIWebView 中也把图片显示出来,一个解决思路就是:拦截替换。拦截 WebP 图片然后转换为 jpg 或者 png 再交给 UIWebView 进行渲染和展示。

拦截替换 WebP 有两种具体实现方式:

方式 1,通过 UIWebView 代理函数给出的页面加载时机进行 WebP 图片的拦截和替换。这种方式首先要遍历获取 HTML 文档中的所有 WebP 图片链接。获取图片链接过程需要依赖第三方 HTML parser 来解析 HTML 文档,或者通过提前协商好的 HTML 内嵌 js 函数进行通信。在得到所有的 WebP 图片链接之后,将 WebP 下载解码并转换为 jpg 或者其他图片 data,然后再 base64 编码成字符串内嵌到 <img> 标签,让 UIWebView 进行加载。

方式 2,通过 NSURLProtocol 全局拦截和过滤所有 HTTP 请求,将 WebP 数据直接转换为其他格式图片数据,再封装为 response body 吐给原来的 HTTP 请求。代码示例:

//WebP_demo/WebPHttpProxy.m

#pragma mark dataDelegate

 

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{

    NSData *transData = data;

    if ([dataTask.currentRequest.URL.absoluteString hasSuffix:@"webp"]) {

        

        //借用 SDWebImage 中 WebP 的解码方法,将 webp data 转化为 JPEG data

        UIImage *imgData = [UIImage sd_imageWithWebPData:data];

        transData = UIImageJPEGRepresentation(imgData, 1.0f);

    }

    

    [self.client URLProtocol:self didLoadData:transData];

}

这样就可以达到以透明的方式使用 WebP 图片。但是由于 NSURLProtocol 的全局性质,影响范围大,这种方式存在潜在的风险,需要严格的过滤和限制 WebP 请求的拦截。NSURLProtocol 作用的叠加性质,也无法保证与其它第三方代码的兼容。虽然这种方式有很多代码可以参考,本文附带的 demo 也有此种方式的实现,但是在具体工程中使用还需要谨慎和完善。

但,上文已经提到这种全局 NSURLProtocol 拦截的方式存在较大风险。一些网络文章也普遍的引用这种实现方式,但是这段代码是不严谨的,原因在于 NSURLSession 支持流式的 HTTP 请求,或者在 response body 体积比较大的时候,从 didReceiveData 回调回来的一次数据仅仅是整个 response body 的一部分。

在 iOS 平台使用 SDWebImage 结合又拍云图片处理 API,在原始图片 URL 后面拼接后缀 !/format/webp,不需要其他任何改变,就可以简单快捷的利用 WebP 格式来大幅度减少图片请求流量,提高图片加载体验。

推荐阅读:

1. WebP图片压缩效果对比

2. 移动端APP如何解决WebP兼容

原文来自:SDK.cn


详细产品信息,请咨询我们的售前工程师

QQ咨询 热线电话:0750-3460006 邮箱地址:765666699@qq.com