介绍
文档主要是描述获取png字节流,转换像素数据的过程。
大致过程:
1. 获取字节流
2. 解析数据块
3. 解压
4. 过滤
5. 最终像素
png格式
目前解析的是RGBA格式的图片,IHDR数据块显示的过滤方式是0,压缩方式0。其它格式的png,解析过程的差异主要会体现在过滤这个过程。
签名
Png 文件字节流,固定开头。
16进制数据:89 50 4E 47 0D 0A 1A 0A 对应的ASCII 50-P 4E-N 47-G PNG
数据块
PNG定义了两种类型的数据块,一种是关键数据块,另一种是辅助数据块(可选数据块)。关键数据块定义了4个标准的数据块。既是IHDR,PLTE(调色板数据块),IDAT,IEND。本文解析的图片是RGBA32位的图片,就不介绍PLTE。
每个数据块格式固定
- IHDR
文件头数据块IHDR(header chunk,13个字节)包含PNG文件中存储的图像数据的基本信息,包括分辨率、比特深度、色彩模式、压缩方法,是非常重要的数据块,必须作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。
下图是 3* 3 图片
00 00 00 0D 10进制结果:13 chunkData 长度13
49 48 44 52 ASCII IHDR
00 00 00 03 图片宽度 3
00 00 00 03 图片高度 3
08 图片深度 06 颜色类型
00 压缩 00 过滤 00 扫描方法
CRC: 56 28 B5 BF
2. IDAT
生成IDAT需要先经过Filter(具体算法由IHDR指定),再经过Deflate(具体算法由IHDR指定)。这个数据块可以包含多个,但是必须连续。图片显示只包含一个
00 00 00 1A 长度 -26
49 44 41 54 ASCII IDAT
08 1D 63 64 …00 37 9E 01 9E 一共26个 这块数据包含压缩数据 和前两个标识数据
CRC:9F 19 1D 7E
3. IEND
数据格式固定
00 00 00 00 49 45 4E 44 AE 42 60 82
00 00 00 00 长度-0
49 45 4E 44 ASCII IEND
AE 42 60 82 CRC
压缩
PNG中的Deflate与gzip、zlib中的deflate原理一样,结合了LZ77和Hoffman算法。
图片显示的压缩方式是 0
具体压缩算法,百度。
// 使用System.IO.Compression进行Deflate压缩
public byte[] MicrosoftCompress(byte[] data)
{
MemoryStream uncompressed = new MemoryStream(data); // 这里举例用的是内存中的数据;需要对文本进行压缩的话,使用 FileStream 即可
MemoryStream compressed = new MemoryStream();
DeflateStream deflateStream = new DeflateStream(compressed, CompressionMode.Compress); // 注意:这里第一个参数填写的是压缩后的数据应该被输出到的地方
uncompressed.CopyTo(deflateStream); // 用 CopyTo 将需要压缩的数据一次性输入;也可以使用Write进行部分输入
deflateStream.Close(); // 在Close中,会先后执行 Finish 和 Flush 操作。
byte[] result = compressed.ToArray();
return result;
}
解压
图片显示的压缩方式是 0,所以使用zlib去解压。
解压记得把得到的IDAT 前两位去掉。要不然无法正常解压。有兴趣可以去了解这前两个字节的含义。
解压得到的数据:
// 使用System.IO.Compression进行Deflate解压
public byte[] MicrosoftDecompress(byte[] data)
{
MemoryStream compressed = new MemoryStream(data);
MemoryStream decompressed = new MemoryStream();
DeflateStream deflateStream = new DeflateStream(compressed, CompressionMode.Decompress); // 注意: 这里第一个参数同样是填写压缩的数据,但是这次是作为输入的数据
deflateStream.CopyTo(decompressed);
byte[] result = decompressed.ToArray();
return result;
}
解压的数据截图:
33 的图片IDAT解压后得到313 个字节
334(RGBA)+3(每行的过滤方式) =39 个字节
数据读取是从左到右读取,由上到下扫描
每一行的开头 001 002 002 代表每行的过滤方式
过滤
边界值都是0 处理,值大于255需要value%256
a:同行前一个像素相同位置的值
b:前一行同位置的值
c:b位置前一个像素相同位置的值
Filt(x): 过滤后的值
Orig(x): 原值
Recon(a): a 位置的原值
Recon(b): b 位置的原值
floor((Recon(a)+Recon(b))/2): 均值
PaethPredictor(Recon(a),Recon(b),Recon©)
函数逻辑图:
- 过滤0:
返回过滤值 Filt(x) - 过滤1:
返回Filt(x)+Recon(a) - 过滤2:
Filt(x)+Recon(b) - 过滤3:
Filt(x)+(Recon(a)+recon(b))/2 - 过滤4:
PaethPredictor(Recon(a),Recon(b),Recon©),具体看逻辑图。
过滤后得到值:
得到9个像素值
工具
HxD
参考
https://cloud.tencent.com/developer/article/1857732 数据解析
https://mp.weixin.qq.com/s/XGzuSF-L-qKriyRGcGs-KA Lz77 压缩
https://www.ffutop.com/posts/2019-05-10-png-structure/ 图片解析
https://www.w3.org/TR/2003/REC-PNG-20031110/ png 规范