WebP 极限压缩及ios实现
Levi.duan 会议提纲
概念以及定义
什么是Webp ?
-
WebP (发音 weppy ),是一种同时提供了有损压缩与无损压缩的图片文件格式,是Google新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快
-
WebP 无损压缩的图片可以比同样大小的 PNG 小 26%;
WebP 有损压缩的图片可以比同样大小的 JPEG 小 25-34%;
WebP 支持无损的透明图层通道,代价只需增加 22% 的字节存储空间;
WebP 有损透明图像可以比同样大小的 PNG 图像小3倍。
优点
WebP 的优势体现在它具有更优的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。
科技博客 GigaOM 曾报道:YouTube 的视频略缩图采用 WebP 格式后,网页加载速度提升了 10%;谷歌的 Chrome 网上应用商店采用 WebP 格式图片后,每天可以节省几 TB 的带宽,页面平均加载时间大约减少 1/3;Google+ 移动应用采用 WebP 图片格式后,每天节省了 50TB 数据存储空间。
劣势
根据Google的测试,目前WebP与JPG相比较,编码速度慢10倍,解码速度慢1.5倍。
在编码方面,一般来说,我们可以在图片上传时生成一份WebP图片或者在第一次访问JPG图片时生成WebP图片,对用户体验的影响基本忽略不计,主要问题在于1.5倍的解码速度是否会影响用户体验。
和其他图片格式区别?
GIF(Graphics Interchange Format)
GIF图形交换格式是一种位图图形文件格式,以8位色(即256种颜色)重现真彩色的图像。它实际上是一种压缩文档,采用LZW压缩算法进行编码,有效地减少了图像文件在网络上传输的时间。它是目前广泛应用于网络传输的图像格式之一。
优点
-
优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小。
-
可插入多帧,从而实现动画效果。
-
可设置透明色以产生对象浮现于背景之上的效果。
缺点
由于采用了8位压缩,最多只能处理256种颜色(2的8次方),故不宜应用于真彩图像。
PNG(Portable Network Graphics)
便携式网络图片(Portable Network Graphics),简称PNG,是一种无损数据压缩位图图形文件格式。PNG格式是无损数据压缩的,PNG格式有8位、24位、32位三种形式,其中8位PNG支持两种不同 的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位 PNG 在24位基础上增加了8位透明通道(32-24===8),因此可展现256级透明程度。
优点
-
支持256色调色板技术以产生小体积文件
-
最高支持48位真彩色图像以及16位灰度图像。
-
支持Alpha通道的半透明特性。
-
支持图像亮度的gamma校正信息。
-
支持存储附加文本信息,以保留图像名称、作者、版权、创作时间、注释等信息。
-
使用无损压缩。
-
渐近显示和流式读写,适合在网络传输中快速显示预览效果后再展示全貌。
-
使用CRC循环冗余编码防止文件出错。
-
最新的PNG标准允许在一个文件内存储多幅图像。
缺点
过分臃肿
JPG(Joint Photographic Experts Group)
JPEG是一种针对相片影像而广泛使用的一种失真压缩标准方法。JPEG的压缩方式通常是破坏性资料压缩(lossy compression),意即在压缩过程中图像的品质会遭受到可见的破坏。
优点
JPEG/JFIF是最普遍在万维网(World Wide Web)上被用来储存和传输照片的格式。 JPEG在色调及颜色平滑变化 的相片或是写实绘画(painting)上可以达到它最佳的效果。在这种情况下,它通常比完全无失真方法作得更好,仍然可以产生非常好看的影像(事实上它会比其他一般的方法像是GIF产生更高品质的影像,因为GIF对于线条绘画(drawing)和图示的图形是无失真,但针对全彩影像则需要极困难的量化)
缺点
它并不适合于线条绘图(drawing)和其他文字或图示(iconic)的图形,因为它的压缩方法用在这些图形的型态上,会得到不适当的结果;
图片编码原理
WebP的压缩主要分为有损压缩、无损压缩以及有损带透明通道压缩。
有损WebP
有损WebP基于VP8视频编码中的预测编码方法来压缩图像数据,其基本步骤类似于JPEG压缩,主要包含格式转换、分割子块、预测编码、FDCT、量化、Z排列、熵编码,流程如下图所示,红色代表与JPEG不同的部分。
格式转换
若压缩前图像数据为RGB格式,则需先进行格式转换成YUV格式,Y表示亮度分量,UV表示色度分量。之所以转换成YUV格式是因为人类视觉对亮度远比色度敏感,所以可通过适当减少色度数据的存储来节省数据占用的空间,但却不会对视觉效果造成太大影响,如可每两个或四个相邻的像素点才保存一对UV值。
分割宏块
接下来将数据分割成一个个8x8或16x16的宏块。
预测编码
预测编码的原理是基于前面编码好的宏块,预测多余的动作颜色等信息,属于帧内预测。对各宏块可使用以下几种帧内预测模式:
H_PRED(horizontal prediction).使用block左边的一列L来填充block中的每一列
V_PRED(vertical prediction):使用block上边的一行A来填充block中的每一行
DC_PRED(DC prediction):使用L和A中所有像素的平均值作为唯一的值填充block
TM_PRED(TrueMotion prediction):使用渐进的方式,记录上面一行的渐进差,以同样的差值,以L为基准拓展每一行。
FDCT
FDCT(Forward Discrete Cosine Transform,正向离散余弦变换)是将一组空间域的像素点转变成频域中的系数,对每个宏块执行FDCT,使得变换后数据的低频部分分布在数据块的左上方,高频部分集中在右下方,其中左上角第一个系数称为直流系数,其他均为交流系数。
量化
量化是压缩中损失数据的主要步骤,它主要原理是把经过DCT变换后的宏块中每个数值除以量化表中对应的系数并取整。其中量化表中高频部分对应的系数比低频部分系数要大得多,则在经过量化后,高频部分的频率系数被大大衰减甚至许多被清零,而低频部分的频率系数则较好地被保留。由于人眼对低频部分更敏感,所以经过量化后再还原成图像对视觉效果影响较小,但数据得到有效的压缩。量化的最终目的是减少低频部分非零系数的幅值并增加高频部分零值系数的数量。
Z排列
为更便于后续的编码,需在编码前对数据块进行重新的排列,使得低频部分的数据排在前面,高频部分的数据排在后面,以增加数组中连续零值的数量,所以采用一种Z字型的排列方式
DPCM
可用DPCM(Differential Pulse Code Modulation,差分脉冲编码调制)对直流系数进行编码。由于直流系数的数值较大,且相邻数据块的直流系数相差不大,所以可使用DPCM对相邻数据块间量化后的直流系数差值进行编码,从而提高压缩比。
行程编码
行程编码是一种根据相同数据重复多次的情况简化表示的算法,例如1111222222333按照行程编码表示为(1,4)(2,6)(3,3)。由于量化后的交流系数中包含较多连续零值系数,因此可用行程编码对它们进行编码来有效压缩数据长度。
熵编码
熵编码是一种无损数据压缩编码方式,WebP中采用布尔算术编码作为熵编码方式。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(0.0 ≤ n < 1.0)的小数n。消息越长,编码表示它的间隔就越小,表示这一间隔所需的二进制位就越多。
其他
WebP还有一些细节上的步骤,比如自适应分块(对不同区域的宏块分配不同的压缩参数)、环路滤波等。
PS:为什么有损WebP会比JPEG好?
主要原因是预测编码。
自适应分块也提供了较好表现。
环路滤波在中、低比特率的情况下有较大帮助。
算数编码相比霍夫曼增强了5%~10%的压缩能力。
无损WebP
无损WebP基于使用不同的技术对图像数据进行转换,包括:预测空间变换、色彩空间转换、使用调色板、多像素打包成一个像素、alpha值替换等技术。对于熵编码,则采用改进的LZ77-Huffman编码来紧凑稀疏值,它是一种对距离值的2D编码技术。
有损WebP(支持透明)
别于有损WebP和无损WebP,这种编码允许对RGB频道的有损编码同时可对透明度频道进行无损编码。由于这种形式目前其他的格式还未能提供,所以目前需要使用透明度的话都会使用无损的PNG,导致大小膨胀。对于这类图片,WebP提供了较好的压缩效果。相比有损的WebP,添加透明通道只增加22%的大小。
因此,将支持透明的PNG换成无损+支持透明的WEBP可以平均节省60%-70%大小,这个已经被一些含较多Icon的移动网站证明。
动态WebP
动态WebP的原理与GIF和APNG原理类似,每一帧记录变化区域的坐标、长宽、播放延时等用于还原并播放
Webp 安装以及oc代码实现
Mac 下通过WebP命令行工具安装
Google提供了命令行工具用于将图片转换为webp。
在Mac下,可以使用homebrew安装webp工具:
brew install webp
安装完命令行工具后,就可以使用cwebp将JPG或PNG图片转换成WebP格式。
cwebp [-preset <...>] [options] in_file [-o out_file]
options参数列表中包含质量参数q,q为0~100之间的数字,比较典型的质量值大约为80。
也可以使用dwebp将WebP图片转换回PNG图片(默认)。
dwebp in_file [options] [-o out_file]
-lossless:无损压缩
-q:压缩质量,值越大越图片质量越好
-m:压缩方式,值越大则图片质量越好,体积越小,但是耗时较长
选择无损压缩时,“-lossless -q 100” 是最佳方案
选择有损压缩时,“-q 75”是最佳方案(图片质量与体积大小达到均衡)
无论何种压缩参数,加上“-m 6”都能使得输出的 WebP 图片进一步减少体积,量级是1%~2%,但是会增加耗时
小于 256 色:以图标,图形,剪贴画为代表,最适合采用 WebP 无损压缩,精细度完美,体积大幅减少;
大于 256 色:以多数表情图,广告图为代表,最适合采用 WebP 有损压缩,选择较高压缩比(建议压缩质量为 100% ~ 75%)
远大于 256 色:以风景照,视频截图为代表,最适合采用 WebP 有损压缩,选择适中压缩比(建议压缩质量为 75% 以
WebP 解码
+ (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 编码成字符串内嵌到 标签,让 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 的一部分。
libwebp库WebpDemo使用方法:
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *demoImage = [UIImage imageNamed:@"MN.png/MN.jpg"];
[normalView setImage:[demoImage imageByApplyingAlpha:alpha]];
NSData *demoImageData = UIImageJPEGRepresentation(demoImage, 1.0);
uint64_t fileSize = [demoImageData length];
[normalLabel setText:[NSString stringWithFormat:@"%@ format file size: %.2f KB with alpha: %.2f", [self contentTypeForImageData:demoImageData] , (double)fileSize/1024, alpha]];
[convertedView setImage:[UIImage imageNamed:@"Default"]];
[convertedLabel setText:@"Waiting..."];
if (!asyncConvert) {
NSData *webPData = [UIImage imageToWebP:demoImage quality:quality];
[self displayImageWithData:webPData];
}
else {
[UIImage imageToWebP:demoImage quality:quality alpha:alpha preset:WEBP_PRESET_PHOTO completionBlock:^(NSData *result) {
[self displayImageWithData:result];
} failureBlock:^(NSError *error) {
NSLog(@"%@", error.localizedDescription);
}];
}
}
- (void)displayImageWithData:(NSData *)webPData
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *webPPath = [paths[0] stringByAppendingPathComponent:@"image.webp"];
if ([webPData writeToFile:webPPath atomically:YES]) {
uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:webPPath error:nil] fileSize];
[convertedLabel setText:[NSString stringWithFormat:@"WEBP format file size: %.2f KB at %.f%% quality", (double)fileSize/1024, quality]];
if (!asyncConvert) {
[convertedView setImage:[UIImage imageWithWebP:webPPath]];
}
else {
[UIImage imageWithWebP:webPPath completionBlock:^(UIImage *result) {
[convertedView setImage:result];
}failureBlock:^(NSError *error) {
NSLog(@"%@", error.localizedDescription);
}];
}
}
}