Handsum: 一种LQIP图像文件格式
摘要
Nigel Tao介绍了Handsum,一种基于DCT的新型固定大小LQIP图像文件格式,与JPEG相比,提供可预测的文件大小和简单性。
<p><a href="https://lobste.rs/s/gmzzeb/handsum_lqip_image_file_format">评论</a></p>
查看缓存全文
缓存时间: 2026/07/11 19:26
# Handsum:一种 LQIP 图像文件格式
来源:https://nigeltao.github.io/blog/2026/handsum.html
## Nigel Tao (https://nigeltao.github.io/)
低质量图像占位符 (LQIP) 是指非常小(无论是在文件大小还是像素尺寸上)的图像,它们加载极快,能在全分辨率图像缓慢后台加载时立即提供视觉反馈。它们的体积足够小,以至于可以将其 base64 编码后内联到网页中,从而减少网络往返次数并提升页面加载时间。
现有的 LQIP 技术包括 theBlurhash (https://blurha.sh/) 和 Thumbhash (https://evanw.github.io/thumbhash/) 自定义图像文件格式,以及直接使用低质量 JPEG 或 WebP 和极小的像素尺寸。这篇博客文章介绍了一种名为 Handsum 的新 LQIP 自定义图像文件格式。
Handsum 文件的大小也是固定的:最低质量设置为 48 字节,最高质量为 147 字节。对于桌面、原生或命令行应用,如果你将缩略图存储在数据库中,这允许你使用固定大小的列,这可以更快速、更简单地迭代 (https://www.joelonsoftware.com/2001/12/11/back-to-basics/)。
Handsum 基于离散余弦变换 (DCT),该变换也用于 Blurhash 算法 (https://github.com/woltapp/blurhash/blob/master/Algorithm.md)、Thumbhash 算法 (https://evanw.github.io/thumbhash/#details) 以及 JPEG 本身。理解相对简单的 Handsum 格式的工作原理,将有助于你理解相对复杂的 JPEG 格式的工作原理(JPEG 规范 (https://www.w3.org/Graphics/JPEG/itu-t81.pdf) 是一份 186 页的 PDF 文件;Handsum 更简单)。
## 网页示例
以下是 Handsum 网页演示 (https://nigeltao.github.io/blog/2026/wasm-handsum-example/) 的带注释截图。它使用了一个用 C 语言编写 (https://github.com/google/wuffs/blob/09b8cbb82f41638408ff66d024120a36b41c779e/snippet/basic-handsum-decode.c) 并编译为 Wasm 的 Handsum 解码器。
Handsum Wasm
## 对比
以下是将 Handsum 与 PNG、Thumbhash(与 Blurhash 非常相似)、WebP、ETC2 和 JPEG 进行的对比。你可能希望在新标签页中打开此图像,进行缩放和平移。
Handsum 对比
这里有很多内容。我们来逐一分解。
每一行涉及一张著名图片,如维基百科所示:地出 (https://commons.wikimedia.org/wiki/File:NASA-Apollo8-Dec24-Earthrise.jpg)、大碗岛 (https://commons.wikimedia.org/wiki/File:A_Sunday_on_La_Grande_Jatte,_Georges_Seurat,_1884.jpg)、林肯 (https://commons.wikimedia.org/wiki/File:Abraham_Lincoln_1863_Portrait_%283x4_cropped%29.jpg)、蒙娜丽莎 (https://commons.wikimedia.org/wiki/File:Mona_Lisa,_by_Leonardo_da_Vinci,_from_C2RMF_retouched.jpg)、议会大厦 (https://commons.wikimedia.org/wiki/File:Claude_Monet_-_The_Houses_of_Parliament,_Sunset.jpg)、珍珠耳环 (https://commons.wikimedia.org/wiki/File:1665_Girl_with_a_Pearl_Earring.jpg)、星月夜 (https://commons.wikimedia.org/wiki/File:Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg)、神奈川冲浪里 (https://commons.wikimedia.org/wiki/File:Tsunami_by_hokusai_19th_century.jpg)、范艾克 (https://commons.wikimedia.org/wiki/File:Portrait_of_a_Man_by_Jan_van_Eyck.jpg)、睡莲 (https://commons.wikimedia.org/wiki/File:Water-Lilies-and-Japanese-Bridge-%281897-1899%29-Monet.jpg)。
第一列“原始”是图像本身,缩放至适合 32×32 像素边界框(同时保持宽高比)。例如,500×745 像素的《蒙娜丽莎》图像被调整为 21×32。
之后每一列都是经过有损编码再解码后的原始图像。每个单元格上方的数字(如 1368 或 615)是编码后的大小(字节数)。更多的字节数与更高的质量明显相关。问题在于,在保持可接受质量的前提下,你能将大小(以字节为单位)降到多低。
第二列“PNG 16”只是将图像缩小到适合 16×16 像素边界框,然后无损编码为 PNG。(是的,如果我们使用 `pngcrush` 或使用 WebP 无损格式,文件大小可能会略小一些,但差别可能不大)。解码生成一个 16×16 以内的图像(例如 11×16 的《蒙娜丽莎》),然后我们将其上采样(使用双线性滤波器)回 32 像素宽或高。
第三列使用 Thumbhash (https://evanw.github.io/thumbhash/) LQIP 编解码器。它生成极小的编码,仅占 24-27 字节。
接下来四列使用 Handsum,分别对应其四个质量设置。“Hsum 16 q=1”中的“16”意味着,与“PNG 16”类似,该编解码器本身生成的东西适合 16×16 边界框,因此我们上采样到 32 以获得更适合与其他单元格比较的图像。
再接下来的四列使用基于 VP8 的 WebP 有损格式,质量分别为 0、25 和 75。“WebP 16”列在 16×16 内编码(并解码)(同样,我们进行上采样)。“WebP 32”列在 32×32 图像内编码(解码时无需上采样)。
接下来的两列使用 ETC2(包装在 PKM / PACKMAN 容器中),这是 Ericsson 纹理压缩格式,在 OpenGL ES 3.0 (https://registry.khronos.org/OpenGL/specs/es/3.0/es_spec_3.0.pdf)(所引用规范的附录 C.1)中是必需的,类似于其他纹理格式如 BCn / DXTn / S3TC。ETC2 设计用于在 GPU 上解码(具有高度并行性),但您也可以在 CPU 上解码,就像任何其他图像格式一样。ETC2 是一种有趣且未被充分重视的图像格式,但深入探讨它超出了本博客文章的范围。同样,“16”表示对较低分辨率源图像进行编码并在解码后上采样,“32”表示对较高分辨率源图像进行编码。
最后两列是 JPEG,质量为 75(大致相当于 WebP 的“quality = 75”,但并不完全相同)。同样,“16”与“32”表示较低分辨率加采样与较高分辨率。
在 Handsum 的 q=1(最低质量)下,它大致相当于更大(更高文件大小)但更优(更好视觉质量)的 Thumbhash。更大是相对而言的。绝对差异仅为 24 字节。所谓“更优视觉质量”是指,如果你让我根据缩略图猜测著名图片,那么对于《蒙娜丽莎》或《戴珍珠耳环的少女》,Handsum(q=1)的结果是合理的,但 Thumbhash 则不合理。不过,仍然比 WebP 16 q=0 产生的现代土豆艺术要好。
在 Handsum 的 q=4(最高质量)下,对于给定的字节大小预算,它大致相当于 WebP 有损 (VP8) 和 ETC2。然而,一个区别是,在给定的质量设置下(且 c=3,见下文),每个 Handsum 图像文件的大小相同:q=1 文件始终为 48 字节,q=2 为 75 字节,q=3 为 123 字节,q=4 为 147 字节。
如果你想要制作一个展示艺术品目录(油画、音频记录等)的嵌入式应用,例如,你可以为每个目录条目预算 256 字节。在分别分配给艺术家姓名和艺术作品名称 50 字节(必要时省略)以及几个字节给其他元数据(时间戳、曲目长度等)后,你可以为缩略图留下 150 字节。如果你使用 WebP 编码 16×16 缩略图,q=75 大多数情况下会小于或等于 150 字节,但并非总是如此。WebP 编码器通常也有质量设置,但没有字节预算设置,因此生成足够小(以字节为单位)的 WebP 需要一些调整。相比之下,使用 Handsum q=4,你每次都能保证得到 147 字节。
## Handsum 的工作原理
Handsum 有三种颜色设置(c=1 表示灰度,c=3 表示 RGB 或等效的 YCbCr,c=4 添加了 Alpha 通道)。这篇博客仅讨论 c=3。参考实现的代码注释中有更多关于 c=1 和 c=4 的细节。
这篇博客也主要关注 q=4(最高)质量设置,因为它具有最简单的 DCT 实现。
对于任何源图像(例如 500×745 像素的《蒙娜丽莎》),编码算法将其缩减为 144 字节的像素数据(在 q=4 下)。加上一个 3 字节的头部后变为 147 字节:15 位魔数签名、2 位颜色、2 位质量和 5 位宽高比。
Handsum 编码可以分解为一系列阶段。解码只是按相反顺序反转这些阶段,再加上一个后处理“环路滤波器”阶段(见下文)。当解码到达第 0 阶段(调整大小)时,它并不是完全的反转。解码图像的最长边(宽或高)为 16 像素,而 5 位的宽高比(在头部中)给出较短边。Handsum 不是一种通用图像格式,仅是一种缩略图格式。
Handsum 阶段
### 第 0 阶段:调整大小
第 0 阶段
调整为 16×16 大小,每像素 3 字节 RGB(红、绿、蓝),我们从 768 字节的数据开始。编码的后续阶段都在这个 16×16 工作空间上进行,无论原始源图像的宽高比如何。
### 第 1 阶段:YCbCr 转换
第 1 阶段
从 RGB 转换到 YCbCr 使用与 JPEG / BT.601 相同的公式:
```
Y = (+0.2990 * R) + (+0.5870 * G) + (+0.1140 * B)
Cb = (-0.1687 * R) + (-0.3313 * G) + (+0.5000 * B)
Cr = (+0.5000 * R) + (-0.4187 * G) + (-0.0813 * B)
```
这个从每像素 3 字节 RGB 到每像素 3 字节 YCbCr 的变换在无限精度实数下是无损(且完全可逆)的,在有限精度计算机数字中只有轻微损失。
(理论上)无损变换本身并不节省字节,因为我们仍然是 3×16×16 = 768。线性代数爱好者会认识到这是一个(无损)基变换。但这为后续阶段更好地压缩 16×16 图像奠定了基础。
压缩意味着将 768 这个数字降下来。但是,对于固定字节大小的输出(768 / 144 是 5.333 倍的压缩比),降低这个数字必然需要丢弃(或者说“丢失”,在有损压缩意义上)信息。切换到 YCbCr 给了我们更好的机会来丢弃人们大多不会注意到的信息。
### 第 2 阶段:色度下采样
第 2 阶段
人类视觉对亮度更敏感,对色度较不敏感,因此我们可以更积极地丢弃色度通道信息(以获得更好的压缩比)。这个阶段简单地丢弃了两个色度通道数据的 75%(占整体 768 的 50%),将 Cb 和 Cr 从 16×16 下采样到 8×8。现在我们处于 (16×16 + 8×8 + 8×8) = 384 总字节,节省了 50%。
这是经典的 4:2:0 色度子采样 (https://en.wikipedia.org/wiki/Chroma_subsampling),与 WebP 图像相同,并且在 JPEG 图像中也非常流行。对于那些已经了解 JPEG 工作原理的人,Handsum 的 16×16 像素工作空间本质上就是一个 JPEG 4:2:0 MCU(最小编码单元)。
### 第 3 阶段:DCT
第 3 阶段
我们将 16×16 的亮度和 8×8 的色度网格分割成 2×2 的块,每个块 4 个样本,并独立地对每个块应用离散余弦变换。这会将 4 个样本转换为 4 个样本,每个块如此。与第 1 阶段一样,这只是一个基变换。与第 1 阶段一样,这本身并不会降低字节数(并且在无限精度实数下是无损变换),但它为后续机会打开了大门。
Handsum q=4 使用 2×2 块(因此每个块有 4 个系数)。较低的 Handsum 质量设置使用 4×4 或 8×8 块(而 JPEG 的 DCT 也使用 8×8)。DCT 适用于这些块大小中的任何一种,但 DCT 公式对于 2×2 是最简单的,我们将在下面更详细地介绍。
### 第 4 阶段:量化
第 4 阶段
我们开始时每个 RGB 样本使用 8 位(半开区间 `0 .. 256`)。从 8 位量化到 4 位(半开区间 `0 .. 16`)在概念上很简单,只需在编码端进行 `value >>= 4`,在解码端进行 `value *= 0x11`。
实际上,Handsum 的量化稍微复杂一些:
- DC 和 AC 分量(见下文)的逆量化略有不同。对于 `0 .. 256` 范围内的样本,我们将 DC 乘以 0x11,以便能够达到纯黑(0x00)和纯白(0xFF)。对于 AC,我们只乘以 0x10,因为我们希望能够精确再现中间点(0x80 = 128)。如果所有 DCT 前的样本都相等,那么所有 AC 分量都将处于中间位置,如果我们在解码时无法再现平坦样本,就会出现明显的伪影。有意识的取舍是我们无法再现极高的 AC 值。
- 类似但略有不同,我们正向偏置色度(而非亮度)值进行编码,并在解码时反向偏置,以便在编码灰度图像时,中性色度样本在偏置后具有一个 DC 值(0x88 而非 0x80),该值可以通过量化精确往返。
- 色度值不仅被偏置,还被缩放。根据我之前的博客文章《可视化 Y′CbCr 色度空间》(https://nigeltao.github.io/blog/2026/visualizing-ycbcr.html),实践中大多数色度值在 `64 .. 192` 范围内,而不是 `0 .. 256`。缩放意味着我们的 4 位值可以捕获更多细节,但会牺牲区分极值的能力。
- 上面的项目符号忽略了这样一个事实:在 2×2 DCT 之后,系数是 9 位值(范围在 `0 .. 512` 或 `-256 .. +256`)而不是 8 位值(范围在 `0 .. 256`)。请参见下面的公式。
- Handsum 的参考编码器(它使用 *一种* 编码算法,而不是 *唯一的* 编码算法)在编码时实际上使用了比解码器更小的 AC 分量“桶宽度”,以在一定程度上补偿环路滤波器回归均值(见下文)。
JPEG 也会量化其 DCT 后的系数,但 JPEG 的量化因子是可配置的。当你向 libjpeg 传递 `-quality 25` 或 `-quality 90` 设置时,实际上是在调整量化因子(这些因子被写入 JPEG 文件)。更大的量化因子会丢弃更多信息,但也会导致量化后的数字更小,以及更长的零值序列,从而更好地压缩。
Handsum 的质量设置不同。Handsum 的量化因子是硬编码的,并且其输出设计为固定字节大小,因此它不使用 JPEG 的一些熵编码技术,如游程编码或霍夫曼编码。无论如何,这里丢弃一半的位(最低有效的一半)可再节省 50%,从 384 字节减少到 192 字节。
### 第 5 阶段:高频消除
第 5 阶段
最后阶段获取每个 2×2 块中的 4 个 DCT 后系数,并简单地丢弃其中一个,再节省 25%,从 192 字节减少到 144 字节。
对于 Handsum q=3,我们会丢弃 16 个 4×4 系数中的 6 个(38%)。对于 q=2,丢弃 16 个中的 10 个(63%)。对于 q=1(最低质量),丢弃 64 个 8×8 系数中的 49 个(77%)。在所有情况下,Handsum 保留低频分量,丢弃高频分量。熟悉 JPEG “之字形系数排序”的人可能觉得这种优先级排序很熟悉。
之前进行 DCT 的目的是,对于 q=4,第四个系数控制一个高频“棋盘格”模式(每个 2×2 块内),这通常不太明显。如果你错过了(比较第 4 阶段和第 5 阶段图像),这里是发现差异的地方。
第 4 阶段 vs 第 5 阶段
相比之下,如果我们不进行 DCT 阶段(将空间域 `(x, y)` 变换到频率域 `(u, v)`),而是直接丢弃每四个 DCT 前(而非 DCT 后)样本中的一个,视觉影响将非常明显(且糟糕)。
糟糕的阶段
## 离散余弦变换
DCT 是 Handsum 编解码器中最棘手的概念。它是一种基变换,意味着将某些值(在我们的例子中是 4 个值)从一种表示转换为另一种表示,
相似文章
@llama_index: LlamaParse 现在原生解析 HEIC 文件。HEIC 是 Apple 的默认图像格式,因此在企业文件系统中随处可见……
LlamaParse 现在原生支持 HEIC 文件(Apple 的默认图像格式),无需手动转换为 JPEG 即可解析。
Bun.Image
Bun.Image 是一个零依赖的可链式图像处理管道,用于解码、调整大小、旋转和重新编码 JPEG、PNG、WebP、HEIC 和 AVIF 格式,在后台线程运行,灵感来自 Sharp。
Unsloth Gemma 4 QAT MTP 辅助模型现已可用
Unsloth 发布了 Gemma 4 QAT MTP 辅助模型,以 GGUF 文件形式在 Hugging Face 上提供,支持 q8_0 及更大量化格式。
# LiftQuant:基于维度提升与投影的连续比特宽度大语言模型量化
# LiftQuant 引入"先提升后投影"机制,实现大语言模型的连续(非整数)位宽量化,精准适配硬件内存预算。该框架将 70B 大语言模型压缩至 2.4 位以适配 24GB GPU,性能超越当前最先进的 2 位模型。
Hy4的官方1位量化版本??? 👀
本文介绍了Hy4预览模型的官方1位量化构建版本,提供尺寸减小的GGUF文件,以及在修改后的llama.cpp上运行的说明。