All formats

jxl-oxide

LanguageRust
LicenseMIT or Apache-2.0
Repositorygithub.com/tirr-c/jxl-oxide
Documentationdocs.rs/jxl-oxide
Depends onpure Rust; optional rayon (threads), lcms2 or moxcms (color management), image (integration)
Read / writeyes / no, all features including animation, HDR, CMYK and JPEG reconstruction

jxl-oxide is a complete JPEG XL decoder written in Rust, with no C code. It passes the JPEG XL conformance tests and decodes everything the format allows: lossy and lossless images, animation, extra channels, HDR, progressive and cropped decoding, and reconstruction of the original JPEG from recompressed files. It also ships a command-line tool, jxl-oxide (cargo install jxl-oxide-cli).

It only decodes. To encode JPEG XL from Rust, use jpegxl-rs (libjxl bindings) or the lossless-only zune-jpegxl.

Installation#

Shell
cargo add jxl-oxide
# with image crate integration:
cargo add jxl-oxide --features image
cargo add image

Default features include rayon for multithreaded decoding.

Basic usage: decode a JXL file to pixels#

Rust
use jxl_oxide::JxlImage;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let image = JxlImage::builder().open("in.jxl")?;
    println!("{} x {}, {:?}", image.width(), image.height(), image.pixel_format());

    let render = image.render_frame(0)?;  // first keyframe
    let mut stream = render.stream();     // interleaved samples, orientation applied
    let len = (stream.width() * stream.height() * stream.channels()) as usize;
    let mut pixels = vec![0u8; len];      // u8, u16 or f32
    stream.write_to_buffer(&mut pixels);

    println!("{} channels, first pixel {:?}", stream.channels(), &pixels[..stream.channels() as usize]);
    Ok(())
}

How to convert JXL to PNG with the image crate#

With the image feature, JxlDecoder implements image::ImageDecoder:

Rust
use image::DynamicImage;
use jxl_oxide::integration::JxlDecoder;
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let decoder = JxlDecoder::new(File::open("in.jxl")?)?;
    let img = DynamicImage::from_decoder(decoder)?;
    img.save("out.png")?;
    Ok(())
}

Or register a hook once, and image::open handles .jxl like any other format:

Rust
jxl_oxide::integration::register_image_decoding_hook();
let img = image::open("in.jxl")?;

How to read all frames of an animation#

Rust
let image = JxlImage::builder().open("anim.jxl")?;
for i in 0..image.num_loaded_keyframes() {
    let render = image.render_frame(i)?;
    println!("frame {i}: duration {} ticks", render.duration());
}

How to rebuild the original JPEG from a recompressed JXL#

Rust
use jxl_oxide::{JpegReconstructionStatus, JxlImage};
use std::fs::File;

let image = JxlImage::builder().open("photo.jxl")?;
if matches!(image.jpeg_reconstruction_status(), JpegReconstructionStatus::Available) {
    image.reconstruct_jpeg(File::create("photo.jpg")?)?;  // bit-identical to the original
}

How to get the ICC profile#

Rust
let image = JxlImage::builder().open("in.jxl")?;
if let Some(icc) = image.original_icc() {
    println!("embedded ICC profile: {} bytes", icc.len());
}
let output_icc: Vec<u8> = image.rendered_icc(); // profile of the pixels render_frame returns

Tips#