JPEG XL 的十年长跑:从 Google 实验室到下一代图像编码

2026-06-04 27 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:14 分钟

互联网每天传输数十亿张图片,但承载这些图片的编码格式,二十多年来几乎没变过。JPEG 在 1992 年成为标准后,统治了整个 Web 图像生态——直到带宽与画质的矛盾越来越不可调和:要么压缩到糊成一团,要么体积大到拖慢页面。

JPEG XL 就是为终结这个矛盾而生的。它不是某天突然冒出来的标准提案,而是 Google 一个研究团队用十年时间、通过一系列看似分散的开源项目,一步步逼近最优解的结果。这个格式被预期能用三十年,背后的技术路线值得每一个做图像、视频或基础设施的工程师理解。

从理解旧格式开始:2011-2017 的"拆解"阶段

JPEG XL 的起点不是"我们要做一个新标准",而是"先把现有格式搞透"。

2011 年前后,Google 的图像研究团队开始系统性地拆解 JPEG 和 WebP 的压缩机制。他们发现几个关键瓶颈:

  • JPEG 的 8×8 DCT 块结构是画质损失的核心原因。块效应在低码率下尤其明显,但提高码率又违背压缩初衷。
  • 色度下采样(4:2:0)在节省空间的同时,让边缘和细节区域的色彩严重失真,而人眼对这些区域恰恰最敏感。
  • 熵编码效率低——JPEG 用的 Huffman 编码在概率分布不均匀时浪费大量比特。

这些发现不是纸上谈兵。团队把结论直接写进了开源项目:2017 年发布的 Guetzli,就是一个针对 JPEG 率失真优化的编码器。它不改变格式本身,只在 JPEG 的约束内寻找更优的量化策略,平均能比 libjpeg 减少 20-30% 文件体积而保持同等视觉质量。

Guetzli 的代价是编码速度极慢(一张图几分钟),但它验证了一个关键假设:JPEG 格式内部的冗余远比人们以为的大,只是传统编码器没找到最优的参数组合。

突破格式限制:从修补到重建

理解了旧格式的天花板后,下一步自然就是突破格式本身。

2018 年,Google 发布了 Pik——一个实验性的新图像编码器。Pik 的核心思路:

  1. 更大的变换块:不再受限于 8×8,使用自适应块大小,让平滑区域用大块、细节区域用小块。
  2. 更精细的色度处理:根据图像内容局部决定色度分辨率,而不是全局一刀切的 4:2:0。
  3. ANS(Asymmetric Numeral Systems)熵编码:比 Huffman 更接近理论极限,编码效率提升显著。

Pik 是 JPEG XL 的直接前身。但团队很快发现,Pik 的某些设计虽然效果好,却过于依赖特定实现,难以标准化。于是他们转向了更系统的方法。

JPEG XL 的核心技术栈

2019 年,团队联合 Cloudinary 等业界伙伴,正式向 JPEG 委员会提交提案,代号 JPEG XL。核心设计可以拆成三层:

变换层:VarDCT + Modular

JPEG XL 不是单一变换,而是双模式:

  • VarDCT 模式:继承自 Pik 的思路,用可变大小的 DCT 块处理自然图像。块大小从 4×4 到 256×256 动态选择,配合自适应量化,在低码率下大幅减少块效应。
  • Modular 模式:用预测编码(类似 FLIF/FUIF 的思路)处理计算机生成图像、图标、截图等非自然图像。这类图像的像素分布和自然照片完全不同,DCT 反而是低效的。

两种模式在编码器端自动选择或混合,不需要用户干预。

熵编码:ANS + 上下文建模

JPEG XL 使用 ANS 作为核心熵编码器,配合高度精细的上下文模型。每个符号的编码概率不是全局统计,而是根据其空间邻域、变换系数位置、已解码信息等动态预测。这意味着同一张图里,平坦天空和复杂纹理区域用的是完全不同的概率模型。

工具层:JPEG 无损重压缩

这是 JPEG XL 最实用的功能之一:可以把现有 JPEG 文件无损压缩到更小体积,同时保留完整的像素级还原能力。 原始 JPEG 的所有数据都被保留,只是用更高效的熵编码重新打包。这意味着迁移成本几乎为零——你不需要重新编码,不需要担心画质变化,只需要把文件体积缩小 20% 左右。

实践:用 JPEG XL 编码、解码和对比

下面是一套可以直接跑的命令,让你在自己的机器上体验 JPEG XL 的效果。

安装 libjxl 工具链

# macOS
brew install jpeg-xl

# Ubuntu/Debian(从源码编译,确保拿到最新版)
sudo apt-get install cmake g++ libbrotli-dev
git clone https://github.com/libjxl/libjxl.git --recursive --depth 1
cd libjxl
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
sudo cmake --install build

# 验证安装
cjxl --version
djxl --version

编码与解码

# 将 PNG 编码为 JPEG XL(指定目标质量,1-100,类似 JPEG 的质量参数)
cjxl input.png output.jxl --quality 85

# 更精细的控制:指定目标码率而非质量
cjxl input.png output.jxl --distance 1.0  # distance 越小画质越高,0 为无损

# 无损编码
cjxl input.png lossless.jxl --distance 0

# JPEG 无损重压缩(不改变像素,只缩小文件)
cjxl original.jpg recompressed.jxl --jpeg_quality_transcoding  # 保持原 JPEG 质量

# 解码回 PNG
djxl output.jxl decoded.png

批量对比脚本

下面这个 Python 脚本可以批量对比 JPEG、WebP 和 JPEG XL 在相同视觉质量下的文件体积:

