0x0背景
原本是放到自己博客的,不怎么用了,把文章同步過(guò)來(lái),原文地址[iOS/OC]iOS10.3.3上APNG不動(dòng)
問(wèn)題現(xiàn)象:有1張APNG動(dòng)圖,在其他系統(tǒng)上都OK,單單在iOS10.3.3上不動(dòng)。特別迷,花了一下午的時(shí)間排查這個(gè)問(wèn)題。最終咨詢了@DreamPiggy ,才把這個(gè)問(wèn)題搞清楚。
0x1原因
APNG的數(shù)據(jù)塊有IHDR、acTL、fcTL、IDAT、fdAT等類(lèi)型。每幀APNG的圖像由1個(gè)FCTL,>=1個(gè)(通常是1~2個(gè))IDAT圖像組成。其中FCTL是每幀的控制信息,IDAT是圖像信息。在iOS10.3.3系統(tǒng)中,當(dāng)每幀APNG的fdAT>=3時(shí),系統(tǒng)解碼會(huì)出錯(cuò),導(dǎo)致解碼失敗,并取第一幀信息作為解碼結(jié)果,即展示PNG靜圖。
簡(jiǎn)單的說(shuō),即:
iOS 10.3.3系統(tǒng)BUG,在APNG較大且較復(fù)雜時(shí),APNG解碼失敗,返回首幀PNG靜圖。
0x2APNG
分析過(guò)程中,第一步就是查詢APNG的標(biāo)準(zhǔn),發(fā)現(xiàn)這塊文檔十分匱乏,尤其是中文文檔。在此,按個(gè)人理解整理一份。
1.APNG結(jié)構(gòu)
一個(gè)流傳甚廣的圖如下。PNG的基本結(jié)構(gòu)是PNG簽名(PNG Signature)+圖像頭(IHDR)+數(shù)據(jù)塊(IDAT)+結(jié)束塊(IEND),4部分組成,而APNG則是在此基礎(chǔ)上擴(kuò)展,主要是增加了acTL控制塊保存整體動(dòng)圖控制信息,將N張圖片的IDAT取出來(lái)作為每一幀的信息,并在每一幀增加fcTL控制卡保存每幀圖像的控制信息。
[圖片上傳失敗...(image-90acc5-1537881630217)]
2.手工解碼APNG
只知道APNG的結(jié)構(gòu),當(dāng)出現(xiàn)APNG相關(guān)問(wèn)題的時(shí)候,你還是不知道是怎么回事。下面我以上面有問(wèn)題的APNG為例,手工解碼APNG。從結(jié)構(gòu)上分,APNG有PNG簽名、數(shù)據(jù)塊,兩種類(lèi)型。
1)PNG簽名
整個(gè)文件的前8個(gè)byte是PNG簽名頭,為8950 4e47 0d0a 1a0a,將這8個(gè)byte轉(zhuǎn)為ascii就是PNG
[圖片上傳失敗...(image-c96bb8-1537881630218)]
2)數(shù)據(jù)塊類(lèi)型
數(shù)據(jù)塊(chunk)常見(jiàn)類(lèi)型有:IHDR、acTL、fcTL、IDAT、fdAT、IEND
基本格式如下:
| 序號(hào) | 描述 | 長(zhǎng)度(byte) |
|---|---|---|
| 1 | chunk內(nèi)容長(zhǎng)度 | 4 |
| 2 | chunk類(lèi)型 | 4 |
| 3 | chunk內(nèi)容 | 由1chunk內(nèi)容長(zhǎng)度決定 |
| 4 | 校驗(yàn)碼 | 4 |
其中chunk類(lèi)型將其由hex轉(zhuǎn)為ascii,即為對(duì)應(yīng)的值,如:
[圖片上傳失敗...(image-e3dd5b-1537881630218)]
①I(mǎi)HDR
| 長(zhǎng)度(byte) | 內(nèi)容 | 意義 |
|---|---|---|
| 4 | 0000 000d | chunk內(nèi)容長(zhǎng)度為13 byte |
| 4 | 4948 4452 | IDHR |
| 13 | 0000 0465 0000 01ea 0806 0000 00 | 見(jiàn)下標(biāo) |
| 4 | ad34 f3f4 | 校驗(yàn)碼 |
IHDR的內(nèi)容意義如下:
| 描述 | 長(zhǎng)度(byte) | 內(nèi)容 |
|---|---|---|
| 圖片寬度 | 4 byte | 0000 0465 |
| 圖片高度 | 4 byte | 0000 01ea |
| 圖像深度 | 1 byte | 8 |
| 顏色類(lèi)型 | 1 byte | 6 |
| 壓縮方法 | 1 byte | 0 |
| 過(guò)濾方式 | 1 byte | 0 |
| 掃描方式 | 1 byte | 0 |
②acTL
| 長(zhǎng)度 | 內(nèi)容 | 意義 |
|---|---|---|
| 4 byte | 0000 0008 | chunk內(nèi)容長(zhǎng)度為8 byte |
| 4 byte | 6163 544c | acTL |
| 8 byte | 0000 000a 0000 0000 | 前4byte為幀數(shù),10幀;后4byte為循環(huán)次數(shù),無(wú)限循環(huán); |
| 4 byte | ad34 f3f4 | 校驗(yàn)碼 |
③fcTL
| 長(zhǎng)度 | 內(nèi)容 | 意義 |
|---|---|---|
| 4 byte | 0000 001a | chunk內(nèi)容長(zhǎng)度為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 | 校驗(yàn)碼 |
④IDAT
| 長(zhǎng)度 | 內(nèi)容 | 意義 |
|---|---|---|
| 4 byte | 00 0080 00 | chunk內(nèi)容長(zhǎng)度為32768 byte |
| 4 byte | 4944 4154 | IDAT |
| 32768 byte | 略 | 略 |
| 4 byte | 876e ca46 | 校驗(yàn)碼 |
3)數(shù)據(jù)塊類(lèi)型補(bǔ)充
在動(dòng)圖中,第1幀稱(chēng)為關(guān)鍵幀,其他幀信息在壓縮算法下需要有第1幀計(jì)算得來(lái)。在APNG中,關(guān)鍵幀就是IDAT,第2幀開(kāi)始為fdAT。
根據(jù)數(shù)據(jù)塊類(lèi)型可知,通常37byte開(kāi)始,為acTL數(shù)據(jù)塊,可以以此作為是否為APNG的標(biāo)識(shí)。但是這個(gè)不是強(qiáng)制的,你也可以自己定義數(shù)據(jù)塊類(lèi)型,在IHDR之后添加相應(yīng)信息。
例如,為APNG添加了簽名和時(shí)間戳后,在Safari下顯示是正常的,在Chrome下就無(wú)法正常加載。即:Chrome和Safari對(duì)于APNG的標(biāo)準(zhǔn)解讀不同,且明顯Chrome對(duì)APNG的標(biāo)準(zhǔn)支持不完善。
3.參考文章
PNG規(guī)范中文解讀:png的故事:獲取圖片信息和像素內(nèi)容
PNG標(biāo)準(zhǔn)英文文檔:PNG (Portable Network Graphics) Specification, Version 1.2
APNG標(biāo)準(zhǔn)英文文檔:APNG Specification
APNG介紹:APNG那些事
APNG分析網(wǎng)站:https://animatedpngs.com/
HEX轉(zhuǎn)ascii網(wǎng)站:https://www.rapidtables.com/convert/number/hex-to-ascii.html
0x3問(wèn)題分析
1.分析圖片數(shù)據(jù)
在有以上的對(duì)APNG的知識(shí)儲(chǔ)備后,就可以開(kāi)始進(jìn)行正式的問(wèn)題分析了。
對(duì)多組圖片手動(dòng)解碼,分析fdAT數(shù)據(jù)發(fā)現(xiàn),fdAT在同一幀中連續(xù)3次或以上,會(huì)導(dǎo)致在iOS10.3.3上解碼失敗。結(jié)論以猜測(cè)為主,部分證實(shí),暫無(wú)實(shí)錘。相關(guān)數(shù)據(jù)就不放了。有興趣的同學(xué)可以自己解析一下看看。
2.ImageIO的符號(hào)斷點(diǎn)
ImageIO有一個(gè)LogDebug函數(shù),添加符號(hào)斷點(diǎn)后,可以斷到這個(gè)符號(hào),側(cè)面印證了此時(shí)系統(tǒng)APNG解碼失敗。
0x4總結(jié)
有必要總結(jié)下。各大廠對(duì)圖片格式的解讀是不一致的,尤其是在動(dòng)圖上,在一些特定場(chǎng)景下就會(huì)踩坑。目前我已知的有:
1.本文的問(wèn)題,APNG的fdAT>=3時(shí),蘋(píng)果系統(tǒng)(iOS/mac os)解碼失敗,變?yōu)殪o圖;
2.APNG添加簽名后,Android解碼失敗,無(wú)法展示;
3.安卓和蘋(píng)果對(duì)GIF的循環(huán)次數(shù)理解不一致,通??梢栽谔O(píng)果解GIF時(shí)循環(huán)次數(shù)加1,以保持多端一致;
其他隱藏的坑不知道還有多少。