iOS 工程中的 PNG 图片

4,166 阅读10分钟

什么是 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 122004 (header)
|   Copyright (C) 1998-2004,2006 Glenn Randers-Pehrson,
|   Copyright (C) 19961997 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 1for 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 扩大成为 215970Compress 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:利用 tinify 提供的API,目前 tinify 的免费版压缩数量是单次不超过 20 张且大小不超过 5 M。对于一般的 iOS 应用程序来说,足够日常开发的使用
  • ImageOptim-CLI:自动先后执行压缩率较高的为 ImageAlpha 的有损压缩 加上 ImageOptim 的无损压缩。

可以通过查看这个表格对比 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 深入了解 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

主要关注以下字段 CompressionEncodingSizeOnDisk


  • 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:

在这里查看图片压缩 [10] 更多理论基础。 

jpg 转换成 png

在 Assets Catalogs 中添加的 jpg 文件,在最终的 .car 文件中会转换成 png 格式

当我们跟 UI 要图片时,如果 UI 扔给你一张 .jpgjpeg 格式的图片,你会专业的说出,最好给我 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 图片

更多工具可以参照 Analysing Assets.car file in iOS [11] 中所提及的工具以及QuickLook 的可视化 .car 文件插件 [12] ,很多工具都是开源代码,可以查看具体查看。

总结

在项目中使用 PNG 或其他格式图片资源时,需要考虑其对整体包大小的影响。对于添加进工程中的 PNG 资源,APP 都会对其进行处理,以便更快速的读取。

添加进 Asset Catalog 中的 PNG 图片,无损压缩的 PNG 图片在其最终构建中,其压缩方式可能并没有什么作用,但是有损压缩的话,可能与 Apple 本身的压缩方法起冲突 ,导致负优化

总的来说,如果是 Asset Catalog PNG 图片资源来说,一般不需要主动压缩的。

参考资料

CgBI_file_format:iphonedev.wiki/index.php/C…

抖音品质建设 - iOS 安装包大小优化实践篇:juejin.cn/post/691631…

今日头条 iOS 安装包大小优化 - 新阶段、新实践:mp.weixin.qq.com/s/oyqAa8wKd…

PNG图片压缩对比分析:mp.weixin.qq.com/s/0ajYgnP3m…

TinyPNG4Mac:github.com/kyleduo/Tin…

ImageOptim-CLI:github.com/JamieMason/…

Portable Network Graphics (PNG) Specification (Second Edition):www.w3.org/TR/PNG/

pngdefry ‒ Repairing -iPhone fried PNGs:www.jongware.com/pngdefry.ht…

pngcheck:www.libpng.org/pub/png/app…

Chapter 9. Compression and Filtering:www.libpng.org/pub/png/boo…

Analysing Assets.car file in iOS:stackoverflow.com/questions/2…

QuickLook 的可视化 .car 文件插件:blog.timac.org/2018/1112-q…

Asset Catalog Tinkerer:github.com/insidegui/A…