Reed's News
← 返回精选

操控JPEG渐进扫描实现嵌入式动画

Tech 72 vitaut 2026/7/18 723 字 原文 ↗

渐进式JPEG的“逆向玩法”

JPEG格式有个实用特性:支持优先保存低频分量。这意味着图片无需完全下载,就能先显示低分辨率版本,而非只呈现残缺的局部。

实现这一特性的原理,是将压缩数据拆分为多个“扫描段”,每个扫描段前都有头部标识。以下是一张示例图片的首个扫描段解析:

FF DA - “扫描段起始”标记 00 0C - 大端序长度字段(共12字节,含自身) 03 - 扫描覆盖的通道数(3个) 01 - 第一个通道的全局ID 00 - 霍夫曼(Huffman)表索引#1(直流分量DC:0,交流分量AC:0) 02 - 第二个通道的全局ID 10 - 霍夫曼表索引#2(直流分量DC:1,交流分量AC:0) 03 - 第三个通道的全局ID 10 - 霍夫曼表索引#2(直流分量DC:0,交流分量AC:0) 00 - DCT(离散余弦变换)起始频点(直流分量DC) 00 - DCT结束频点(同样为直流分量DC) 01 - 精度:半精度,无前置数据 f8ad 512d d3f1 cd96 bcb0 58df 53d5 5d97……(后续为大量霍夫曼编码的DCT系数)

这个扫描段包含了三个颜色通道的最低频(直流分量DC)傅里叶频点。简单来说,Y代表亮度(近似绿色G),Cb代表蓝色差(B-G),Cr代表红色差(R-G)。

首个扫描段之后,文件中还有8个扫描段,用于补充剩余数据:

扫描段编号 涉及通道 DCT频点范围 精度
0 Y、Cb、Cr 0 - 0 半精度(-1位)
1 Y 1 - 5 四分之一精度(-2位)
2 Cb 1 - 63 半精度
3 Cr 1 - 63 半精度
4 Y 6 - 63 四分之一精度
5 Y 1 - 63 半精度
6 Y、Cr、Cb 0 - 0 全精度
7 Cr 1 - 63 全精度
8 Cb 1 - 63 全精度
9 Y 1 - 63 全精度

扫描段#0提供了极低分辨率的图片预览; 扫描段#1为亮度通道补充细节; 扫描段#2至#5包含完整的低精度数据; 扫描段#4的频点范围比较特殊,它是为了填补扫描段#1留下的空白,让扫描段#5能基于完整的四分之一精度数据进行构建; 扫描段#6至#9则补充最后缺失的信息,将图片提升至全精度。

由于每个扫描段都会明确指定频点范围,理论上可以构造出一种特殊JPEG文件,让后续扫描段覆盖已渲染的图像数据。

实现起来其实非常简单: 将多张分辨率相同的图片拼接,同时过滤掉图像起始、帧起始和图像结束标记。可以用十六进制编辑器手动操作,我则写了一个简易的C程序来完成。

在低速网络环境下加载时,这个拼接后的文件会在多张图片之间切换:

加载过程中,图片会在“草丛中的猫”与“水泥地上的另一只猫”之间切换

不过,大多数解码器会在扫描段达到一定数量后停止工作: 我推测这是为了防范类似“zip炸弹”的攻击,但这也导致这种方法最多只能实现9帧切换,不足以构成完整动画。

要实现更多帧,就得尽量减少每帧的扫描段数量。最简单的思路是从仅含单个扫描段的基线JPEG入手,但这种方法行不通:

在渐进模式下,一个扫描段不能同时包含交流分量AC(频点大于0)和直流分量DC(频点为0)。基线模式没有这个限制,但基线解码器会在第一个扫描段结束后停止解析。

由于交流分量必须跟随直流分量,最小的“渐进式”JPEG只能是仅含直流分量的扫描段。又因为DCT以16×16块为单位处理,这类图像并非纯色,而是原始分辨率的1/16:

扫描段编号 涉及通道 DCT频点范围 精度
0 Y、Cb、Cr 0 - 0 全精度

用这种方法,我成功让Chrome浏览器渲染了约90帧后才停止。Firefox等浏览器的容忍度更高,但90个扫描段的文件几乎能在所有主流浏览器中正常工作。

额外的好处是,这种方法避免了直接拼接时出现的“重影”问题——重影是因为交流分量扫描段本应优化已有数据,通常这能让图片在不增加文件体积的前提下包含多精度层级,但与我的玩法不兼容。而如果文件只包含无递进关系的直流分量扫描段,就不会有这个问题。

由于“仅含直流分量”的帧符合JPEG标准,生成这类文件无需特殊工具:

cat >frame.scans <<EOF
# 仅含直流分量的扫描段:0,1,2:0-0,0,0; # 无其他内容
EOF
jpegtran -scans frame.scans -outfile out.jpg in.jpg

借助这种方式,甚至能把一整段视频塞进单个图片文件:

一段“视频”:一只黑猫朝镜头走来

除了制作非常规的“瑞克roll”或其他恶作剧,这种玩法没有实际应用价值——因为无法添加时间信息,播放节奏完全取决于网络延迟。

不过,利用局部渲染特性可以玩出不少花样: 比如这个纯HTML视频,只用了

标签:badapple.rose.systems

当然,数据不一定非要硬编码: 这个交互式单页应用就完全没有使用CSS或JavaScript。

相关链接