翻出之前搭建的hexo博客

日常

一开始接触hexo是因为看了别人的GitHub Page。之后我也试着做了一个,期间还挑过好几次主题看哪个最简洁最现代风......

最近换了WordPress是因为看到了szszss的Shadermod教程,感觉这种页面风格挺不错(有一种怀旧感),想自己整个WP和Twenty Twelve试试效果。
(虽然上小学时用过WP,但用了一会之后就一直没理过,直到前几年接触了Gravatar之后WP才重回视野......)

如果没特殊情况的话我可能就选定WP而不会换其他博客系统了。至于之前的hexo文章,反正也没写啥东西,直接丢弃算了。。。之前写的PNG规范详解的草稿也不打算留,因为感觉不是很好qwq

等写完了QOI解码之后应该会写PNG格式(大概是?)

今日小趣事

日常

水个动态看到群友回复了,然后截图扔到群里
之后发生了个这么个情况(

做了个简陋的固定导航栏

日常

CSS好久之前学的,都快忘干净了,而且那时候学的也不精通。。。
一边开着F12开发工具一边鼓捣CSS做了个简易固定效果,感觉并不咋样,但能用就行(

Zzzz...

日常

高二开学第一天
目前想不到要干什么。。。

详解QOI图像格式

编程

大概在2021年的时候,出现了一种新的图像格式。这个格式简单高效,虽然压缩后的体积比PNG稍微大了一点,但是编码效率比PNG快了20多倍左右,解码效率也快了2倍。图像格式不仅开源还能自由使用。最重要的是它简单,它整个规范就只有一页,不像PNG的规范有80多页这么长。这个图像格式简单到琪露诺看了规范都能写个解码器出来。这个如此高大上的格式却有着一个特别随意的名字,叫做QOI(Quite OK Image)。
感觉可以翻译成“差不多得了图像格式”(笑)

我们先来看看它的规范文档:

是不是很一目了然?这个格式最大的特点就是简单。相信你熟读了规范之后,也能自己动手在几天时间内做出一个解码器。

接下来开始深究规范。

首先QOI定义了14字节的头部。最开始的4字节magic是文件标识,内容恒定为“qoif”,用来说明这是个QOI文件(在Linux上通常是看文件内容而不是后缀名来区分格式)。接着是两个32位大端序无符号整数width和height,用来标识图像的长度和宽度。下一个则是8位无符号整数channels,用来说明图像的通道信息。目前仅定义了3表示RGB图像,4表示RGBA图像。最后一个是8位无符号整数colorspace用来表示图像的色彩空间。0代表RGB通道的色彩空间是sRGB,Alpha通道是线性空间。1代表所有通道都是线性空间(通常用于渲染方面,感兴趣的可以去了解一下图形学中的Gamma校正)。另外提一嘴QOI图像是不预乘Alpha的。

Plain Text
qoi_header { char magic[4]; // magic bytes "qoif" uint32_t width; // image width in pixels (BE) uint32_t height; // image height in pixels (BE) uint8_t channels; // 3 = RGB, 4 = RGBA uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear };

之后便紧跟着一系列区块(Chunk),类似于PNG的数据块。区块以2-bit或者8-bit的标识作为开头来区分不同区块,并且区块比特长度能被8整除(意味着与字节对齐)。目前定义的区块有:

  • 0b11111110 QOI_OP_RGB:记录了完整的RGB像素

  • 0b11111111 QOI_OP_RGBA:记录了完整的RGBA像素(只有RGBA图像才会出现)

  • 0b00xxxxxx QOI_OP_INDEX:记录了索引,指向数组里的像素(这个数组下文会提到)

  • 0b01xxxxxx QOI_OP_DIFF:记录了与前一个像素的差值

  • 0b10xxxxxx QOI_OP_LUMA:也是记录了与前一个像素的差值,但允许记录的差值的范围更大

  • 0b11xxxxxx QOI_OP_RUN:行程编码,记录了重复之前像素的次数

注意,8-bit标识优先于2-bit标识。当解码器读到2个0b1比特的时候会接着向后读取判断它是不是QOI_OP_RGB或者QOI_OP_RGBA,如果都不是才会判断为QOI_OP_RUN。

当整个图像都被像素填满时编解码工作就结束了,在数据流末尾还有7个0x00字节和1个0x01字节作为结束标志。而PNG貌似要读到IEND数据块才算结束。由于QOI有大部分区块是参考之前的像素来计算出当前像素的值,所以定义了编解码器以[r:0, g:0, b:0, a:255]作为第一个像素的参考值。同时QOI编解码器还维护着能存储64个像素的数组,初始值为0,每次处理完一个像素就复制一个放到这个数组里。存放的位置由以下公式计算:

Plain Text
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64

接下来是区块的定义:

QOI_OP_RGB
Byte[0]
7 6 5 4 3 2 1 0
Byte[1]
7 .. 0
Byte[2]
7 .. 0
Byte[3]
7 .. 0
1 1 1 1 1 1 1 0红色值r绿色值g蓝色值b

如果图像有Alpha通道,那么解码这个区块的时候,当前像素的Alpha值保持为前一个像素的Alpha值。

QOI_OP_RGBA
Byte[0]
7 6 5 4 3 2 1 0
Byte[1]
7 .. 0
Byte[2]
7 .. 0
Byte[3]
7 .. 0
Byte[4]
7 .. 0
1 1 1 1 1 1 1 1红色值r绿色值g蓝色值bAlpha值a
QOI_OP_INDEX
Byte[0]
7 6

5 4 3 2 1 0
0 0索引index

索引值的范围是0-63

QOI_OP_DIFF
Byte[0]
7 6

5 4

3 2

1 0
0 1红色差dr绿色差dg蓝色差db

色差存储的是当前像素与前一个像素的差值。举个例子:当前像素的红色值是114,前一个的是113,那么红色差就是114-113=1。
色差值的范围是-2-1,存储时要加上偏移量2,然后再存储为无符号整数。例子:-2存储为0(0b00),1存储为3(0b11)。
超出0 --- 255范围的值会环绕到此范围内,可能大家不知道是怎么个“环绕”法,具体做法是给小于0的值加上256,大于255的就减去256(这种做法并不是最高效的,只是为了演示原理)。举个例子:

Plain Text
1 - 2 = -1 --> -1 + 256 = 255 ( -1 --> 255) 1 - 3 = -2 --> -2 + 256 = 254 ( -2 --> 254) 255 + 1 = 256 --> 256 - 256 = 0 (256 --> 0) 255 + 2 = 257 --> 257 - 256 = 1 (257 --> 1)

Alpha值保持和前一个像素的不变。

QOI_OP_LUMA
Byte[0]
7 6

5 4 3 2 1 0
Byte[1]
7 6 5 4

3 2 1 0
1 0绿色差dg红色差与绿色差的差值dr_dg蓝色差与绿色差的差值db_dg

与QOI_OP_DIFF类似,但是能存储的值的范围更大,也更复杂(bushi)
绿色差的范围是-32-31,存储时加上偏移量32。
红色差与绿色差的差值 & 蓝色差与绿色差的差值 的范围都是-8-7,偏移量都是8。这两个差值的计算方法如下:

Plain Text
/* cur_px 代表当前像素 */ /* prev_px 代表前一个像素 */ dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)

和QOI_OP_DIFF一样有环绕操作,同样保持Alpha值不变。

QOI_OP_RUN
Byte[0]
7 6

5 4 3 2 1 0
1 1行程长度run

行程编码。行程长度决定了要重复多少次前一个像素的内容,范围是1-62,存储时加上偏移量-1。
需要注意的是,使用63(0b111110)和64(0b111111)作为行程长度会和QOI_OP_RGB以及QOI_OP_RGBA的区块标识冲突。


为了达到更好压缩,编码时通常会按顺序选择编码成哪种区块。从上往下依次是:

  • (1-byte)QOI_OP_RUN
  • (1-byte)QOI_OP_INDEX
  • (1-byte)QOI_OP_DIFF
  • (2-byte)QOI_OP_LUMA
  • (4-byte)QOI_OP_RGB
  • (5-byte)QOI_OP_RGBA

当像素无法被编码成第一种区块时就会向下尝试直至最后一个。由于所有的区块中只有QOI_OP_RGBA能记录Alpha值的变化,而这种区块会把单个像素编码成5个字节,所以QOI处理Alpha渐变的区域不仅节省不了空间反而还会倒贴(

到这里规范的内容讲的几乎差不多了。因为快到高二开学了,匆匆忙忙赶出了这篇博客,可能会有疏忽的地方。~~同时我这个人语言水平不太好,有些地方讲的可能甚至不如琪露诺。~~等有时间了看看能不能把QOI解码器教程给肝出来吧(挖坑ing)

hello, world

网站部署好之后的第一件事:写个hello world
不过我确实想不到要写什么了,那就走个流程罢(bushi