- Published on
退化JPEG:将视频塞进一张图片
- Authors
- Name
- vitaut
- maurycyz.com

JPEG渐进模式的工作原理
JPEG文件支持渐进式编码,其核心是将图像数据分割成多个“扫描”(scan),每个扫描携带不同频段的信息。网络传输时,客户端先解码低频扫描获得低分辨率预览,然后继续下载高频扫描逐步完善细节。这种设计原本是为了改善用户体验,让大图在完全下载前即可大致可见。
扫描的结构
每个扫描以 FF DA 标记开头,后面跟着长度、包含的颜色通道、使用的霍夫曼表索引以及DCT频段范围。例如,第一个扫描可以只包含直流分量(DC,即0频段的系数),这样图像显示为16×16像素块的色块,分辨率极低。后续扫描逐步添加交流分量(AC,1-63频段),直至完整图像。
颜色通道采用YCbCr空间,其中Y(亮度)最重要,Cb/Cr(色度)精度可降低。作者给出的一个典型渐进JPEG包含10个扫描:先有DC扫描提供半精度预览,然后亮度用1-5频段细化,接着色度全频段以半精度填充,最后全精度覆盖,以此实现质量平滑提升。
将多个图像嵌入同一个JPEG
作者发现了渐进模式的一个漏洞:既然每个扫描可以独立指定DCT频段范围,理论上可以构造文件,让后续扫描覆盖已渲染的数据,从而显示完全不同的图像。实现方法很简单:将多个尺寸相同的JPEG文件拼接,去掉SOI、SOF和EOI标记,保留每个文件中的扫描段。通过网络加载时,解码器会依次处理这些扫描,导致画面在每个“帧”切换。
但这种做法很快遇到瓶颈:多数解码器对扫描数量有限制(比如Chrome约90个),用于真正的动画远远不够。为了解决这个问题,需要最小化每帧所使用的扫描数量。
仅含DC扫描的帧
最简单的方式是让每个“帧”只包含单个DC扫描(即只有直流分量)。在渐进模式下,扫描不能同时包含DC和AC数据,但可以单独使用DC扫描。这样每帧的DCT块大小为16×16,得到1/16分辨率的缩略图。由于去除了AC扫描,避免了“鬼影”现象(AC扫描本应细化前一帧的数据,叠加后会产生混叠)。
通过命令行工具 jpegtran 可以轻松生成此类文件:
jpegtran -scans frame.scans -outfile out.jpg in.jpg
其中 frame.scans 指定DC-only扫描。作者用这种方法将90个不同画面放入一个JPEG,在Chrome中可正常播放,Firefox支持更多。
实际演示与趣味应用
作者给出了一个纯粹基于HTML <dialog> 标签的视频演示网站(badapple.rose.systems),展示“bad apple”字符画动画全部嵌入单张JPEG。另外还提供了一个无需CSS和JavaScript的交互式单页应用,完全依赖JPEG的渐进渲染。
文章最后总结:这毫无实际用途——无法添加时间信息,播放速度完全取决于网络延迟。但它是一次对JPEG格式边界的精彩探索,适合用于恶作剧(例如rickroll)或技术炫技。相关代码(merge.c)已公开,可供感兴趣的人研究。
原标题:Regressive JPEGs。 HN 原始发布时间:2026年7月18日星期六。当前记录为 655 分、65 条评论。