iOS 工程中的 PNG 图片
The following article is from iOS成长指北 Author iOS成长指北
什么是 PNG(Portable Network Graphics)
参照 W3C[7] 对于 PNG 的定义,这是一种用于无损、便携、压缩良好的光栅图像存储的可扩展文件格式。
Compress PNG Files
在 iOS 减包实战中我们提到,iOS 图片资源需经过压缩然后加到我们的项目当中。
而当我们提及图片压缩时,都会提到 Xcode 的 Build Setting 中存在一个 Compress PNG Files 的选项,并且大部分文章都提及了,压缩过后的图片再经过 Compress PNG Files 之后可能会变得巨大
。
我们来看看 Compress PNG Files 究竟是什么:
Apple 会处理 PNGs
用于 iPhone 和 iPad 应用程序的 PNG 图像可能已经使用 Apple 修改过的 pngcrush 工具进行了性能优化,这个过程被称作 Compress PNG Files。
早期开发过程中存在一个崩溃现象,是由于将图片的文件后缀 jpg
修改改成 png
,在编译过程中会触发 pngcrush caught libpng error
。
那么 Apple 对 PNG 图片做了什么?参照 pngdefry[8] 对照 W3C[7] 的定义的剖析,Apple 大致做了以下几点:
将插入一个名为CgBI的私有但关键的数据块。 放置在图片本身 IHDR
块之前存储在 IDAT
块中的压缩图像数据缺少zlib
压缩头和Adler-32
校验和8 位真彩色图像以 BGR/BGRA 顺序存储,而不是 IHDR
块中指示的 RGB 和 RGBA 顺序。图像像素使用预定的的 Alpha 值 修改后的文件使用文件扩展名 .png
以及为有效图像定义的内部文件结构,但符合标准的PNG
查看和编辑软件不再能够处理它们。
所以,你无法使用从构建的 ipa
包获取的 PNG 图片。你可以使用 pngdefry[8] 查看适用于 iOS 平台的 PNG 图片而使用 pngcheck[9] 查看那些正常的 PNG 图片。
当你从构建的 ipa
包中拿到了一个 PNG 图片,你可以利用解码命令将其解码,然后就可以正常使用了。
验证 Compress PNG Files 对图片大小的影响
我们找到 Xcode 的优化命令,在 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize 获取对应编译过程中优化方法——截取 optimizePNGs
部分,这部分对应了 Compress PNG Files 的实现。
sub optimizePNGs {
my $name = $File::Find::name;
if ( -f $name && $name =~ /^(.*).png$/i) {
my $crushedname = "$1-pngcrush.png";
my @args = ( $PNGCRUSH, "-q", "-iphone", "-f", "0" );
if ( $stripPNGText ) {
push ( @args, "-rem", "text" );
}
push ( @args, $name, $crushedname );
if (system(@args) != 0) {
print STDERR "$SCRIPT_NAME: Unable to convert $name to an optimized png!\n";
return;
}
unlink $name or die "Unable to delete original file: $name";
rename($crushedname, $name) or die "Unable to rename $crushedname to $name";
print "$SCRIPT_NAME: Optimized PNG: $name\n";
}
}
Xcode 通过从获取文件列表中所有后缀为 .png
的图片文件,依次执行 Apple 修改 pngcrush
工具,最终获得 Apple 所需的 PNG 图片。
我们以之前文章的头图作为所需要的测试案例
xcrun -sdk iphoneos pngcrush -q -iphone -f 0 image.png image1.png
去掉 -q
参数,可以看到这个过程的一些信息:
xcrun -sdk iphoneos pngcrush -iphone -f 0 image.png image1.png
| pngcrush 1.6.4
| Copyright (C) 1998-2002,2006 Glenn Randers-Pehrson
| Copyright (C) 2005 Greg Roelofs
| This is a free, open-source program. Permission is irrevocably
| granted to everyone to use this version of pngcrush without
| payment of any fee.
| Executable name is pngcrush
| It was built with libpng version 1.2.7, and is
| running with libpng version 1.2.7 - September 12, 2004 (header)
| Copyright (C) 1998-2004,2006 Glenn Randers-Pehrson,
| Copyright (C) 1996, 1997 Andreas Dilger,
| Copyright (C) 1995, Guy Eric Schalnat, Group 42 Inc.,
| and zlib version 1.2.11, Copyright (C) 1998-2002 (or later),
| Jean-loup Gailly and Mark Adler.
| It was compiled with LLVM Apple LLVM 12.0.5 (clang-1205.0.19.59.6) [+internal-os, ptrauth-isa=deployment-target-based] and modified by Apple as indicated in the sources.
Recompressing image.png
Total length of data found in IDAT chunks = 144033
IDAT length with method 120 (fm 0 zl 9 zs 1) = 215970
Best pngcrush method = 120 (fm 0 zl 9 zs 1) for image1.png
(49.94% IDAT increase)
(49.76% filesize increase)
CPU time used = 0.064 seconds (decoding 0.008,
encoding 0.042, other 0.013 seconds)
图片大小从 144033
扩大成为 215970
,Compress PNG Files 的本质就是编译过程中 Apple 利用 pngcrush
来处理所需要使用的 PNG 图片,以便 iOS 平台更快的使用图片。
你可以将文件添加到工程目录中而不是 Assets Catalogs 中,然后构建出 ipa 包,利用 pngdefry[8] 和 pngcheck[9] 分析图片。
pngcrush
引申
Apple 修改的 pngcrush
工具主要有两种方法:
编码命令:xcrun \-sdk iphoneos pngcrush \-iphone
解码命令:xcrun \-sdk iphoneos pngcrush \-revert-iphone-optimizations
图片压缩
在 iOS 减包的 Tip 中,我们了解到资源问题是影响包大小的主要部分,而图片资源是开发过程中最常见的。使用正确的图片压缩工具能够有效的进行减包。
有损压缩和无损压缩
常见的压缩工具有 tinypng,pngquant,ImageAlpha、ImageOptim、pngcrush、optipng、pngout、pngnq、advpng 等,根据其压缩方式分成两大阵营:有损压缩和无损压缩
根据资料显示,tinypng、pngquant、ImageAlpha、pngnq 都是有损压缩,基本采用的都是quantization算法,将 24 位的 PNG 图片转换为 8 位的 PNG 图片,减少图片的颜色数;pngcrush、optipng、pngout、advpng 都是无损压缩,采用的都是基于 LZ/Huffman 的DEFLATE 算法,减少图片 IDAT chunk 区域的数据。一般有损压缩的压缩率会大大高于无损压缩。
压缩工具
对于项目中常见的背景图、占位图和大的标签图来说,推荐使用以下两种工具
TinyPNG4Mac[1]:利用 tinify[2] 提供的API,目前 tinify 的免费版压缩数量是单次不超过 20 张且大小不超过 5 M。对于一般的 iOS 应用程序来说,足够日常开发的使用 ImageOptim-CLI[3]:自动先后执行压缩率较高的为 ImageAlpha[4] 的有损压缩 加上 ImageOptim[5] 的无损压缩。
可以通过查看这个表格[6]对比 TinyPng 和 ImageOptim-CLI 。
对于小图来说,例如我们常见的 icon 图标来说,我们通过改变其编码方式为 RGB with palette 来达到图片压缩效果。你可以使用 ImageOptim 改变图片的编码方式为 RGB with palette。
imageoptim -Q --no-imageoptim --imagealpha --number-of-colors 16 --quality 40-80 ./1.png
Xcode 负优化
通过 Palette Images[7] 深入了解 palette
,也就是所谓的调色板算法。
我们一般使用 Assets Catalogs 对图片资源进行管理。其会存在对应的优化方式
在构建过程中,Xcode 会通过自己的压缩算法重新对图片进行处理。在构建 Assets Catalogs 的编译产物 Assest.car 的过程中,Xcode 会使用 actool
对 Assets Catalogs 中的 png 图片进行解码,由此得到 Bitmap 数据,然后再运用 actool 的编码压缩算法进行编码压缩处理。所以不改变编码方式的无损压缩方法最终的包大小来说,可能没有什么作用。
对同一张图片,在不同设备、iOS 系统上 Xcode 采用了不同的压缩算法这也导致了下载时候不同的设备针对图片出现大小的区别。
利用 assetutil
工具分析 Assest.car
来得到其具体的压缩方法
sudo xcrun --sdk iphoneos assetutil --info ***.app/Assets.car > ***.json
主要关注以下字段 Compression
、Encoding
、SizeOnDisk
。
Compression 压缩方法,针对不同设备、iOS系统,其压缩方法都是不同的
Encoding 编码方式 SizeOnDisk 最精确图片大小
{
"AssetType" : "Image",
"BitsPerComponent" : 8,
"ColorModel" : "RGB",
"Colorspace" : "srgb",
"Compression" : "deepmap2",
"Encoding" : "ARGB",
"Name" : "image",
"NameIdentifier" : 51357,
"Opaque" : false,
"PixelHeight" : 300,
"PixelWidth" : 705,
"RenditionName" : "image.png",
"Scale" : 1,
"SHA1Digest" : "294FEE01362591334E3C3B4ECE54AF0EA8491781",
"SizeOnDisk" : 113789,
"Template Mode" : "automatic"
}
如果启用 APP Thinning 来生成不同设备的 ipa 包,然后针对每个 ipa 包都进行一次解压缩,并获取其中的 Assets.car 导出对应的 assets.json 似乎有些冗余,你也可以利用京东商城的 APP 瘦身实践中提及的 assetutil
的方法从通用包的 Assets.car 文件导出指定设备的 Assets.car 文件
sudo xcrun --sdk iphoneos assetutil --idiom phone --subtype 570 --scale 3 --display-gamut srgb --graphicsclass MTL2,2 --graphicsclassfallbacks MTL1,2:GLES2,0 --memory 1 --hostedidioms car,watch xxx/Assets.car -o xxx/thinning_assets.car
对瘦包来说,加进工程的图片大小并不能真实反映出其对构建包的影响。请牢记图片的真实的大小是其在 ipa
的大小——包括添加进 Asset Catalogs 中的图片最终被转成.car
文件,其余是在包里的图片、各种 bundle
里的文件。
压缩的危害
不要盲目的追求最大的压缩比,既需要考虑压缩出图片的质量,也需要考虑经过 Xcode 最终构成文件的真实大小。
压缩完成的图片尽量在高分辨率的设备上看看会不会有什么问题,让 UI 好好看看,会不会出现噪点、毛边等现象。
如果一个图片经过有损压缩最终导致其在 Assets.car 中 SizeOnDisk
值变得很大的话,但其在各个设备上的表现情况可以被接受,你可以尝试将其加到 bundle 中使用,并将其图片格式修改为 Data
,这样 Xcode 就不会对其进行压缩处理了。不过不要忘记将调用方法改为 imageWithContentOfFile:
。
jpg 转换成 png
在 Assets Catalogs 中添加的 jpg 文件,在最终的 .car 文件中会转换成 png 格式
当我们跟 UI 要图片时,如果 UI 扔给你一张 .jpg
或 jpeg
格式的图片,你会专业的说出,最好给我 PNG 图片,否则系统会转化成为 PNG的
。
首页,我们加进工程目录中的 JPG 图片构建以后不会经过任何压缩且其在包中的格式不会修改。
而对于添加进Assets Catalogs 中的 JPG 图片,我们需要解包.car
文件,我们以下面的测试 JPG 图片为例,其素材名称和文件名称都为 Untitledlogo
。assetutil
工具得到对应 JSON 的部分:
{
"AssetType" : "Image",
"BitsPerComponent" : 8,
"ColorModel" : "RGB",
"Encoding" : "JPEG",
"Name" : "Untitledlogo",
"NameIdentifier" : 16638,
"Opaque" : true,
"PixelHeight" : 300,
"PixelWidth" : 705,
"RenditionName" : "Untitledlogo.jpg",
"Scale" : 1,
"SHA1Digest" : "8AD0802190689DEC7778A28EEFFBE972F4121B5B",
"SizeOnDisk" : 68122,
"Template Mode" : "automatic"
}
多数逆向 .car 文件的工具,都是利用 Apple 的私有库 CoreUI.framework
模拟 Apple 访问图片的过程然后利用 pngcrush
解码恢复其中的 PNG 图片。本质是用 CoreUI.framework
中的 CUICatalog 提取其内容。
笔者使用的是Asset Catalog Tinkerer,选择我们的测试的 Assets.car
所得是两张PNG 图片,是的,是两张被解码过得 PNG 图片
.car
文件插件 [12] ,很多工具都是开源代码,可以查看具体查看。
总结
在项目中使用 PNG 或其他格式图片资源时,需要考虑其对整体包大小的影响。对于添加进工程中的 PNG 资源,APP 都会对其进行处理,以便更快速的读取。
添加进 Asset Catalog 中的 PNG 图片,无损压缩的 PNG 图片在其最终构建中,其压缩方式可能并没有什么作用,但是有损压缩的话,可能与 Apple 本身的压缩方法起冲突 ,导致负优化
。
总的来说,如果是 Asset Catalog PNG 图片资源来说,一般不需要主动压缩的。
参考资料
CgBI_file_format:iphonedev.wiki/index.php/C…[8]
抖音品质建设 - iOS 安装包大小优化实践篇:juejin.cn/post/691631…[9]
今日头条 iOS 安装包大小优化 - 新阶段、新实践:mp.weixin.qq.com/s/oyqAa8wKd…[10]
PNG图片压缩对比分析:mp.weixin.qq.com/s/0ajYgnP3m…[11]
TinyPNG4Mac:github.com/kyleduo/Tin…[12]
ImageOptim-CLI:github.com/JamieMason/…[13]
Portable Network Graphics (PNG) Specification (Second Edition):www.w3.org/TR/PNG/[14]
pngdefry ‒ Repairing -iPhone fried PNGs:www.jongware.com/pngdefry.ht…[15]
pngcheck:www.libpng.org/pub/png/app…[16]
Chapter 9. Compression and Filtering:www.libpng.org/pub/png/boo…[17]
Analysing Assets.car file in iOS:stackoverflow.com/questions/2…[18]
QuickLook 的可视化 .car
文件插件:blog.timac.org/2018/1112-q…[19]
Asset Catalog Tinkerer:github.com/insidegui/A…[20]
参考资料
https://github.com/kyleduo/TinyPNG4Mac
[2]https://tinify.cn/
[3]https://github.com/JamieMason/ImageOptim-CLI
[4]http://pngmini.com/
[5]https://imageoptim.com/
[6]http://jamiemason.github.io/ImageOptim-CLI/comparison/png/photoshop/desc/
[7]http://www.manifold.net/doc/mfd9/palette_images.htm
[8]https://iphonedev.wiki/index.php/CgBI_file_format
[9]https://juejin.cn/post/6916317500992913421
[10]https://mp.weixin.qq.com/s/oyqAa8wKdioI5ZDG5LjkfA
[11]https://mp.weixin.qq.com/s/0ajYgnP3mZt75-WVmBohcg
[12]https://github.com/kyleduo/TinyPNG4Mac/
[13]https://github.com/JamieMason/ImageOptim-CLI
[14]https://www.w3.org/TR/PNG/
[15]http://www.jongware.com/pngdefry.html
[16]http://www.libpng.org/pub/png/apps/pngcheck.html
[17]http://www.libpng.org/pub/png/book/chapter09.html
[18]https://stackoverflow.com/questions/22630418/analysing-assets-car-file-in-ios
[19]https://blog.timac.org/2018/1112-quicklook-plugin-to-visualize-car-files/
[20]https://github.com/insidegui/AssetCatalogTinkerer
- EOF -
看完本文有收获?请分享给更多人
关注「 iOS大全 」加星标,关注 iOS 动态
点赞和在看就是最大的支持❤️