在从零开始手敲次世代游戏引擎(三十五)当中我们完成了衣裙贴图的加载和绘制。但是所谓画龙点睛,我们的模型目前仍然是没有?的。之所以这样,因为眼睛的贴图是PNG格式的,而目前我们只写了BMP和JPEG格式的解析器。

那么让我们立即开始PNG格式解析器的编写。
经过BMP和JPEG格式的锤炼,我们对此应该已经是比较驾轻就熟的了。首先依然是从FrameWork/Parser当中现有的代码拷贝生成PNG.hpp,然后根据参考引用当中的PNG格式的规格说明,修改这个代码。
与JPEG格式类似,PNG格式也是将数据组织为Chunk单位进行存储的。其Chunk的头部通过4个ASCII字母(8bit)类型进行标识。比较有意思的是,在这个4字母的标识符当中,PNG格式使用了字母的大小写来表示Chunk的一些属性。比如:
-
第一个字母大写表示该Chunk是一个基本Chunk,也就是PNG标准规定的必须有的Chunk。相对的,如果小写则表示该Chunk是一个扩展Chunk,也就是说是可有可无的;
-
第二个字母大写表示该Chunk表示符是经过国际标准化组织标准化过的,而小写则表示是未经标准化由应用私自扩展的(也就是说如果小写,表示这部分是没有互换性的);
-
第三个字母目前必须大写。小写保留给今后,目前还没想好有什么用;
-
第四个字母表示这个Chunk能否原样复制到经过处理之后的图片当中,即使我们并不知道这个Chunk是用来干啥的。如果小写,那么我们可以无脑地将其复制到处理之后另存的图片当中,即使我们不知道这个Chunk的含义;如果大写,那么如果我们对第一个字母为大写的Chunk进行了任何修改,就不能原样复制第四个字母为大写的Chunk。换句话说,第四个字母表示该Chunk当中的数据是否对第一个字母为大写的Chunk有依赖性。
图片的数据是存储在IDAT这个chunk里面。将数据提取出来之后,首先要将其解压缩。与JPEG不同,PNG文件格式是无损压缩格式,它采用得数据压缩就是LZ77,也就是我们熟知的zip压缩包采用的压缩方式。(严格来说,zip当中可以有多种压缩算法,一些是被申请了专利的。而LZ77是public的)
LZ77压缩也可以理解为一种动态的霍夫曼编码,细节上与JPEG采用的霍夫曼有一点不同。我们可以基于之前在JPEG解析器当中编写的霍夫曼树的代码编写LZ77的解压缩代码。也可以导入成熟的库-zlib。我这里采用的是后者。
zlib的使用方法可以参考参考引用*5。
在完成解压之后,需要对数据进行反过滤(defiltering)。为了提高压缩率,我们需要减少数据的带宽,也就是减少数据的统计方差。更为通俗一点来讲的话,我们需要让数据尽可能地集中在0的两边,而不是分布在取值范围所容许的所有值上(0-255)。
在前面介绍的JPEG当中,我们是通过一个被称为quantization的过程实现这个目的的。而在PNG当中,使用一种称为prediction(预测)的算法实现这个目的。这个算法其实是利用图像的连续性,使用当前像素周边已知的像素值来预测当前像素的值。(在数学意义上,就是相当用偏导数替代值)。对于大多数图像来说,导数的变化幅度要远低于值的变化幅度,从而能够起到减少带宽的目的。
对于这个偏导数的选择(也就是用周边的哪个或者哪几个像素预测当前像素的值),PNG规范规定了几种方式,并且以行(Scan Line)为单位,对方式进行了选择。也就是说,在解压出来的数据当中,每行数据的第一个字节,并不是图像数据的一部分,而是对于这个预测公式的一个选择参数。
最后完成的代码在这里:
运行测试的结果如题图所示。
注意目前的代码只考虑了color type为6,也就是rgba类型的非索引png图片格式。其它的类型稍加修改就可以对应,但是目前还未编写。
参考引用