PNG 文件格式和压缩介绍
status
category
date
summary
slug
icon
tags
password
之前立的flag终归是要还的,为了更好地优化项目中的图片资源,提出最优的压缩方案,先针对PNG图片格式做一个相对深入一点的学习。
什么是PNG
PNG的全称是便携式网络图形(Portable Network Graphics),是目前最流行的网络传输和展示的图片格式,设计它的初衷是为了代替GIF,它的特点也正是相对于GIF的优势:
- 免费:很久之前GIF专利是收费的,这也是PNG发展起来的原因。
- 无损压缩:PNG采用了基于LZ77的压缩算法,使得它压缩率更高,且不损失数据。
- 体积小:PNG利用特殊的编码方式标记重复出现的数据,使体积更小。可以联想一下计数排序。
- 可变透明度:PNG独有的透明通道,支持对原图像添加256个透明层次,这个特性为web和移动应用的图标设计提供了极大的便利。
相关概念
透明度
透明度的实现主要有三种方式:
- 不透明:最简单直接,整张图片每个像素都不透明
- 索引透明:记录某个像素点是否透明,要么 100% 透明,要么完全不透明
- Alpha 透明:可以指定像素点的透明度,例如 50% 透明
色彩
图像色深度指的是每个像素所使用的位数,用于确定彩色图片每个像素可能有的颜色数,或者灰度图像每个像素可能有的灰度级数。注意色深度和位深度是两个概念。若色彩深度为 n,则每个像素所用的位数为 n,每个像素就有2n 种颜色选择,例如:8 位图片指的是每个像素有 256 种可用的颜色。另外,所以谈到色深或者位深的时候,要尤其注意单位,是
位深
还是位深/通道
,具体的区别可以参考下面的 PNG 类型。色彩的实现由三种方式:
- 灰度(greyscale):描述从白到黑的程度
- 真色彩(true color):每个像素保存自己原始的、真实的 RGB 颜色值
- 索引色(indexed color):为图片创建一张颜色表,其中保存了所有用到的颜色和对应的颜色值,并为它们编号,可以理解为颜色的宏定义。这样做可以减小图片的文件大小,但缺点是颜色种类的数量受到颜色表的限制。
PNG类型
我们一般谈论的PNG类型:
PNG 8
:8 代表 8 位,每个像素支持存储 256种颜色。支持索引透明和 Alpha 透明。
PNG 24
:24 代表 24 位,即 8位/通道,RGB 每个通道都可以表示 256 种颜色,因此总共可以表示的颜色为256 的三次方,即 16777216 种颜色。PNG 24
能表示的颜色更多,也意味着文件会更大。PNG 24
是不透明的。
PNG 32
:32 代表PNG 24
再加上 8 位的透明通道,也就是说PNG 32
能表示的颜色和PNG 24
一样多,并且还支持 256 种透明颜色。
但是根据上面介绍的透明度和色彩模式,PNG 还支持很多种图片:调色板图片(包含 24 位 RGB 或 32 位 RGBA 的索引)、灰度图片(有或无 Alpha 通道)和真色彩图片(RBG 或 RBGA)。
隔行扫描
隔行扫描(Interlacing),也称交错扫描(interleaving),是一种位图编码的方式,编码时不严格按照相邻行进行。当连接速度比较慢时,采用隔行扫描的图片可以渐进地进行显示,即先显示较为模糊的版本,然后逐渐变清晰。如图:

我们常用的 GIF、PNG、JPEG 都采用了隔行扫描的方式,其中 PNG 采取的是二维、七通道的Adam7,二维表示扫描支持竖直和水平方向,7代表一张图片会被拆分为7个子图片。

