Regressive JPEGs: 通过渐进式 JPEG 扫描创建动画
Regressive JPEGs: 通过渐进式 JPEG 扫描创建动画
利用渐进式 JPEG 扫描实现动画
通过操纵渐进式 JPEG 的结构方式,可以创建一个在加载过程中在不同图像之间切换的单一图像文件。这是因为渐进式 JPEG 的设计初衷是先保存低频分量,从而允许浏览器渲染一个低分辨率预览,并随着更多数据的到达而不断细化。通过拼接多个图像并剥离特定的标记,开发者可以强制解码器用新的图像内容覆盖之前渲染的数据。
渐进式 JPEG 的工作原理
标准的渐进式 JPEG 将压缩数据分解为多个“扫描”(scans),每个扫描都带有前缀头。这些扫描允许图像在下载过程中以低分辨率显示,而不是从上到下被截断。
扫描的结构
一个典型的扫描头包含:
- Start of Scan (SOS) Marker:
FF DA - Length Field: 一个大端序值,指示扫描的大小。
- Channel Information: 通道数量(通常为 YCbCr)及其相关的 Huffman 表索引。
- Spectral Range: 起始和结束的离散余弦变换 (DCT) 频率分量。例如,仅包含 DC (0) 分量的扫描提供最低频的分量。
- Precision: 细节程度(例如,半精度或全精度)。
色彩空间与亮度
JPEG 使用 YCbCr 色彩空间而非 RGB。由于人眼对亮度的敏感度高于色彩,因此亮度 (Y) 被优先考虑以保证质量。色度 (Cb 和 Cr) 可以以较低的分辨率保存(半分辨率/四分之一像素计数),这意味着完整的色度数据占用的空间通常比亮度数据更少。
实现“回归式” JPEG
为了创建一个内容会发生变化的图像,作者拼接了多个分辨率相同的图像,并过滤掉了 Start-of-Image (SOI)、Start-of-Frame (SOF) 和 End-of-Image (EOI) 标记。
克服解码器限制
大多数浏览器解码器都会限制扫描的数量,以防止出现类似“压缩炸弹”式的资源耗尽。简单地拼接完整的渐进式 JPEG 通常在经过大约九帧后就会失败。
为了增加帧数(在 Chrome 中最高可达 90 帧),作者通过使用 DC-only scans 优化了帧。在渐进模式下,一个扫描不能同时包含 AC(分量大于 0)和 DC(分量为 0)数据。通过创建仅由单个 DC-only 扫描组成的帧,作者实现了两个目标:
- 增加帧数: 减少每帧的扫描数量,使得更多的帧可以放入解码器的限制范围内。
- 消除重影: 由于 AC 扫描旨在细化现有数据,仅使用 DC 扫描可以防止新帧与前一帧混合。
生成方法
可以使用 jpegtran 通过指定仅包含 Y、Cb 和 Cr 通道的 DC 分量的扫描配置来生成 DC-only 帧:
cat > frame.scans << EOF
# DC only scan: 0,1,2:0-0,0,0; # and nothing else
EOF
jpegtran -scans frame.scans -outfile out.jpg in.jpg
实际限制与浏览器行为
由于 JPEG 格式不包含时间信息,这些“动画”的播放速度完全取决于网络下载速度。
浏览器兼容性
- Chrome: 在停止之前大约渲染 90 帧。
- Firefox: 通常比 Chrome 对高数量的扫描更具耐心。
- Safari: 据报道在图像完全下载完成之前会处于冻结状态,从而抵消了效果。
针对时序的潜在变通方法
社区讨论建议了几种模拟受控播放的方法:
- Server-side Timing: Web 服务器可以向客户端发送定时分块的图像数据。
- Service Workers: 可以使用 Service Worker 来模拟慢速连接,以控制扫描的交付。
社区技术洞察
社区专家指出,虽然渐进式 JPEG 对感知加载时间有益,但它们可能会引入解码开销。一位用户提到,启用渐进模式可能会在配合使用 JPEG Turbo 和 OpenGL 等库时显著降低解码速度,这表明在高速网络环境下,渐进式构建的视觉收益可能不再那么重要。