#!/usr/bin/env python3
"""对比 JPEG / WebP / JPEG XL 在近似视觉质量下的文件体积。
依赖:cjxl、cwebp、libjpeg(cjpeg)已安装并在 PATH 中。
用法:python3 compare_formats.py <输入PNG或JPEG> <JPEG质量参数 1-100>
"""
import subprocess, sys, os, pathlib

def run(cmd):
    print(f"  → {cmd}")
    subprocess.run(cmd, shell=True, check=True)

def size_kb(path):
    return os.path.getsize(path) / 1024

def main():
    if len(sys.argv) < 3:
        print("用法: python3 compare_formats.py <输入文件> <质量 1-100>")
        sys.exit(1)

    src = sys.argv[1]
    q = sys.argv[2]
    stem = pathlib.Path(src).stem
    out_dir = pathlib.Path("compare_out")
    out_dir.mkdir(exist_ok=True)

    # JPEG
    jpeg_path = out_dir / f"{stem}_q{q}.jpg"
    run(f"cjpeg -quality {q} {src} > {jpeg_path}")

    # WebP
    webp_path = out_dir / f"{stem}_q{q}.webp"
    run(f"cwebp -q {q} {src} -o {webp_path}")

    # JPEG XL(用 distance 映射质量:distance ≈ (100-q)/100 的简化近似)
    distance = round((100 - int(q)) / 100, 2)
    jxl_path = out_dir / f"{stem}_d{distance}.jxl"
    run(f"cjxl {src} {jxl_path} --distance {distance}")

    # JPEG XL 无损重压缩(仅当输入是 JPEG 时)
    if src.lower().endswith((".jpg", ".jpeg")):
        jxl_trans_path = out_dir / f"{stem}_transcode.jxl"
        run(f"cjxl {src} {jxl_trans_path}")
        print(f"\nJPEG 无损重压缩: {size_kb(jxl_trans_path):.1f} KB(原始 JPEG: {size_kb(src):.1f} KB)")

    print(f"\n{'格式':<12} {'体积(KB)':<10}")
    print("-" * 22)
    print(f"{'JPEG':<12} {size_kb(jpeg_path):<10.1f}")
    print(f"{'WebP':<12} {size_kb(webp_path):<10.1f}")
    print(f"{'JPEG XL':<12} {size_kb(jxl_path):<10.1f}")

if __name__ == "__main__":
    main()

运行示例:

python3 compare_formats.py photo.png 80

典型结果:在同等视觉质量下,JPEG XL 的体积约为 JPEG 的 50-60%,比 WebP 再小 10-20%。无损模式下,JPEG XL 的体积通常比 PNG 小 40-60%。

在浏览器中启用 JPEG XL

Chrome 曾在 2023 年短暂支持 JPEG XL 后又移除,目前需要通过 flag 或扩展启用。Firefox 的支持仍在实验阶段。如果你想在 Web 页面中提前使用,可以用服务端检测 + fallback 策略:

# Nginx 配置:优先发送 JXL,不支持时 fallback 到 JPEG
location /images/ {
    # 检查 Accept 头是否包含 image/jxl
    if ($http_accept ~* "image/jxl") {
        rewrite ^(.*)\.(jpg|jpeg|png)$ $1.jxl last;
    }
    # fallback:原文件照常返回
    try_files $uri =404;
}

三十年标准的底气与现实的阻力

JPEG XL 被称为"能用三十年的格式",底气来自几个设计决策:

  • 前向兼容:比特流设计预留了扩展空间,未来可以加入新工具而不破坏旧解码器。
  • 全场景覆盖:自然照片、计算机图形、无损、有损、动画、Alpha 通道、JPEG 重压缩——一个格式全搞定,不需要在不同场景切换不同格式。
  • 编码效率天花板高:VarDCT + Modular + ANS 的组合,在理论上已经逼近当前数学工具的极限,短期内很难有格式在同等复杂度下大幅超越。

但现实阻力同样明显:

  1. 浏览器支持未落地。Chrome 移除支持的官方理由是"生态不够成熟",背后是既有格式(WebP、AVIF)的利益格局和专利顾虑。
  2. 编码速度仍需优化。JPEG XL 的编码复杂度高于 AVIF,实时场景(如用户上传缩略图生成)需要硬件加速或进一步算法优化。
  3. 迁移成本不只是技术。CDN、图片处理服务、设计工具链的全面支持需要时间,而 AVIF 已经在抢同一块地盘。

工程师的行动清单

如果你在做图片相关的基础设施,现在可以做的:

  • 立即试用 JPEG 无损重压缩。这是零风险、零画质损失的收益——把现有 JPEG 资源池跑一遍 cjxl --jpeg_quality_transcoding,平均缩小 20%,回退只需 djxl 还原为原 JPEG。
  • 在新项目中做 A/B 测试。用上面的对比脚本跑自己的图片集,看 JPEG XL 在你的典型内容(产品图、截图、摄影)上实际能省多少带宽。
  • 关注浏览器进展。Chrome 的 --enable-jxl flag 仍在更新,Firefox 的 Nightly 也有实验支持。在服务端做好 Accept 头检测,一旦主流浏览器正式支持,可以一天内切换。
  • 不要把 AVIF 和 JPEG XL 当成二选一。两者各有优势——AVIF 在视频序列和硬件解码上更强,JPEG XL 在静态图像的画质/体积比和 JPEG 兼容性上更强。根据场景选工具,而不是选阵营。

JPEG XL 的十年故事说明一件事:好的标准不是靠委员会投票出来的,而是靠十年拆解、十年实验、十年迭代,把每一个比特的效率都逼到极限后,自然成为标准。剩下的,只是生态跟上技术的时间差。


相关推荐