JPEG XL Returns to Chrome: From Obsolete to the Future

⚡ Chromium 🔧 Rust / C++ 👤 Helmut Januschka

How JPEG XL went from "obsolete" to the future of web images - and the honor of being part of its comeback

Status:LANDED — JPEG XL is now supported in Chromium Canary 145.0.7632.0+!

🎉 MISSION ACCOMPLISHED

JPEG XL has officially returned to Chromium.

Available in Canary 145.0.7632.0+ (Enable via chrome://flags/#enable-jxl-image-format)

The Story

In October 2022, Google removed JPEG XL from Chromium, citing "insufficient ecosystem interest."

In November 2025, Chrome's Architecture Tech Leads announced: "We would welcome contributions to integrate a performant and memory-safe JPEG XL decoder in Chromium."

What changed?

Why JPEG XL?

Implementation

Phase 1: C++ (Abandoned)

Initial approach used libjxl in C++. Feature complete with animation support, but feedback requested Rust for memory safety.

ABANDONED CL 7170439 — C++ implementation (superseded by Rust)

Phase 2: Rust (Current)

Pivoted to jxl-rs, a pure Rust decoder. Memory-safe and aligned with Chromium's direction.

MERGED CL 7201443 — Add jxl-rs to third_party (73,908 lines)

Blink integration:

  1. MERGED CL 7320482 — Add JXL infrastructure: enums and build flag
  2. MERGED CL 7319379 — Add JXL image decoder using jxl-rs
  3. MERGED CL 7184969 — Wire up JXL decoder (The final piece!)

The Rust decoder required significant optimization. The jxl-rs community merged 26 PRs in December 2025:

Image Before After C++ libjxl Speedup
bike (2048×2560) 265ms 198ms 170ms +34%
progressive (4064×2704) 694ms 560ms 450ms +24%
blendmodes (1024×1024) 115ms 85ms 266ms +35%
View all jxl-rs contributions (26 PRs) →

SIMD Optimizations:

  • MERGED #585 SIMD table lookup with shuffle
  • MERGED #537 SIMD F32 to U8/U16 conversions
  • MERGED #536 SIMD transfer functions
  • MERGED #535 SIMD upsampling (2x, 4x, 8x)
  • MERGED #533 SIMD min and store_interleaved
  • MERGED #532 SIMD noise convolution
  • MERGED #530 SIMD chroma upsampling
  • MERGED #529 SIMD YCbCr to RGB

Performance:

  • MERGED #600 SinglePropertyLookup for table-based routing
  • MERGED #583 Optimize EPF sigma for modular encoding
  • MERGED #581 Cache default quantization tables
  • MERGED #538 Flattened modular trees
  • MERGED #526 Weighted predictor cache locality
  • MERGED #525 Cache natural coefficient orders
  • MERGED #524 Precompute cosines in spline rendering

API & Integration:

  • MERGED #594 Bump version to 0.2.2
  • MERGED #587 Bump version to 0.2.1
  • MERGED #586 Fix rendering bugs
  • MERGED #574 Add premultiply_output option
  • MERGED #556 FFI-friendly API for Chromium
  • MERGED #540 Benchmark CI stage
  • MERGED #496 Remaining decoder API methods
  • MERGED #495 Preview frame API
  • MERGED #494 Fix spline DISTANCE_EXP
  • MERGED #493 Fix rect bounds check
  • MERGED #492 Remove zerocopy dependency
  • MERGED #491 HDR tone mapping
  • MERGED #533 Add min and store_interleaved

Current Status

✅ Standard image decoding
✅ ICC color profiles
✅ Animations (multi-frame)
✅ Alpha/transparency
✅ Wide gamut (Display-P3)
✅ HDR support (PQ/HLG)

Chromium rolls:


Use JPEG XL Today

Don't want to wait? The jxl-rs-polyfill brings JPEG XL to all browsers now.

One line of code:

<script src="https://cdn.jsdelivr.net/npm/jxl-rs-polyfill/dist/auto.js"></script>

That's it. All .jxl images work everywhere.

More usage examples →

NPM:

npm install jxl-rs-polyfill
import { JXLPolyfill } from 'jxl-rs-polyfill';
const polyfill = new JXLPolyfill();
await polyfill.start();

Programmatic decoding:

import { decodeJxlToPng } from 'jxl-rs-polyfill';
const pngBytes = await decodeJxlToPng(jxlBytes);

GitHub: jxl-rs-polyfill


Live Demo

JXL animation support — no browser supports JXL animations natively yet, so this uses the WASM polyfill:

Animated JXL Demo


Acknowledgments

Special thanks to Luca Versari (veluca93) for reviewing PRs and managing jxl-rs releases. The collaboration from jxl-rs maintainers made this possible.


Resources: