链接: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等格式)。

架构

在这里插入图片描述

章节导航

  1. 图像描述(qoi_desc)
  2. QOI文件格式
  3. QOI库API接口
  4. 命令行转换器(qoiconv)
  5. 基准测试工具(qoibench)
  6. 像素哈希与索引
  7. 行程长度编码(QOI_OP_RUN)
  8. 差值编码(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_32qoi_read_32辅助函数处理32位整数的读写,channelscolorspace则作为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文件视为专为图像设计的容器,包含三个主要部分:

  1. 文件头:位于文件起始位置,作为整张图像的"身份证"
  2. 像素数据:图像的实际颜色信息,通过智能分块实现高效存储
  3. 结束标记:文件末尾的特殊标识,宣告数据终止

让我们深入解析每个部分。

第一部分:文件头——图像的增强版"身份证"

QOI文件始终文件头开始,固定长度为14字节。这个头部至关重要,因为它包含第一章讨论的所有图像元数据

除了qoi_desc中的width(宽度)、height(高度)、channels(通道数)和colorspace(色彩空间)外,文件头还包含4字节的"魔法标识":字符序列"qoif"。这个魔法字符串告知打开文件的程序:“请注意,这是QOI图像文件!”

QOI库编码图像时,首先写入这个头部。以下是qoi_encodeqoi_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)
通道数13表示RGB图像,4表示RGBA(含透明度)
色彩空间10表示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深入探讨如何以编程方式操作这些组件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89420.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89420.shtml
英文地址,请注明出处:http://en.pswp.cn/web/89420.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧城轨可视化:一屏智管全城

图扑智慧城轨可视化系统&#xff0c;把地铁线路、车站、列车都搬进三维画面。列车晚点预警、站台拥挤提示、设备故障定位…… 这些关键信息一屏聚合&#xff0c;调度员能快速调整发车频次&#xff0c;疏导高峰客流。遇上突发情况&#xff0c;系统联动应急方案&#xff0c;同步显…

包新的Git安装与使用教程(2024九月更新)

目录 一、安装git 1.下载git 2.git安装 3.环境变量配置与测试 二、使用教程 1.创建版本库 2.版本回退 3.删除和恢复文件 一、安装git 1.下载git 官方下载地址&#xff1a;https://git-scm.com/download 然后进入以下页面&#xff0c;点击下载链接即可(windows一般都是…

中望3D 2026亮点速递(1)-全新槽功能螺纹功能,减少繁琐操作

本文为CAD芯智库整理&#xff0c;未经允许请勿复制、转载&#xff01;中望3D 2026全新的槽功能&#xff0c;包括&#xff1a;&#xff08;1&#xff09;可快速生成多种槽形&#xff1b;&#xff08;2&#xff09;快速生成一个或多个槽&#xff1b;&#xff08;3&#xff09;支持…

2025毫米波雷达技术白皮书:智能汽车与物联网的感知核心

随着人工智能、物联网&#xff08;IoT&#xff09;和智能汽车产业的迅猛发展&#xff0c;毫米波雷达技术正成为感知领域的核心驱动力。毫米波雷达凭借其高精度、全天候和强抗干扰能力&#xff0c;广泛应用于智能汽车的自动驾驶、物联网的环境感知以及工业自动化。2025年&#x…

用 React-Three-Fiber 实现雪花下落与堆积效果:从零开始的 3D 雪景模拟

在 Web3D 开发中&#xff0c;自然现象模拟一直是极具吸引力的主题。本文将基于 React-Three-Fiber&#xff08;R3F&#xff09;框架&#xff0c;详解如何实现一个包含雪花下落、地面堆积的完整雪景效果。我们会从基础粒子系统入手&#xff0c;逐步完善物理交互逻辑&#xff0c;…

从抓包GitHub Copilot认证请求,认识OAuth 2.0技术

引言 在现代开发工具中&#xff0c;GitHub Copilot 以智能、嵌入式的人工智能代码补全能力著称。作为一项涉及用户敏感数据和付费授权的服务&#xff0c;其认证授权流程尤为值得技术研究。本文基于实际抓包 VS Code 中的 Copilot 登录认证请求&#xff0c;系统梳理其 OAuth 2.…

Linux操作系统之线程:分页式存储管理

目录 前言&#xff1a; 一、分页式存储管理 二、二级页表的地址转化 三、缺页中断 总结 前言&#xff1a; 我们上篇文章简单介绍了线程的一些知识点&#xff0c;但是还有很多坑没有给大家填上&#xff0c;包括页表部分我们还没为大家说明。 本篇文章我将会继续为大家讲解…

xss1-8

Level-1<script>alert()</script>基础反射型 无任何过滤Level-2"> <script>alert()</script> <"闭合属性&#xff1a;">用来闭合当前标签的value属性注入新标签&#xff1a;闭合属性后&#xff0c;插入独立的<script>…

