Loading... ## APNG 格式 > 为了更简单的让非专业人士理解,先了解下字符编码,为了不那么复杂,本文仅介绍2个辅助理解字节码,一个是常规的UTF8,一个是跟本文有关的HEX > > - 如果你使用记事本打开一张图片,会出现一串的乱码,这个其实是以UTF8编码的方式呈现,可以很简单的在node.js里使用Stream复现这个效果 > > ```javascript > const fs = require('fs') > // 以1.png创建一个可读写的stream流 > let readerStream = fs.createReadStream('images/1.png') > // 设置编码格式 > readerStream.setEncoding('utf8') > > let data = '' > > // 使用readStream对象的EventEmitter监听事件 > readerStream.on('data', chunk => { > data += chunk > }) > readerStream.on('end', () => { > console.log(data) > }); > readerStream.on('error', err => { > console.log(err.stack) > }); > > // 所以软件可以修改apng,原理都是如此大同小异 > ``` > > - 或者也可以使用ES6中的FileReader API,直接在浏览器中预览。 > > ```javascript > <input id="input" type="file"> > <script> > let input = document.querySelector('#input') > input.addEventListener('change', e => { > let fileReader = new FileReader > fileReader.onload = f => { > console.log(f) //控制台打印数据,点击f.target.result右边的小图标即可预览 > } > fileReader.readAsArrayBuffer(e.target.files[0]) //以二进制读取 > }) > </script> > ``` > >  > - 如果你使用另外的工具,比如winhex,打开图片,则会以 hex的编码方式呈现,复现时只需要把上面的setEncoding设置为`hex` PNG文件是一种二进制的位图,由特定的文件头+若干文件块(chunk)组成 一个PNG文件的基本结构是这样的 ``` |-- PNG Signature --|-- IHDR --|-- IDAT --|-- IEND --| ``` PNG 签名表示这是一个PNG文件 IHDR 是图片的基本信息,如宽高,色彩等 IDAT 是具体图片图像数据块,一个PNG文件有可能包含多个IDAT数据块 IEND 表示一个PNG文件的结尾 PNG的文件块(chunk)是特定格式的二进制数据块,其基本格式如下 ``` |--4:长度--|--4:标识符--|--N:内容,长度由前面参数决定--|--4:CRC32--| ``` 一个基本的APNG文件是在PNG文件格式上增加acTL, fcTL等动画控制块形成的。 此处引用张现成的图片说明 一下  acTL是动画控制块,包括 帧数和播放次数 fcTL是帧控制块,包括帧的大小位置,序号,延时,清除方式,混合方式等信息 第一个fcTL块后面跟的是一个或多个 IDAT 块 第N个fcTL块后面跟的是一个或多个 fdAT 块 fdAT的内容构成上,比IDAT多了一个序号,这个序号是整个文件 fcTL和fdAT 两种块一起共享的 一个fcTL以及后面跟的所有内容块,组成了APNG的一个帧 ### acTL acTL块的格式如下 ``` |--4:长度0x08--|--4:acTL--|--4:帧数--|--4:循环数--|--4:CRC32--| ``` 结合原图我们查看一下内容,  * `00 00 00 08` 表示本块内容的长度(8字节)对于 acTL块来说是固定的 * `61 63 54 4C` 是 "acTL" 四字母的ASCII码 * `00 00 00 19` 表示本图片一共有0x19=== 25帧 * `00 00 00 00` 表示本图片的播放次数为:无限循环播放 ### fcTL fcTL块的格式如下 ``` (0) |--------------4:长度---------------|--------------4:fcTL---------------| (8) |--------------4:序列号-------------|--------------4:宽度----------------| (16)|--------------4:高度---------------|--------------4:X偏移--------------| (24)|--------------4:Y偏移-------------|----2:延时分子----|----2:延时分母----| (32)|-1:清除方式-|-1:混合方式-|-----------4:CRC32----------| ``` 既然acTL告诉我们一共有25帧,那么fcTL块就会有25个,我们先看一下第一帧的fcTL  * `00 00 00 1a` 表示本块内容的长度(0x1a,即26字节)对于 fcTL块来说是固定的 * `66 63 54 4C` 是 "fcTL" 四字母的ASCII码 * `00 00 00 00` 表示本帧的序号为0 * `00 00 00 94` 表示本帧的宽度为 0x94 === 148 像素,高度也类似 * 后面的 8字节00表示当前帧的位置是无偏移的 * `00 32 03 E8` 表示当前帧的播放延时为 0x32 / 0x03E8 即 50 / 1000 === 50ms * `01` 表示本帧的清除方式为 【清除为背景】 * `00` 表示本帧的混合方式为 【覆盖】 关于清除方式 ,混合方式,可以看一下这篇文章 [https://developer.mozilla.org/zh-CN/docs/Mozilla/Tech/APNG](https://developer.mozilla.org/zh-CN/docs/Mozilla/Tech/APNG) 在本篇文章的例子中,我们比较关注的是 序号,和fcTL的整体意义。 后续的帧就不重复写了,各帧的fcTL chunk ,字段意义是一样的。在本例子火狐图片中,除了序号和crc,都是一样的。 ### 校验APNG格式(js检测机制) 校验 APNG 格式就是判断文件是否存在类型为 `acTL` 的块。因此需要依序读取文件中的每一块,获取块类型等数据。块的读取是根据上文所述的 PNG 块的基本组成结构进行处理,流程实现如下图所示:  off 初始值为 8,即 `PNG Signature` 的字节大小,然后依序读取每一块。首先读取 4 个字节获取数据块长度 length,继续读取 4 个字节获取数据块类型,然后执行回调函数处理本块的数据,根据回调函数返回值 res、块类型和 off 值判断是否需要继续读取下一块(res 值表示是否要继续读取下一块数据,默认为 `undefined` 继续读取)。如果继续则 off 值累加 `4 + 4 + length + 4`,偏移到下一块的开始循环执行,否则直接结束。关键代码如下: ```javascript const parseChunks = (bytes, callback) => { let off = 8; let res, length, type; do { length = readDWord(bytes, off); type = readString(bytes, off + 4, 4); res = callback(type, bytes, off, length); off += 12 + length; } while (res !== false && type !== 'IEND' && off < bytes.length); }; ``` 调用 `parseChunks` 从头开始查找,一旦存在 `type === 'acTL'` 的块就返回 `false` 停止读取,关键实现如下: ```javascript let isAnimated = false; parseChunks(bufferBytes, (type) => { if (type === 'acTL') { isAnimated = true; return false; } return true; }); if (!isAnimated) { reject('Not an animated PNG'); return; } ``` 最后修改:2022 年 03 月 10 日 © 允许规范转载 打赏 赞赏作者 支付宝 赞 0 如果觉得我的文章对你有用,请随意赞赏
5 条评论
为什么中间那个软件下载不了啊,悲催的
最近私人网盘抽风了,过些时候来看哦
Lazada我没研究过,但是是阿里技术团队输入的技术,阿里系得产品(比如alioss对象存储)一般都会在图片上传后进行裁剪或者转码以降低图片存储大小,你看下图片的后缀是不是一串webp_90_300_200之类的
博主真好 可惜不太看得懂QAQ