链接:https://phoboslab.org/log/2021/11/qoi-fast-lossless-image-compression
(看代码设计的时候,真的大为震撼,伟大的algorithm T.T)
docs:QOI图像格式
qoi
项目提出了Quite OK Image(QOI)格式,这是一种快速且无损的图像压缩方案。
它为开发者提供了核心库API接口,能够便捷地将原始像素数据编码为QOI格式,或将QOI文件解码还原为图像。
项目还包含qoiconv
命令行工具用于图像格式转换,以及qoibench
工具用于性能评估(对比PNG等格式)。
架构
章节导航
- 图像描述(
qoi_desc
)
- QOI文件格式
- QOI库API接口
- 命令行转换器(
qoiconv
)
- 基准测试工具(
qoibench
)
- 像素哈希与索引
- 行程长度编码(QOI_OP_RUN)
- 差值编码(QOI_OP_DIFF, QOI_OP_LUMA)
第一章:图像描述结构体(qoi_desc
)
欢迎来到QOI(“Quite OK Image”)图像格式的世界!
在这个开篇章节中,我们将深入探讨QOI库理解与处理图像的基础概念——qoi_desc
结构体。
图像描述的定义
假设我们持有一张数字照片。在打印、屏幕显示或发送给他人之前,计算机需要掌握其基本属性:
- 图片宽度是多少?
- 图片高度是多少?
- 使用标准RGB色彩模式还是包含透明通道的RGBA模式?
- 色彩空间如何解析(例如明亮或暗淡)?
这正是
qoi_desc
结构体所提供的"身份证"功能。
这个精炼的结构体包含了QOI库处理图像所需的所有基础维度
与色彩属性
信息,而无需接触实际像素数据。
qoi_desc
的核心构成
该结构体包含四个关键要素:
组件 | 描述 |
---|---|
width | 图像宽度,以像素为单位 |
height | 图像高度,以像素为单位 |
channels | 每像素的色彩分量数。通常为3 (RGB三通道)或4 (RGBA四通道含透明) |
colorspace | 色彩解析方式。常规图像使用QOI_SRGB (标准sRGB),线性未校正数据使用QOI_LINEAR 。初学者只需理解这是色彩的"风味"定义 |
在qoi.h
头文件中可见其具体定义:
// 摘自:qoi.htypedef struct {unsigned int width; // 图像宽度(像素单位)unsigned int height; // 图像高度(像素单位)unsigned char channels; // 3代表RGB,4代表RGBAunsigned char colorspace; // 0代表sRGB,1代表线性色彩空间
} qoi_desc;// 色彩空间常量定义
#define QOI_SRGB 0
#define QOI_LINEAR 1
这就是我们图像身份证的蓝图。
读取图像时的qoi_desc
应用
图像处理中最常见的操作之一是从文件载入图像。
当通过QOI库加载QOI图像时,首先需要从文件头读取qoi_desc
信息,该操作由qoi_read
函数完成。
以qoiconv.c
格式转换工具中的简化示例说明:
// 摘自:qoiconv.c// 1. 声明qoi_desc类型变量(当前为空身份证)
qoi_desc desc;// 2. 调用qoi_read加载图像
// - argv[1]是输入文件名(如"input.qoi")
// - &desc指向空结构体,qoi_read将填充该结构体
// - 0表示"使用文件中指定的通道数"
void *pixels = qoi_read(argv[1], &desc, 0);// 若pixels非空(读取成功),desc将包含图像的完整元数据
// 例如可获取:
// int image_width = desc.width;
// int image_height = desc.height;
// int image_channels = desc.channels;
此时desc
变量已载入完整图像参数,pixels
变量则存储实际像素数据。
写入图像时的qoi_desc
应用
保存QOI文件时,需通过qoi_desc
告知库文件图像参数。参考qoiconv.c
中的保存示例:
// 摘自:qoiconv.c// 假设w、h、channels已存储图像参数
// pixels包含实际像素数据// 调用qoi_write保存图像
// - argv[2]是输出文件名(如"output.qoi")
// - 第三个参数即时构建qoi_desc结构体
int encoded = qoi_write(argv[2], pixels, &(qoi_desc)
{.width = w,.height = h,.channels = channels,.colorspace = QOI_SRGB // 明确指定sRGB色彩空间
});
此处我们主动构建图像身份证并传递给编码函数。
底层实现机制
QOI文件以14字节的头部起始
,精确存储qoi_desc
信息。
QOI编码*qoi_encode
流程:
解码*qoi_decode
过程示意图:
QOI库通过qoi_write_32
和qoi_read_32
辅助函数处理32位整数的读写,channels
和colorspace
则作为8位值处理。编码函数核心实现:
// 摘自:qoi.h(QOI_IMPLEMENTATION段)void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {unsigned char *bytes; // QOI文件数据缓冲区int p = 0; // 缓冲区当前位置指针// 写入魔数标识qoi_write_32(bytes, &p, QOI_MAGIC);// 写入描述信息qoi_write_32(bytes, &p, desc->width);qoi_write_32(bytes, &p, desc->height);bytes[p++] = desc->channels;bytes[p++] = desc->colorspace;// ...(后续编码过程)return bytes;
}
对应解码实现:
// 摘自:qoi.h(QOI_IMPLEMENTATION段)void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {const unsigned char *bytes = (const unsigned char *)data;int p = 0;// 读取并验证魔数unsigned int header_magic = qoi_read_32(bytes, &p);// 填充描述变量desc->width = qoi_read_32(bytes, &p);desc->height = qoi_read_32(bytes, &p);desc->channels = bytes[p++];desc->colorspace = bytes[p++];// ...(后续解码过程)return pixels;
}
总结
本章阐明了qoi_desc
作为QOI图像元数据核心容器的关键作用,其包含的宽度、高度、通道数(RGB/RGBA)和色彩空间参数,既是图像编码的起点,也是解码过程的基石。
下一章将深入解析这些元数据在QOI文件格式中的具体组织方式。
第二章:QOI 文件格式
在第一章:图像描述(qoi_desc
)中,我们学习了qoi_desc
结构体,它就像一张"身份证",保存着图像的宽度、高度和色彩类型等关键信息。
但仅仅了解图像的元数据是不够的;我们需要一种将图像永久存储在计算机磁盘上的方法,以便保存、共享或后续加载。
从像素到文件
假设我们有一张精美的数码照片,当前仅以原始像素数据的形式存在于计算机内存中。为了永久保存,需要将其写入文件。
但如何将这些信息——图像的尺寸、颜色及压缩方式——组织成单个文件,且保证任何QOI兼容程序都能正确解析?
这正是QOI文件格式要解决的问题。
它就像一本详尽的食谱,精确说明图像数据在.qoi
文件内的结构组织和压缩方式。遵循这个规范,任何支持QOI的程序都能正确读写图像,确保所有人看到的画面完全一致。
.qoi
文件的内部结构
.qoi
文件采用极简结构设计,便于计算机快速处理。我们可以将.qoi
文件视为专为图像设计的容器,包含三个主要部分:
- 文件头:位于文件起始位置,作为整张图像的"身份证"
- 像素数据:图像的实际颜色信息,通过智能分块实现高效存储
- 结束标记:文件末尾的特殊标识,宣告数据终止
让我们深入解析每个部分。
第一部分:文件头——图像的增强版"身份证"
QOI文件始终以文件头开始,固定长度为14字节。这个头部至关重要,因为它包含第一章讨论的所有图像元数据。
除了qoi_desc
中的width
(宽度)、height
(高度)、channels
(通道数)和colorspace
(色彩空间)外,文件头还包含4字节的"魔法标识":字符序列"qoif"
。这个魔法字符串告知打开文件的程序:“请注意,这是QOI图像文件!”
QOI库编码图像时,首先写入这个头部。以下是qoi_encode
(qoi_write
的核心编码函数)的起始部分:
// 来源:qoi.h(位于 QOI_IMPLEMENTATION 中)
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {unsigned char *bytes; // 存储文件原始字节int p = 0; // 'p' 跟踪当前写入位置// 1. 写入魔法标识"qoif"qoi_write_32(bytes, &p, QOI_MAGIC); // QOI_MAGIC 是数值形式的'qoif'// 2. 写入图像宽度qoi_write_32(bytes, &p, desc->width);// 3. 写入图像高度qoi_write_32(bytes, &p, desc->height);// 4. 写入通道数(3表示RGB,4表示RGBA)bytes[p++] = desc->channels;// 5. 写入色彩空间(0表示sRGB,1表示线性)bytes[p++] = desc->colorspace;// ... 函数后续部分将写入像素数据块return bytes;
}
这段代码显示,文件最开始的字节依次是魔法标识、图像宽度、高度、通道数和色彩空间。
这种设计确保读取文件时,程序无需解码像素数据即可立即获取图像基本信息。
第二部分:像素数据——智能打包的"数据块"
文件头之后是图像颜色数据的主体部分。QOI在此展现其精妙的压缩技术:不采用逐个像素记录RGBA值的低效方式,而是使用多种"数据块"。
这些数据块如同不同类型的指令集。例如:
- “重复前一个像素10次”(运行块/
QOI_OP_RUN
) - “此像素与前一个略有不同”(差值块/
QOI_OP_DIFF
) - “此像素与之前某位置完全相同”(索引块/
QOI_OP_INDEX
) - “直接记录完整像素值”(
RGB/RGBA块
,当其他块不适用时使用)
QOI编码器智能]选择最紧凑的数据块类型,这种智能打包使QOI文件体积小于未压缩图像。我们将在后续章节(如第七章:行程编码和第八章:差值编码)深入解析各数据块类型。
像素始终按行编码,从左到右,从图像左上角开始
。
第三部分:结束标记——终止信号
每个QOI文件以固定的结束标记收尾:7个0x00
字节(全零)后接1个0x01
字节(一)。
结束标记如同电影的"剧终"提示,对解码器至关重要。
它明确标识图像数据的终点,防止解码器越界读取
可能导致错误或误将随机内容解析为像素数据。
以下是qoi_encode
添加结束标记的实现:
// 来源:qoi.h(位于 QOI_IMPLEMENTATION,编码函数末尾)
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {// ...(文件头和像素块的编码代码)// 写入8字节结束标记for (int i = 0; i < (int)sizeof(qoi_padding); i++) {// qoi_padding 是预定义数组 {0,0,0,0,0,0,0,1}bytes[p++] = qoi_padding[i];}*out_len = p; // 更新文件总大小return bytes;
}
此循环简单地将预定义的8字节序列写入文件,标记数据终止。
完整的QOI文件结构
下表总结.qoi
文件的完整结构,展示各部分的顺序和大致大小:
组成部分 | 大小(字节) | 描述 |
---|---|---|
魔法标识 ("qoif" ) | 4 | 标识QOI图像文件 |
宽度 | 4 | 图像宽度(像素单位,如1920) |
高度 | 4 | 图像高度(像素单位,如1080) |
通道数 | 1 | 3 表示RGB图像,4 表示RGBA(含透明度) |
色彩空间 | 1 | 0 表示sRGB(标准色彩空间),1 表示线性色彩空间 |
像素数据块 | 可变 | 压缩后的像素信息,由运行块、差值块、索引块等组成,构成文件主体 |
结束标记 | 8 | 固定序列(7个0x00 后接0x01 ),标识文件结束 |
这种简洁可预测的结构是QOI文件编解码速度优异的关键。
库函数如何处理文件格式
通过QOI库API函数,我们可以直观理解编解码过程与文件格式的交互。
编码图像(写入.qoi
文件)
使用qoi_write
(内部调用qoi_encode
)保存图像时,库函数按QOI规范组织数据:
QOI库如同专业主厨,将原始像素"食材"按精确配方打包成标准文件。
解码图像(读取.qoi
文件)
使用qoi_read
(内部调用qoi_decode
)加载文件时,库函数按顺序解析各部分数据:
此时,QOI库如同细心的读者,逐步解析文件各部分:先验证"身份证",再按"配方步骤"重构图像,最终确认终止标记。
总结
本章建立了对QOI文件格式的基础认知:每个.qoi
文件包含固定大小的文件头(元数据标识)、可变长度的像素数据块(智能压缩),以及明确的**结束标记**。
这种简洁、高效且可预测的结构设计,正是QOI卓越性能的核心。
接下来,我们将通过QOI库API深入探讨如何以编程方式操作这些组件。