我突然理解什么是解释器了,解释器的话全称应该理解为字节码解释器。本质上是对特定的数据赋予一定的规则下的意义,然后用同样的规则可以去解读同一个数据,这个实际上就是信息论。我们的解释器,机器码之类的也是这样,数据本没有意义,但是特定的规则赋予了其意义即信息。
这里我要讲到的QOI就是这么一种规则,图像的压缩规则。我们知道我们的原图像的每个像素的内容,这个就是数据就是我们的信息,每个像素需要3~4个字节来存储,这就导致在像素较多的情况下,我们所需的空间就很多。我们可以通过特定的方式对其进行再编码从而实现对信息的压缩,然后用同样的方式对其进行解码。
直接开始吧
QOI
QOI (The Quite OK Image Format) 是一种新的无损图像压缩方式, 它在保持压缩率与 PNG 相近的同时 (比 PNG 大 ~35%), 编码速度达到了 PNG 的 20~50 倍, 而解码速度也有 PNG 的 3~4 倍, 并且它极简的编码解码方式也是一个极大的亮点. 可以在 QOI 的主页 上找到更多信息.
在这个文档中可以看到QOI的具体规则QOI编码规则
图像格式
这里QOI只支持24位RGB和32位RGBA格式的图像,文件格式主要由三个部分组成:
- 文件头
0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 | 13
文件头 "qoif" width height channel colorspace
这里的magic指的是魔法数字,我们的魔法数字是“qoif”用于解码的时候识别格式
channel指的是颜色通道,3为RGB,4为RGBA
colorspace则是颜色空间,0为sRGB非线性空间,1为Linear线性空间
这里我们文件头主要是用来说明图像的信息,并不会影响后面的内容
- 编码数据
这里省略等下说
- 结尾填充
0 1 2 3 4 5 6 7
填充字节 0 0 0 0 0 0 0 1
有7位0和一位1组成的填充字节,用于检测结束
图像编码
这里我们要介绍QOI的编码规则,一共分为六个压缩模块,我们分别介绍一下功能:
QOI_OP_RGB
┌─ QOI_OP_RGB ────────────┬─────────┬─────────┬─────────┐
│ Byte[0] │ Byte[1] │ Byte[2] │ Byte[3] │
│ 7 6 5 4 3 2 1 0 │ 7 .. 0 │ 7 .. 0 │ 7 .. 0 │
│─────────────────────────┼─────────┼─────────┼─────────│
│ 1 1 1 1 1 1 1 0 │ red │ green │ blue │
└─────────────────────────┴─────────┴─────────┴─────────┘
将当前的像素值写入文件内,不进行压缩编码,该模块的标识码是0xFE,RGB的压缩程度为133%
QOI_OP_RGBA
┌─ QOI_OP_RGBA ───────────┬─────────┬─────────┬─────────┬─────────┐
│ Byte[0] │ Byte[1] │ Byte[2] │ Byte[3] │ Byte[4] │
│ 7 6 5 4 3 2 1 0 │ 7 .. 0 │ 7 .. 0 │ 7 .. 0 │ 7 .. 0 │
│─────────────────────────┼─────────┼─────────┼─────────┼─────────│
│ 1 1 1 1 1 1 1 1 │ red │ green │ blue │ alpha │
└─────────────────────────┴─────────┴─────────┴─────────┴─────────┘
同上,该模块的编码的标识码是0xFF,RGBA的压缩程度为125%
QOI_OP_INDEX
┌─ QOI_OP_INDEX ──────────┐
│ Byte[0] │
│ 7 6 5 4 3 2 1 0 │
│───────┼─────────────────│
│ 0 0 │ index │
└───────┴─────────────────┘
在进行编码时,我们会初始化一个缓存长度为64的数组running_array,将其中所有的元素初始化为RGBA(r=0,g=0,b=0,a=0)和RGB(r=0,g=0,b=0,a=255),每次我们对其进行hash转换index_position = (r*3+g*5+b*7+a*11),然后将对应的索引覆盖为当前的像素值,如果之后再次遇到相同的像素值时,我们我们可以直接使用index来表示这个像素值。
对于RGB信息,压缩程度为33%。对于RGBA信息,压缩程度为25%。这个模块的标识码是0b00
QOI_OP_DIFF
┌─ QOI_OP_DIFF ───────────┐
│ Byte[0] │
│ 7 6 5 4 3 2 1 0 │
│───────┼─────┼─────┼─────│
│ 0 1 │ dr │ dg │ db │
└───────┴─────┴─────┴─────┘
如果当前像素值和上一个像素值差距较小(差值在-2~1即0~3)时,我们可以通过计算不同像素值的差值,来压缩存储,其中:
- dr = now_r - last_r
- dg = now_g - last_g
- db = now_b - last_b
这里的-2~1需要+2转换成0~3进行存储,在转换时需要要注意。同时只能用于A值不变的情况下
对与RGB信息,压缩程度为33%。对于RGBA信息,压缩程度为25%。这个模块的标识码是0b01
QOI_OP_LUMA
┌─ QOI_OP_LUMA ───────────┬─────────────────────────┐
│ Byte[0] │ Byte[1] │
│ 7 6 5 4 3 2 1 0 │ 7 6 5 4 3 2 1 0 │
│───────┼─────────────────┼─────────────┼───────────│
│ 1 0 │ diff green │ dr - dg │ db - dg │
└───────┴─────────────────┴─────────────┴───────────┘
在自然图像中相邻像素的颜色的变化往往在RGB通道上有相关性(当一个颜色上主要体现为深度变化时,其他通道上的颜色通常变化较小),尤其是绿色通道通常能代表整体亮度的变化趋势。所以我们以绿色为基准,通过计算其他通道相对于绿色通道的差值来进行存储。
- diff_green = now_g - last_g (差值范围为
-32~31->0~63) - dr - dg = (now_r - last_r) - diff_green (差值范围为
-8~7->0~15) - db - dg = (now_b - last_b) - diff_blue (差值范围为
-8~7->0~15)
对于RGB信息,压缩程度为67%。对于RGBA信息,压缩程度为50%。标识码为0b10
QOI_OP_RUN
┌─ QOI_OP_RUN ────────────┐
│ Byte[0] │
│ 7 6 5 4 3 2 1 0 │
│───────┼─────────────────│
│ 1 1 │ run │
└───────┴─────────────────┘
如果当前的像素值和上一个像素的像素值相同,我们就可以使用这个模块来计算。从当前像素开始有多少个像素值是相同的,将他的数量记作run,其范围为1~62->记作0~61。这里我们之所以不使用63和64是因为,在run=63的情况下这个字节会变成0xFE,在run=64的情况下这个字节会变成0xFF从而影响解码。
对于RGB信息,压缩程度为33%。对于RGBA信息,压缩程度为25%。标识码为0b11
编码选择
QOI编码的目的是尽可能的压缩数据的内容,也就是在实际编码的过程中,我们应该尽可能的使用压缩度高的编码方式:
QOI_OP_RUN <= QOI_OP_INDEX = QOI_OP_DIFF <= QOI_OP_LUMA <= QOI_OP_RGB(A)
尽管QOI_OP_INDEX与QOI_OP_DIFF的压缩度相等,但是考虑计算量的话,我们更多的使用QOI_OP_INDEX
图像解码
解码也就是编码的逆过程:
- 读取文件头判断是否为QOI图像格式——头标识为
qoif - 获取图像的高度,宽度,颜色通道和颜色空间
- 对数据块进行解码(逐字节的进行解析)
- 判断标签确定模块
- 根据模块选择解码方式
经过以上步骤,我们可以实现程序的无损压缩转换。同时我们也可以理解字节码解释器的意义。实际上是根据一种规则,来对特定的数据进行解释,从而实现各种功能