便携式网络图形(PNG)规范(第二版)
2003年11月10日W3C正式推荐
当前版本: work byte order)。
而现在的主机一般使用的 X86、ARM 处理器都是小端模式,被称为主机字节顺序(host byte order)。
PNG 使用网络字节顺序。
普通的真彩图片,至少由红、绿、蓝三个通道组成,也就是每个像素占用了三个字节以上的空间。这样图片的压缩效率就很低。
因此,我们构建一个调色板,在调色板里预设好我们需要使用到的颜色,后续使用时,只需要提供调色板的索引位置就可以了。
而且为了防止调色板占用过大空间,我们把调色板的容量设定在256以内。索引位置也就不会超过256,只需要一个字节就可以表示,一个字节只占用一个通道,因此索引模式下,只有一个通道。
一个图片是由许多像素点组成的,可以视作一个二维数组。我们可以提取其中几个特殊的位置,比如横行每隔一个提取一个像素,竖列每隔一个提取一个像素,这样组成一个新的二维数组,可以简略失真的表示原图。这种提取方法叫做 Pass 提取(pass extraction),提取之后形成的数据是交错的,每段都可以包含整个图片的缩略部分。只需要重新组合就能产生完整的图像,即使没有完整的数据,只要其中一部分就可以得到缩略图。是网络传输中加快图像传输的方式,不过现在网速很快,几乎用不到了。
色图的中间区域是白色的,被我们称作白点,是色图的一个参数。另一个参数是基准,用作色图的平移。
我们可以设置白点的坐标,来改变中间白色区域,使其向红色、绿色、蓝色偏移,用以调整色图的变化程度。
PNG 规范不指定应用程序接口,但是涉及四种图像:原图、标准图像、PNG 图像、交付图像。关系如下:
二进制编码占用的比特数,就是样本深度。
PNG 有三种管理色彩空间的方法:使用 ICC 配置、使用 sRGB 配置、使用色度基准和白点位置配置。
ICC 配置比较灵活,易于适配;sRGB 配置需要设置一个特定的颜色空间,可能会占用较多的容量;最后一种比较精确。前两种也推荐使用伽马值。
我们需要通过一些手段,将标准图像转换为 PNG 图像。流程如下:
分离透明通道,实际上,很多标准图像没有透明通道,这样可以默认为无透明度,节省一个通道。
如果不同像素值的个数少于 256,样本深度小于等于8,可以开始构建索引。
如果,颜色样本深度一致,而且每个通道都一样的值,可以用一个通道来表示所有,即灰度图。
不用 alpha 通道表示透明度的一种方法,需要设置背景色。
不是所有的深度都被 PNG 支持,只有 1、2、4、8、16,如果不是这些数的话,深度就要通过软件调节。
比如原始深度是 5,现在要把它变成 8,也就是扩大了。
如果不同通道有不同的深度,我们就会选取最大深度来调整。
这种深度变换是可逆的。
一***有五种:
这里采用两种方法进行 Pass 提取。
第一种是空方法,也就是什么都不做。(所以为什么要这么死板的把这个也计作一种方法)
第二种通过多次扫描得到七个缩小图。也就是 Adam7 算法(不是深度学习的那个 Adam 算法)。
不过这个算法在国内网站这个几乎找不到,维基百科 https://en.wikipedia.org/wiki/Adam7_algorithm )介绍的也不是很清楚。所以我这里就简单说一下:
把上面的到的缩小图(当然空方法读出来的是原图),逐行再读取一遍。(这里的操作就可以有很多了,比如把上面的提取变成一个 yield)
有几种过滤类型,会把过滤类型写到过滤数组之前。
就是编码加密。
把编码后的数据分成一块或者多块。
一个标准的 PNG 文件由许多块组成,每个块有四个部分:长度、名称、数据主体、校验码。
标准的 PNG 定义有 18 种块类型,此外你可以添加自定义的各种块。
这 18 种块类型有:
关键块:
IHDR(image header 文件头)、PLTE(palette 调色板)、IDAT(image data 图片内容)、IEND(image end 文件结尾)
辅助块:
透明相关:tRNS(transparency information 透明信息)
颜色相关:cHRM(chromaticities and white point 色度和白点)、gAMA(gamma 伽马值)、iCCP(embedded ICC profile 嵌入式 ICC 概述)、sBIT(significant bits 有效位)、sRGB(standard RGB colour space 标准RGB颜色空间)
文本相关:iTXt(international textual data 国际化文本)、tEXt(textual data 文本)、zTXt(zip textual data 压缩的文本)
时间相关:tIME(last-modification time 最新修改时间)
其他:bKGD(background colour 背景色)、hIST(histogram 直方图)、pHYs(physical pixel dimensions 物理像素尺寸)、sPLT(Suggested palette 建议调色板)、
传输错误或文件损坏,这会破坏数据流的大部分或全部;语法错误,出现无效块或者丢失块。
两种错误处理方式要区别。
你可以向 ISO/IEC 或者 PNG Group 提交相关扩展,注册新的块类型和文本关键字,拓展新的过滤算法、交错模式的算法、压缩算法。
就是数据流的二进制的结构啦。
所有 PNG 数据流的前八个字符,都是 137 80 78 78 71 13 10 26 10
用 bytes 表示就是 b'\x89PNG\r\n\x1a\n'
这个签名表示接下来的数据都是 PNG 数据流,如果遇到空字符不要打断,需要出现 IEND 才算结束。
每个块由这四个部分组成:
通过名称约定,使得 PNG 解码器在不能识别当前块的用途时,也能通过名称来获取相关信息。
块的名称有四位:
第一位表示辅助,小写表示这个块是辅助块,大写表示这个块是关键块。
第二位表示私有,小写表示这个块是私有的,而非国际标准定义的,大小表示前述 18 种块类型。
第三位是保留位,小写表示这个块是被抛弃的,大写表示可以使用。(用于约定将来的扩展)
第四位表示复制安全性,也就是 PNG 编辑器在编辑图片的时候,如果遇到不安全的数据块,就不会完全的复制,而是有选择的,大写表示 PNG 编辑器可以完全的复制,而不需要担心任何问题。
具体参考 crc32 算法
因为 PNG 图像是可以流式读取的,也就是说不需要读到文件尾,就可以在 PNG 浏览器里预览了。
所以有些东西需要在读取图像内容之前准备好,比如索引调色板。
好像在 4.3 章节写过了?
我们在 4.4 章写过,有五种颜色类型:
颜色类型记录在 IHDR 里。
灰度模式下,亮度取决于 gAMA、sRGB、iCCP,如果没有这些,则取决于机器。
颜色样本不一定和光强成正比,可以通过设置 gAMA 来调节。
值的计算方法如下: 初始为 0,使用了调色板加上1,使用了真彩加上2,使用了透明通道加上4。灰度下是无法使用索引。
有四种方式表示透明:使用透明通道、使用 tRNS 块设置透明颜色信息、索引中在 tRNS 设置 alpha 表、不使用透明通道也不使用 tRNS 表示完全不透明。
透明通道的样本深度是 8 和 16,透明通道保存在像素之中, 表示完全透明, 表示完全不透明。透明度用于图像前景色和背景色的复合。
一些普通的图片不包含透明度,甚至已经把像素值乘以透明度,提前做好了以黑色为背景的复合步骤;但是 PNG 不这么做。
整形(int)是多位字节,short 是两个,int 是四个,long 是八个。
PNG 使用的是网络字节顺序,MSB 在高位,LSB 在低位。
也就是每个 PNG 图像的行,紧凑排列各个像素。
深度少于 8 时,扫描线结尾可能是凑不齐字节,这些不使用的字节不进行处理。
滤波器可以提高压缩数据的压缩性,而且是可逆的。PNG 允许过滤扫描线数据,换句话说就是可以不进行滤波。
过滤后的字节序列与过滤前的相同,但是根据不同的滤波类型,会在最前面添加一个字节的标记。如果没有增加长度就表明没有过滤过。具体过滤方法,在后面解释。
交错模式可以提高 CRT 显示器上网络图片的加载速度(换句话,没有网络,没有 CRT 显示器,交错模式就没有用了)。
参考[4.5.2 Pass 提取](#4.5.2 Pass 提取)
介于 Adam7 的特点,宽或者高小于5的图像会缺少缩略图(2在第五列,3在第五行)。
过滤的目的在于提高压缩率。过滤的方法不是唯一的,交错模式下,所有缩小图都应该使用同一种方法的滤波器;非交错模式下,只有一张图,当然也是只有一种方法。
这个标准里定义了一种 0 号方法,其他的编号为将来保留。0 号方法包含五种类型的滤波器,每个扫描线可以使用不同的滤波器类型。
PNG 规范不强制滤波器的类型,具体的选择方法后面在说。
滤波器是针对字节的,和像素、通道、深度无关。只要给字节就能过滤。
这里是几个参数的定义:
Org() 表示字节原始值;
Flt() 表示过滤后的值;
Rc() 表示重构的值;
Paeth() 参见[9.4 滤波器类型4](#9.4 滤波器类型4)。
如果没有前一个像素,就用 0 代替。每个缩小图的第一行没有前一行,也用 0 代替。
因为使用了过滤,重建时也要按照这个顺序进行计算。
滤波器的输入输出值都是无符号字节。
0、1、2 三种类型的滤波器都很简单,隔列/隔行相减就是。
但是第三种类型, Flt(x) = Org(x) - floor((Org(a) + Org(b)) / 2) 中, Org(a) + Org(b) 存在溢出的情况,不能使用 byte 运算,应该是 short 或者更多位,当然也有右移的算法。
Paeth 算法先计算三个相邻像素(左、上、左上)的线性值,选择与计算值最接近的相邻像素再次计算。注意缓存不要溢出。它的函数如下:
与上面的过滤一样,默认的就是 0 号方法。这两个在 IHDR 里面有标记。
当然,这里用的是 zlib 的压缩,使用默认的等级为 8,压缩字节不超过 32768。
这个校验值和 PNG 块的校验值不一样,两者不能混同。
多个过滤后的行,会被打包压缩成一个 zlib 数据流,并放到多个 PNG 块里,多个 PNG 块解开得到的是一个 zlib 数据流。
当然,这个还涉及到异步读取。zlib 数据流本身就是可以中断的,即使中断,排列较前的数据还是可以读取出来的,这样才有交错模式的解读,所以针对上面的 python 方法,有如下改进:
构建一个持续的读取,边读取边解析。
下面是 18 个 PNG 规范块的介绍:
IHDR 是 PNG 数据流中的第一个块。组成如下:
所以 IHDR 块长度是 13 不会改变。
调色板是一个二维数组,可以看作 Array[n][3],用索引 n 来表示颜色。
因此,n 不会超过 256,PLTE 块的长度也是 3 的倍数。
调色板无论如何都是 8 位深度,即使图像是 1、2、4 的深度,调色板也还是 8。
所有的 IDAT 块合起来是一个 zlib 数据流,参考[10 压缩](#10 压缩)。
这段数据是空的,表示 PNG 数据流结束了。当然,这个块损坏也不会有事。
这是表示透明度信息的块,有三种构成:
灰度模式(凡是等于这个灰度的颜色被认做透明)
真彩模式(由这三个值表示一个透明色)
索引模式(索引模式下,tRNS 相当于一个 alpha 表,这个表和索引一样大,对应表示索引的透明度)
为什么灰度模式和真彩模式用 2 字节表示呢,因为需要适配 16 位深度,而索引模式深度永远小于等于 8。
这个块用于设置一个 CIE 色度空间。组成如下:
储存值是实际值的 100000 倍。
CIE 色度空间是一个二维图像,它由红绿蓝白四个点构建一个近视三角形的包围圈,来设置颜色的偏移程度。
这个块只储存了一个 unsigned int,同样的它的值需要除以 100000,得到实际的 gamma 值。
这个块用于设置一个 ICC 描述。
PNG 只支持固定的深度,如果原图深度不匹配,就会强制的放缩,但是原始信息会保留在这里,用于恢复原始图像。(所以一般不会用到,为什么要把标准图像转化成非标准图像呢)
针对不同通道数,有不同的 sBIT 长度。
使用 sRGB 色彩空间,这时候不能用 ICC 描述了。sRGB 只包含有一个 unsigned byte,表示渲染意图。
值的意义如下:
使用 sRGB 时,推荐使用 gAMA、cHRM,因为有些设备不支持 sRGB,这样就可以兼容性使用。
上面是一个文本信息会使用到的关键字,关键字其实不是很关键,就是一个定义而已,可以自己改动。但是符合上述的是可以被图像软件标准读取。
tEXt 含有如下成分:
当然,这里的压缩方法也是 0,用 zlib 解压后面的数据
国际文本数据,有点高大上的感觉。
语言种类参见 RCF-3066、ISO 646、ISO 639。
背景色哦。
直方图给出了调色板中每种颜色的近似使用频率。
如果 PNG 浏览器无法提供调色板里所有的颜色,那么直方图可以辅助创建相似的调色板供使用。
当然,现在不存在不能提供完整的调色板的软件了。
这个块用于表示像素在屏幕上的实际尺寸。结构如下:
单位说明有两个,False 的时候表示这个块只表示长宽的比,而不是真实值;True 的时候以米为单位,即一个单位为一米,一米包含多少个像素。
具体的通道长度,由样本深度决定,深度为 16 就是两个,小于等于 8 就是一个。
调色板名称是区分大小写的,并受到与关键字参数相同的限制。
在灰度PNG图像中,每个目通常相等的红色、绿色、蓝色和蓝色值,但这不是必需的。
每一个频率值与图像的像素的比例成正比,不是实际频率。
这里用宇宙时间(Universal Time,UTC)
后面的公式太复杂了,算了,直