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 扫描组成的帧,作者实现了两个目标:

  1. 增加帧数: 减少每帧的扫描数量,使得更多的帧可以放入解码器的限制范围内。
  2. 消除重影: 由于 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 等库时显著降低解码速度,这表明在高速网络环境下,渐进式构建的视觉收益可能不再那么重要。

Sources