前言
日常工作中,我们系统会将各种图片格式缩略、截段并转为png、jpg格式。今天从PNG图片开始,来看一下PNG格式是什么,以及他到底是怎么压缩的。
PNG图片简介
PNG是一种无损压缩、体积小、支持透明度的图片格式。图片在计算机中可以抽象的想象为一个二维数组,每个元素都是一个像素点,每个像素点有自己的颜色,合并为一个二维数组,就是一个图片。
PNG又可以分为三类
● png 8:每个像素用八位标识,每个像素有256种颜色
● png 24:每个像素24位表示,每个像素用三个8bit去表示R(红)、G(绿)、B(蓝)数值。
● png 32:每个像素32位表示,和png24不一样的是png32使用8bit去表示A(透明通道)。
PNG的具体格式这里不详细展开,今天主要讲解一张如此大的原始图片是如何进行编码、无损压缩为一张体积小的PNG图片。
PNG编码原理
假设现在有一张原始图,现在需要将它压缩,并且能够将压缩后的图片原封不动的再解压为原始图进行展示,一般有一种叫利用冗余思路。一张图片他其实很多像素点都是相同的,我们其实可以对相同的像素点将32位进行压缩、引用。下面将介绍PNG采用的两种压缩算法:哈夫曼编码、LZSS编码。
DEFALTE压缩算法
哈夫曼编码
假设有一张图,他的很多像素点的值是相同的,我们可以将出现频率最多的像素点分配给较少的比特数记录,例如有如下的一张图(这里只看其中RGB中的一种颜色),这是一个8*8像素的图:
我们可以统计出这里有25个251,21个92,14个147,4个252,那么就可以构造一个哈夫曼树:
构造的方式为,出现次数越多的,在树上保存的位置越高,占用的位数越少,对应到上述例子中,251就用0一位表示,92用10二位表示,252用110三位表示,147用111三位表示。在这个例子中,总共有四种像素,需要两位表示每个像素,而如果采用哈夫曼编码,每个像素占用的大小就为(25*1+21*2+4*3+14*3)/(8*8)=1.89bit/像素。因为在解码过程中还要知道编码方式,所以最终大小还需要保存一个编码版本,在大图中,这种方式确实能够节省很大的空间。
实际上,PNG用到的哈夫曼编码比这个复杂,在进行哈夫曼编码前,图片还需要经过LZSS编码。
LZSS算法
哈夫曼编码还有个问题,虽然它能将所有相同像素合并,但实际上这些相同的像素还能够进行压缩,作用上面那副图的例子就是,把像素数量25、21、4、14这些分子再进行压缩。
下面直接讲解LZSS算法是如何进一步进行压缩的。图片一般来说相邻像素都是一样的,这个叫空间冗余,LZSS就是通过解决空间冗余进行压缩。
LZSS是一个利用回返引用/向前引用为思想的压缩算法,他有一个窗口,窗口内又分为前视缓冲区和搜索缓冲区,这个窗口是一个FIFO的列表,在前视缓冲区的数据会尝试去匹配搜索缓冲区的数据。下面具体看一下LZSS是如何压缩的。
LZSS编码压缩
这里,我们拿一段文本举例LZSS算法是如何进行压缩的,假设现在有一段文本,滑动窗口大小为7,前视缓冲区大小为2,搜索缓冲区大小为5
那么在进行压缩的时候,根据FIFO的特点
数据在读取到上图所示时,dede所在的前视缓冲区就能从搜索缓冲区的abcde中找到de,得到下图的压缩结果,其中2代表前面两位的引用,4代表有四个字符进行了压缩,其中两个字符进行了一次循环。
这里,原来的9个字符大小就压缩为了7个字符大小。而一个图片他的相邻像素很可能出现一大片区域都是同一种颜色,那么压缩比就比较大了。这也是为什么相同像素的两张图,不同的画面内容,他们的大小可能是截然不同的。
LZSS解码
从上述的编码case倒推,我们就可以很轻易的得到解码后的结果。
预解析滤波
滤波编码
当图像出现相邻像素之间并不是完全相同的像素点时,上述的算法压缩效果就不好了,例如下面的像素分布图,那么这个时候,我们在压缩之前,还需要一个预处理操作,为滤波
可以发现,当我们这里如果存储相邻元素的差值,那么就可以得到一个差值像素
这个时候再去通过LZSS编码压缩,得到的压缩结果就很小了。
上面这种滤波方式称之为相邻差值滤波,和相邻差值滤波类似的还有:
● NONE:无滤波,即采用原始像素点进行LZSS压缩(图像素大小小于八位时,就用这种滤波方式)
● UP:与上面一个像素做差
● SUB:相邻像素做差(上述例子)
● AVG:当前像素值减去左边、上边的平均值
● PAETH:计算当前像素和左、上、左上的像素的差值,并取最小的差值
注意,对于边界的像素点,左、上、左上的像素点为空的取0
那么现在就会出现一个问题,如此多的滤波方式,如何知道哪种滤波方式能让LZSS压缩的最小呢?对于一张未知的图片,可能采用不同的滤波方式得到的压缩能力就完全不同。假设每行都算一次,那么一个20*20像素的图,可能出现的滤波方式就有5^20种可能,这样显然不现实。
最小绝对差值和
PNG通过一个启发式算法来决定合适的滤波器,叫【最小绝对差值和】。
假设现在像素如下:
假设现在用的SUB滤波器并对256取模(和前一个像素做差值):
我们将[128, 255] 的分数映射到 [-128, -1]:
之后我们将每行滤波后的结果的绝对值相加,例如这里用SUB滤波器计算出的【最小绝对差值】为341。
那么这种算法对每行都进行一次五种方式的滤波,然后取【最小绝对差值和】最接近0的滤波方式为当前这行的实现,即认为这样的方式更有可能存在重复数据。
滤波解码
只要知道每行的滤波方式,那么解码到原始像素值也就很简单了。
总结
本文主讲了PNG是如何压缩的,可以归纳为以下步骤:
- PNG将未压缩的RGB图像,逐通道(R、G、B)、行的进行滤波。
- 滤波后用LZSS算法进行压缩。
- LZSS结果用哈夫曼编码进一步压缩。
可以看到,PNG的每个编码都是可以完全回归解码的,这就是为什么他是无损压缩。当然,也可以看到他的压缩算法很复杂,所以一张图转码为png的时候,他是很慢的,但他解码速度还凑合,所以他仍然是现在主流的图片。