我突然理解什么是解释器了,解释器的话全称应该理解为字节码解释器。本质上是对特定的数据赋予一定的规则下的意义,然后用同样的规则可以去解读同一个数据,这个实际上就是信息论。我们的解释器,机器码之类的也是这样,数据本没有意义,但是特定的规则赋予了其意义即信息。

这里我要讲到的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格式的图像,文件格式主要由三个部分组成:

  1. 文件头
       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线性空间

这里我们文件头主要是用来说明图像的信息,并不会影响后面的内容

  1. 编码数据

这里省略等下说

  1. 结尾填充
         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~10~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
  • 获取图像的高度,宽度,颜色通道和颜色空间
  • 对数据块进行解码(逐字节的进行解析)
    • 判断标签确定模块
    • 根据模块选择解码方式

经过以上步骤,我们可以实现程序的无损压缩转换。同时我们也可以理解字节码解释器的意义。实际上是根据一种规则,来对特定的数据进行解释,从而实现各种功能