和 GIF 采取的一维、四通道算法相比,PNG 在网速相同的情况下,尤其是网速较慢时,能够更快地显示出图片的基本外貌。
过滤
过滤(filter)是将图片信息变得容易压缩的过程,整张图片只会使用一种过滤方法,而过滤类型会被插到每个扫描行的前面,以方便压缩。目前 PNG 图片指定的过滤方式只有一种(method 0),所以实际使用时,只需要指定过滤类型。过滤类型指的是像素基于相邻像素的变化规律,通过这个变化规律即可推断出某像素的预测值,为了便于描述,规定了相邻像素的方位:左(A)、上(B)、左上(C)。对 method 0 来说,,共有 5 种:
类型值 | 名称 | 预测值 |
0 | None | 0 |
1 | Sub | A |
2 | Up | B |
3 | Average | A和B的平均值,向下取整 |
4 | Paeth | A,B,C 中最接近 p = A + B -C 的一个 |
选择合理的过滤类型可以大大提高压缩效率,但如果采用了隔行扫描,每个阶段的子图片都会被分别过滤,即支持了渐进式传输,当然压缩效率也会下降。
文件结构
了解了 PNG 的基本类型和相关概念,我们可以深入了解一下 PNG 的文件结构,推荐使用 pngcheck 进行 PNG 图片格式的分析。
PNG 文件由一个文件署名(file signature,也叫文件头)和多个数据块(chunks)组成,其中数据块也分为关键数据块(critical)和辅助数据块(ancillary)。关键数据块共有 4 个,每个 PNG 文件必须包含 3 个或以上的关键数据块,而辅助数据块是可选的。
文件署名
文件署名由 8 个字节组成,是用来识别文件格式的,它的值为:
89 50 4E 47 0D 0A 1A 0A
,只要一个文件的署名为该值,就认为它是 PNG 格式的,这些值都有特殊的含义:十六进制值 | 含义 |
89 | 该值可以检测当前环境是否支持八位编码,并且大于ASCII最大值,可以避免被识别为文本文件 |
50 4E 47 | 在 ASCII 中对应 PNG 三个字母,方便被文本编辑器识别 |
0D 0A | CRLF 换行符; 0D 代表 ASCII 13,即\r ,CR;0A 代表ASCII 10,即\n ,LF |
1A | 在 DOS 系统下标志文件类型的结束 |
0A | LF 换行符 |
数据块
文件署名之后,紧接着就是一系列的数据块,每个数据块都包含图片的描述信息,数据块由四个部分组成:
名称 | 长度 | 说明 |
Length | 4 bytes | 指定 Chunk Data 的长度 |
Chunk Type | 4 bytes | 指定数据块类型 |
Chunk Data | 可变 | 按照Chunk Type存放指定数据 |
CRC | 4 bytes | 检查是否有错误的循环冗余代码 |
这里要注意 Length 保存的是该数据块中 Chunk Data 的长度,也就是说,一个数据块的长度应该为 Length 的值加上12 bytes。
Chunk Type 由4个字符组成,:第一个字符是否大写(critical),决定了该数据块是否是关键(critical)数据块;第二个字符大写表示公开,小写表示私有;第三个字符规定必须大写;第四个字符大写则表示只能在关键数据块不变时被复制,小写则表示任何情况下能复制。
图片处理的相关软件或者解码器必须支持关键数据块,否则就不能正确地读取 PNG 文件,接下来介绍四个关键数据块和其他辅助数据块:
IHDR
这个数据块被称为文件头数据块,必须作为第一个数据块,并用13个字节按照顺序来表示图像数据的基本信息:
域 | 长度 | 说明 |
Width | 4 bytes | 图片宽度,以像素为单位 |
Height | 4 bytes | 图片高度,以像素为单位 |
Bit Depth | 1 byte | 图像深度 |
Color Type | 1 byte | 颜色类型 |
Compression Method | 1 byte | 压缩算法 |
Filter Method | 1 byte | 滤波方法 |
Interlace Method | 1 byte | 扫描方法 |
根据颜色类型和是否有 Alpha 通道,可以有多个 Color Type:
Color Type | 可能的Bit Depth | 说明 |
0(000) | 1,2,4,8,16 | 灰度 |
2(010) | 8,16 | RGB |
3(011) | 1,2,4,8 | 索引色 |
4(100) | 8,16 | 有 Alpha 通道的灰度 |
6(110) | 8,16 | RGBA |
虽然这个数据块给 Color Type 预留了8位空间,但目前只有低三位被使用,并且只有上述五种类型是被允许的。实际上每一位的数据是有自己的含义的,1 表示支持该属性,0 表示不支持,从最低位开始:
- 第一位:是否有调色板
- 第二位:是否有三通道
- 第三位:是否有 Alpha 通道
PLTE
调色板(palette)数据块,包含了索引色图像(indexed color image)的相关数据,如果存在,要放在图像数据块(IDAT)之前。调色板实际上是一个彩色索引表,表的项目数范围是 1~256,每个项有 3 个字节分别用来储存RGB 三个值,所以调色板数据块最大字节数为 768。而调色板数据块只能有一个,所以调色板严重限制了图片的颜色种类。
PLTE的存在需要根据上面的 Color Type 来决定:
- 索引色(Type 3)必须有该数据块
- 真色彩(Type 2,6)图片也可以有该数据块的相关内容,目的是便于非真色彩显示程序量化图像数据
- 灰度(Type 0,4)图片不允许出现该数据块
IDAT
存储实际的图片数据,一个图片数据流可能包含多个连续顺序的 IDAT 数据块。创建 IDAT 数据块的步骤:
- 将扫描获得的图片信息及其大小储存,具体的图片信息和大小规则在 IHDR 中保存
- 采取 IHDR 指定的过滤方法,对图像数据进行过滤。目前只有定义了method 0
- 采取 IHDR 指定的压缩方法,对过滤的数据进行压缩
可以看到 IHDR 非常重要,存储了很多关键信息,包括输出的数据和压缩算法。如果想要读取图片信息,将上面三个步骤逆序执行即可。
IDAT 数据块不是必要的,也可以同时存在多个,但是它们必须是连续排列的。
IEND
表示 PNG 文件结束,必须放在最后面,且 Chunk Data 为空。
辅助数据块
接下来用表格形式简单介绍一下各个辅助数据块:
符号 | 说明 |
bKGD | 指定默认的背景颜色 |
cHRM | 校准色度 |
dSIG | 数字签名 |
eXIf | Exif metadata 信息 |
gAMA | 指定亮度的伽马校准值 |
hIST | 色彩直方图,估算颜色的使用频率 |
iCCP | |
iTXt | 以键值对方式存储可能的压缩文本和翻译信息 |
pHYs | 指定像素大小或图片比例 |
sBIT | 除了指定位深度(8,16 等等)外,保存原始数据以便恢复 |
sPLT | 提出建议使用的调色板,可能有多个 |
sRGB | 指明是否使用sRGB color space |
sTER | 储存立体视图(stereoscopic)的相关信息, |
tEXt | 保存作者等键值对的文本信息 |
tIME | 上次修改时间 |
tRNS | 保存透明信息,分为不透明、全透明、Alpha |
zTXt | 保存压缩后的文本信息以及对应的压缩方式标识 |
压缩
上面介绍了一些关于 PNG 的基本概念,但了解这些都是为了一个终极目的:压缩。我个人的理解是,世界上没有真正的无损压缩,压缩代表占用的空间更小,必定损失了信息。只不过有些损失的信息不需要、不重要,甚至可以恢复,那么我们就说这时无损压缩。例如,显示在 web 页面和移动端的图标,它们的作用只是为了构建界面,如果我们不影响肉眼可辨别范围内的色彩、透明度等信息,仅仅删除了它附带的文本信息(如由Photoshop制作),这可以理解为无损压缩。
压缩的本质是对信息的浓缩,用更少的空间表达更多的信息。什么样的图片最适合压缩呢?抛开色彩丰富度不谈,就是最有规律的图片,例如一张纯色的图片。假设本来它需要为每个像素储存RBGA这些信息,现在我只需要保存一个像素,然后说明整张图片都由这个像素组成即可。这样的要求未免太过苛刻,不过规律还可以是变化的规律。
PNG的压缩分为两个步骤:预解析(过滤)和压缩。开始我们提到了过滤,它是通过寻找相邻像素的规律把图片变得容易压缩,例如对于下面这张图片:

这是一张渐变图片,从左到右依次变深,即使规律性不如纯色图片明显,但依然有规律可循。如果我们定义白色为 0,红色为 10 ,并且假定这张图片宽度只有 10 个像素,那么可以用数组表示出依次的颜色:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
我们希望找到相邻像素的规律,这里选择用数组记录和左边像素的颜色差值,可以表示为:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
又找到了差值相同的规律,于是我们只需要说明每个像素的颜色都是左边像素加一,这样就只需要记录
1
这一个数字,再记录该规律就可以表达图片的信息了,节省了很多空间。正式的压缩阶段会把预处理得到的数据进行 Deflate 压缩,它是一种流式压缩方法,同时使用了 LZ77 算法和 哈夫曼编码。它也是一种免费的压缩算法,我们常用的 zip、zlib、zopfli 等压缩方式都使用了 Deflate 算法。
参考资料:
Loading...