APNG 格式
为了更简单的让非专业人士理解,先了解下字符编码,为了不那么复杂,本文仅介绍2个辅助理解字节码,一个是常规的UTF8,一个是跟本文有关的HEX
如果你使用记事本打开一张图片,会出现一串的乱码,这个其实是以UTF8编码的方式呈现,可以很简单的在node.js里使用Stream复现这个效果
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,直接在浏览器中预览。
<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
表示本帧的序号为000 00 00 94
表示本帧的宽度为 0x94 === 148 像素,高度也类似- 后面的 8字节00表示当前帧的位置是无偏移的
00 32 03 E8
表示当前帧的播放延时为 0x32 / 0x03E8 即 50 / 1000 === 50ms01
表示本帧的清除方式为 【清除为背景】00
表示本帧的混合方式为 【覆盖】
关于清除方式 ,混合方式,可以看一下这篇文章 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
,偏移到下一块的开始循环执行,否则直接结束。关键代码如下:
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
停止读取,关键实现如下:
let isAnimated = false;
parseChunks(bufferBytes, (type) => {
if (type === 'acTL') {
isAnimated = true;
return false;
}
return true;
});
if (!isAnimated) {
reject('Not an animated PNG');
return;
}
5 条评论
为什么中间那个软件下载不了啊,悲催的
最近私人网盘抽风了,过些时候来看哦
Lazada我没研究过,但是是阿里技术团队输入的技术,阿里系得产品(比如alioss对象存储)一般都会在图片上传后进行裁剪或者转码以降低图片存储大小,你看下图片的后缀是不是一串webp_90_300_200之类的
博主真好 可惜不太看得懂QAQ