51c嵌入式~单片机~合集1

自己的原文哦~ https://blog.51cto.com/whaosoft/11897656 一、STM32的启动模式配置与应用 三种BOOT模式 所谓启动&#xff0c;一般来说就是指我们下好程序后&#xff0c;重启芯片时&#xff0c;SYSCLK的第4个上升沿&#xff0c;BOOT引脚的值将被锁存。用户可以通过设…

Typecho分类导航栏开发指南:从基础到高级实现

文章目录 Typecho分类导航栏深度解析:父分类与子分类的完美呈现 引言 一、Typecho分类系统基础 1.1 Typecho分类结构 1.2 获取分类数据的基本方法 二、基础分类导航输出 2.1 简单的平铺式导航 2.2 带计数器的分类导航 三、层级分类导航实现 3.1 递归输出父子分类 3.2 使用Type…

C++异步编程工具 async promise-future packaged_task等

深入探讨 C11 中引入的四个核心异步编程工具&#xff1a;std::async, std::future, std::promise, 和 std::packaged_task。它们共同构成了 C 现代并发编程的基础。 为了更好地理解&#xff0c;我们可以使用一个餐厅点餐的类比&#xff1a; std::future (取餐凭证)&#xff1…

Linux-网络管理

网络管理1. 网络基础1.1 TCP/IP 协议栈&#xff08;四层模型&#xff09;1.2 网络设备配置与基础概念1.3 网络接口命名规则1.4 网络配置文件位置2. 常用网络配置命令2.1 查看网络接口信息2.2 配置 IP 地址2.3 启用/禁用网卡2.4 修改网卡 MAC 地址2.5 配置网卡的 MTU&#xff08…

Linux锁的概念及线程同步

目录 1.常见锁概念 死锁 死锁四个必要条件 避免死锁 避免死锁算法 2. Linux线程同步 条件变量 同步概念与竞态条件 条件变量函数 初始化 销毁 等待条件满足 唤醒等待 简单案例&#xff1a; 条件变量使用规范 1.常见锁概念 死锁 死锁是指在一组进程中的各个进程均占有不会释放的…

docker更换国内加速器-更换华为加速器2025-717亲测可用docker 拉取镜像出错

[rootlocalhost ~]# docker pull nginx Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)报错原因就是…

Unity VR多人手术模拟恢复2:客户端移动同步问题分析与解决方案

Unity VR多人手术模拟恢复2&#xff1a;客户端移动同步问题分析与解决方案 &#x1f3af; 问题背景 在开发基于Unity Mirror网络架构的VR多人手术模拟系统时&#xff0c;我们遇到了一个复杂的客户端移动同步问题&#xff1a; 主要操作者&#xff08;第一个客户端&#xff09;&a…

uni-app开发的页面跳转全局加载中

uni-app开发的页面跳转全局加载中首先需要下载插件创建加载中页面组件app.vue页面中监听跳转首先需要下载插件 https://ext.dcloud.net.cn/plugin?id20613 创建加载中页面组件 <!-- 全局自定义加载中 --> <template><view v-if"visible" class&qu…

XXE漏洞4-XXE无回显文件读取-PentesterLab靶场搭建

一.PentesterLab靶场搭建(实验环境搭建)介绍&#xff1a;PentesterLab 是一个全面的漏洞演示平台&#xff0c;但是它是收费的&#xff0c;我们这里只使用它的 xxe 演示案例。安装 PentesterLab 虚拟机:下载好镜像&#xff1a; 1.打开VMware新建虚拟机&#xff0c;选择典型就行。…

【机器学习】图片分类中增强常用方式详解以及效果展示

图片增强常用方式详解 引言 图片数据的质量和多样性对模型的训练效果起着至关重要的作用。然而&#xff0c;实际获取的图片数据往往存在数量不足、分布不均衡等问题。图片增强技术应运而生&#xff0c;它通过对原始图片进行一系列变换&#xff0c;生成更多具有多样性的图片&…

【URL 转换为PDF】HTML转换为PDF

1、方法1 pdfkit 安装依赖 # 安装 wkhtmltopdf&#xff08;系统级&#xff09; # Ubuntu/Debian sudo apt install wkhtmltopdf# macOS brew install wkhtmltopdf# Windows 下载安装&#xff1a;https://wkhtmltopdf.org/downloads.html# 安装 Python 库 pip install pdfkitimp…

单链表的定义、插入和删除

一、定义一个单链表 struct LNode{ //定义单链表节点类型ElemType data; //存放节点数据元素struct LNode *next; //指针指向下一个结点 }; //增加一个新节点&#xff1a;在内存中申请一个结点所需空间&#xff0c;并用指针p指向这个结点 struct LNode * p (struc…