[iOS/OC]iOS10.3.3上APNG不动
0x0背景
原本是放到自己博客的,不怎么用了,把文章同步过来,原文地址[iOS/OC]iOS10.3.3上APNG不动
问题现象:有1张APNG动图,在其他系统上都OK,单单在iOS10.3.3上不动。特别迷,花了一下午的时间排查这个问题。最终咨询了@DreamPiggy ,才把这个问题搞清楚。
0x1原因
APNG的数据块有IHDR、acTL、fcTL、IDAT、fdAT等类型。每帧APNG的图像由1个FCTL,>=1个(通常是1~2个)IDAT图像组成。其中FCTL是每帧的控制信息,IDAT是图像信息。在iOS10.3.3系统中,当每帧APNG的fdAT>=3时,系统解码会出错,导致解码失败,并取第一帧信息作为解码结果,即展示PNG静图。
简单的说,即:
iOS 10.3.3系统BUG,在APNG较大且较复杂时,APNG解码失败,返回首帧PNG静图。
0x2APNG
分析过程中,第一步就是查询APNG的标准,发现这块文档十分匮乏,尤其是中文文档。在此,按个人理解整理一份。
1.APNG结构
一个流传甚广的图如下。PNG的基本结构是PNG签名(PNG Signature)+图像头(IHDR)+数据块(IDAT)+结束块(IEND),4部分组成,而APNG则是在此基础上扩展,主要是增加了acTL控制块保存整体动图控制信息,将N张图片的IDAT取出来作为每一帧的信息,并在每一帧增加fcTL控制卡保存每帧图像的控制信息。
[图片上传失败...(image-90acc5-1537881630217)]
2.手工解码APNG
只知道APNG的结构,当出现APNG相关问题的时候,你还是不知道是怎么回事。下面我以上面有问题的APNG为例,手工解码APNG。从结构上分,APNG有PNG签名、数据块,两种类型。
1)PNG签名
整个文件的前8个byte是PNG签名头,为8950 4e47 0d0a 1a0a,将这8个byte转为ascii就是PNG
[图片上传失败...(image-c96bb8-1537881630218)]
2)数据块类型
数据块(chunk)常见类型有:IHDR、acTL、fcTL、IDAT、fdAT、IEND
基本格式如下:
序号 | 描述 | 长度(byte) |
---|---|---|
1 | chunk内容长度 | 4 |
2 | chunk类型 | 4 |
3 | chunk内容 | 由1chunk内容长度决定 |
4 | 校验码 | 4 |
其中chunk类型将其由hex转为ascii,即为对应的值,如:
[图片上传失败...(image-e3dd5b-1537881630218)]
①IHDR
长度(byte) | 内容 | 意义 |
---|---|---|
4 | 0000 000d | chunk内容长度为13 byte |
4 | 4948 4452 | IDHR |
13 | 0000 0465 0000 01ea 0806 0000 00 | 见下标 |
4 | ad34 f3f4 | 校验码 |
IHDR的内容意义如下:
描述 | 长度(byte) | 内容 |
---|---|---|
图片宽度 | 4 byte | 0000 0465 |
图片高度 | 4 byte | 0000 01ea |
图像深度 | 1 byte | 8 |
颜色类型 | 1 byte | 6 |
压缩方法 | 1 byte | 0 |
过滤方式 | 1 byte | 0 |
扫描方式 | 1 byte | 0 |
②acTL
长度 | 内容 | 意义 |
---|---|---|
4 byte | 0000 0008 | chunk内容长度为8 byte |
4 byte | 6163 544c | acTL |
8 byte | 0000 000a 0000 0000 | 前4byte为帧数,10帧;后4byte为循环次数,无限循环; |
4 byte | ad34 f3f4 | 校验码 |
③fcTL
长度 | 内容 | 意义 |
---|---|---|
4 byte | 0000 001a | chunk内容长度为26 byte |
4 byte | 6663 544c | fcTL |
26 byte | 00 0000 0000 0004 6500 0001 ea00 0000 0000 0000 0000 0c00 6400 00 | 略 |
4 byte | 50 8aec fb | 校验码 |
④IDAT
长度 | 内容 | 意义 |
---|---|---|
4 byte | 00 0080 00 | chunk内容长度为32768 byte |
4 byte | 4944 4154 | IDAT |
32768 byte | 略 | 略 |
4 byte | 876e ca46 | 校验码 |
3)数据块类型补充
在动图中,第1帧称为关键帧,其他帧信息在压缩算法下需要有第1帧计算得来。在APNG中,关键帧就是IDAT,第2帧开始为fdAT。
根据数据块类型可知,通常37byte开始,为acTL数据块,可以以此作为是否为APNG的标识。但是这个不是强制的,你也可以自己定义数据块类型,在IHDR之后添加相应信息。
例如,为APNG添加了签名和时间戳后,在Safari下显示是正常的,在Chrome下就无法正常加载。即:Chrome和Safari对于APNG的标准解读不同,且明显Chrome对APNG的标准支持不完善。
3.参考文章
PNG规范中文解读:png的故事:获取图片信息和像素内容
PNG标准英文文档:PNG (Portable Network Graphics) Specification, Version 1.2
APNG标准英文文档:APNG Specification
APNG介绍:APNG那些事
APNG分析网站:https://animatedpngs.com/
HEX转ascii网站:https://www.rapidtables.com/convert/number/hex-to-ascii.html
0x3问题分析
1.分析图片数据
在有以上的对APNG的知识储备后,就可以开始进行正式的问题分析了。
对多组图片手动解码,分析fdAT数据发现,fdAT在同一帧中连续3次或以上,会导致在iOS10.3.3上解码失败。结论以猜测为主,部分证实,暂无实锤。相关数据就不放了。有兴趣的同学可以自己解析一下看看。
2.ImageIO的符号断点
ImageIO有一个LogDebug函数,添加符号断点后,可以断到这个符号,侧面印证了此时系统APNG解码失败。
0x4总结
有必要总结下。各大厂对图片格式的解读是不一致的,尤其是在动图上,在一些特定场景下就会踩坑。目前我已知的有:
1.本文的问题,APNG的fdAT>=3时,苹果系统(iOS/mac os)解码失败,变为静图;
2.APNG添加签名后,Android解码失败,无法展示;
3.安卓和苹果对GIF的循环次数理解不一致,通常可以在苹果解GIF时循环次数加1,以保持多端一致;
其他隐藏的坑不知道还有多少。