iOS减包实战:Compress PNG Files作用分析
减包这个词大家应该都不陌生,在减包过程中,图片资源的优化这项应该是必经之路了,毕竟在包大小中,图片资源占的比重是很大,而且是可优化空间较大的一项,而在Xcode中有一个build setting就叫做"Compress PNG Files",翻译一下就是“压缩PNG文件”,看上去简直是完美,有木有!
然而,当把这个配置改为YES之后,打包之后发现ipa的大小不但没有减小,反而增大了。说好的Compress呢!!!
这就必须要好好研究下这个"Compress PNG Files"到底是干啥的了!先找到/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize这个脚本,其中有如下一段:
Xcode就是执行了这条命令来进行图片优化的,command line tools中也自带了这么一个命令行工具,通过这个工具,我们可以看看图片经过所谓的Compress之后发生了什么变化。
以如下这张LUT图为例,为什么是这张图片呢,一是因为这种图片在图像处理的App中特别常见,是用来做滤镜调色时使用的,二是因为这种类型的图片差别特别明显。
从结果上来看,经过这个“优化”之后,图片大小从207KB,变成了750KB,整整大了500多K,为了排除偶然性,又选择了另一张图片处理
结果还是增大了非常多,说好的Compress呢!
接下来就要详细的分析下为什么会出现这种情况了。
首先先简单介绍一下PNG这种图片格式,PNG由文件标志和多个数据块组成,文件标志是固定的如下图所示:
数据块的格式如下:
名称 |
字节数 |
说明 |
---|---|---|
Length (长度) |
4字节 |
指定数据块中数据域的长度,其长度不超过(231 -1)字节 |
Chunk Type Code (数据块类型码) |
4字节 |
数据块类型码由ASCII字母(A-Z和a-z)组成 |
Chunk Data (数据块数据) |
可变长度 |
存储按照Chunk Type Code指定的数据 |
CRC (循环冗余检测) |
4字节 |
存储用来检测是否有错误的循环冗余码 |
PNG中常见的数据块类型如下:
PNG文件格式中的数据块 |
||||
---|---|---|---|---|
数据块符号 |
数据块名称 |
多数据块 |
可选否 |
位置限制 |
IHDR |
文件头数据块 |
否 |
否 |
第一块 |
cHRM |
基色和白色点数据块 |
否 |
是 |
在PLTE和IDAT之前 |
gAMA |
图像γ数据块 |
否 |
是 |
在PLTE和IDAT之前 |
sBIT |
样本有效位数据块 |
否 |
是 |
在PLTE和IDAT之前 |
PLTE |
调色板数据块 |
否 |
是 |
在IDAT之前 |
bKGD |
背景颜色数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
hIST |
图像直方图数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
tRNS |
图像透明数据块 |
否 |
是 |
在PLTE之后IDAT之前 |
oFFs |
(专用公共数据块) |
否 |
是 |
在IDAT之前 |
pHYs |
物理像素尺寸数据块 |
否 |
是 |
在IDAT之前 |
sCAL |
(专用公共数据块) |
否 |
是 |
在IDAT之前 |
IDAT |
图像数据块 |
是 |
否 |
与其他IDAT连续 |
tIME |
图像最后修改时间数据块 |
否 |
是 |
无限制 |
tEXt |
文本信息数据块 |
是 |
是 |
无限制 |
zTXt |
压缩文本数据块 |
是 |
是 |
无限制 |
fRAc |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFg |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFt |
(专用公共数据块) |
是 |
是 |
无限制 |
gIFx |
(专用公共数据块) |
是 |
是 |
无限制 |
IEND |
图像结束数据 |
否 |
否 |
最后一个数据块 |
还是用之前那张图片作为例子来看,用pngcheck工具看一下文件信息:
这个图片包含了IHDR、tEXt、iTXt、IDAT、IEND等五个数据块,其中比较关键的数据块是IHDR、IDAT、IEND三个,而当我们同pngcheck查看处理之后图片的话,就会出现如下提示:
这里我们就会发现,这里的Compress PNG Files做的并不是单纯的压缩数据,而是把文件格式也做了修改,当我们去查阅相关资料的时候,可以发现其实Apple是将png图片转换成了一种CgBI格式:
These modifications cause the generated images to be invalid as per the current version of the PNG standard.
- extra critical chunk (CgBI)
- byteswapped (RGBA -> BGRA) pixel data, presumably for high-speed direct blitting to the framebuffer
- zlib header, footer, and CRC removed from the IDAT chunk
- premultiplied alpha (color' = color * alpha / 255)
明显的改动就是在IHDR块之前插入了CgBI块来表示这种格式,同时修改了IDAT块中的数据,原因就是在iPhone中,图像是以BGRA格式在内存中处理的,到这里就可以发现,其实这个所谓的Compress PNG Files,最主要的目的并不是压缩图片的大小,而是将图片转换成iPhone能更方便处理的格式,加快处理速度。
接下来就要解释下为什么LUT这种图片的大小会变化这么大了,首先要看下Compress后的PNG的数据格式,这里通过hexdump可以查询到各个数据块的关键字
这里推荐一个查看CgBI文件数据的命令行工具,pngdefry,这个工具可以用来查看CgBI文件信息,同时可以用来将CgBI文件还原成png文件,当然这里还原之后的png文件和原始文件还是会有区别的,这里后续会有提到。
可以发现,这处理增加了CgBI数据块之外,还增加了一个iDot数据块,这里数据块是Apple自定义的数据块,在网上也没有找到相关的文档,所以暂时无法分析其作用,变化最大的就是IDAT数据块了,IDAT数据块中存放的是实际的图像数据,由于png本身就是一种压缩格式,所以IDAT数据块的数据本身就是经过压缩的,其大小取决于Filter方式、zlib的压缩方式等等。
关于filter方式http://www.libpng.org/pub/png/book/chapter09.html这篇文章讲的非常详细,有兴趣的同学可以看下。而Apple默认的filter方式就是filter0,也就是None这种方式,不同的filter方式对数据的压缩性是有很大影响的,同样是这张图片,使用不用的filter方式处理之后的大小如下:
不同的filter处理之后的图,放到实际工程中使用的话,效果上我测试下来是没有什么区别的,也就是说这几种类型,iPhone都可以解码,不过可以发现,即使是最小的文件也是217KB,还是比源文件207KB要大一些,这又是为什么呢?
xcrun -sdk iphoneos pngcrush -revert-iphone-optimizations lutf0.png lut_revert.png
通过上面的命令,我们可以将图片还原成正常的png格式,然而还原出来的png图片是246KB,比原来的207KB还是大了不少,通过pngcheck,我们可以发现原因:
因为CgBI的IDAT是BGRA格式的,所以不管之前的IDAT是否有Alpha通道,在处理的时候,都会增加alpha通道,其次就是因为每一行数据的filter不同,apple处理的时候,默认每一行都使用相同的filter,而原始文件则可以通过更好的算法,对不同的数据行使用不同的filter,为后面的数据压缩提供更容易压缩的数据。
以上就是对Compress PNG Files这个编译选项的一些分析,当然并不是所有图片经过处理都会变得巨大,也有些图片会变小的,所以当Compress PNG Files这个属性已经开启,并且不确定关闭会不会对现有工程产生大量影响的时候,可以通过如下操作将这张图片排除出compress的方法,尤其是对于LUT图片:
修改为:
参考文献:
【1】http://iphonedevwiki.net/index.php/CgBI_file_format
【2】https://www.cnblogs.com/lidabo/p/3701197.html
作者简介:ash, 天天P图iOS工程师
文章后记:
天天P图是由腾讯公司开发的业内领先的图像处理,相机美拍的APP。欢迎扫码或搜索关注我们的微信公众号:“天天P图攻城狮”,那上面将陆续公开分享我们的技术实践,期待一起交流学习!
加入我们:
天天P图技术团队长期招聘:
(1) AND / iOS 开发工程师 (2) 图像处理算法工程师
期待对我们感兴趣或者有推荐的技术牛人加入我们(base 上海)!联系方式:ttpic_dev@qq.com