前言
不知道有没有玩过恋活人发现,人物卡是一个PNG图片文件,而这个图片文件里虽然表面上是证件照,但它还包含了人物照以及捏脸数据。
写本文的目的就是解释恋活的PNG文件是什么样的。
PNG文件结构
PNG的文件结构算是简单的,就两个部分:PNG签名(Signature)和PNG块(Chunks)。
PNG签名长度为8个字节。
Public Const PngSignature As Long = &HA1A0A0D474E5089
第一个字节是0x89,最高位是置位的以防止被当做文本文件打开
第2-4字节对应字母是PNG。
第5-6字节是CRLF换行。
第7字节是EOF。
第8字节是LF换行。
每个PNG块按顺序分四部分:块大小,块类型,块数据,块CRC。
除了块数据之外,每个成员均为4字节。
PNG文件不讲究文件对齐,所有数据均紧凑排列。
块大小和块CRC字段均为大端序,因此在x86的电脑上要逆字节序使用。
块类型为4字节字符串。
第一个字符的大小写决定它是否是重要的块。若为大写,则该块是重要的块。
第二个字符的大小写决定它是否是标准的块。若为小写,则该快是私有定义的块。
第三个字符按照PNG标准,必须是大写。
第四个字符的大小写决定它是否与重要的块相关。若为大写,则当有重要的块被修改时,该块不能被直接复制使用。
通常而言,一个PNG文件里必然会有IHDR ,IDAT 和IEND 三个块。其中IHDR 必须是第一个块,IEND 必须是最后一个块。
IHDR块
IHDR 块表示PNG文件头,只有13个字节,定义如下:
Public Structure PngChunkIHdr
Dim Width As Integer
Dim Height As Integer
Dim BitDepth As Byte
Dim ColorType As Byte
Dim CompressionMethod As Byte
Dim FilterMethod As Byte
Dim InterlaceMethod As Byte
End Structure
其中Width 表示图片横向的像素单位长度,Height 表示图片纵向的像素单位长度,ColorType 表示像素类型。
注意Width 和Height 也都是大端序的。
像素类型的值的各类定义为:
Public Enum PngPixelFormat
Grayscale = 0
TrueColorRGB = 2
IndexedPalette = 3
GrayscaleAlpha = 4
RGBA = 6
End Enum
IEND块
IEND 块表示PNG文件尾,没有块数据。遍历块到此时应当结束遍历。
IDAT块
IDAT 块表示图片数据。一个PNG文件里可以有好几个IDAT 块来表示一个图片。
PNG使用DEFLATE无损压缩算法,因此IDAT 块里的数据必须解压后再用。
.NET的DeflateStream 似乎不能解压PNG的IDAT 块,且由于PictureBox 控件能显示PNG文件,我没必要舍本逐末地自行把PNG解析成位图后再显示出来。
恋活的PNG文件
我最初的猜想是恋活的PNG文件里塞了一个私有的块,而经过解析后验证,并没有什么私有的块,它是在IEND 块之后追加内容的。
根据多次测试,人物照应该在IEND 块的34字节之后。它的结构同样是个PNG文件。
中间的34字节应该是恋活的签名(可以看见KoiKatuCharaS 字样)。
而在人物照的IEND 块之后的就是具体的捏脸数据了。本文不作深入解析了。
程序源码
本文采用Visual Basic 2010进行WinForm开发,在GitHub上开源。
|