#jpegxl #decoder

jxl-oxide

JPEG XL decoder written in pure Rust

21 unstable releases (10 breaking)

new 0.11.3 Mar 8, 2025
0.11.1 Jan 25, 2025
0.11.0 Dec 28, 2024
0.10.1 Nov 10, 2024
0.3.0 Jun 16, 2023

#56 in Images

Download history 4857/week @ 2024-11-21 5513/week @ 2024-11-28 5102/week @ 2024-12-05 4482/week @ 2024-12-12 2169/week @ 2024-12-19 1658/week @ 2024-12-26 2737/week @ 2025-01-02 4728/week @ 2025-01-09 3950/week @ 2025-01-16 3544/week @ 2025-01-23 4896/week @ 2025-01-30 6068/week @ 2025-02-06 6882/week @ 2025-02-13 6666/week @ 2025-02-20 8058/week @ 2025-02-27 6422/week @ 2025-03-06

28,766 downloads per month
Used in 17 crates (8 directly)

MIT/Apache

1MB
24K SLoC

jxl-oxide

jxl-oxide is a JPEG XL decoder written in pure Rust. It's internally organized into a few small crates. This crate acts as a blanket and provides a simple interface made from those crates to decode the actual image.

See the crate-level docs for usage.


lib.rs:

jxl-oxide is a JPEG XL decoder written in pure Rust. It's internally organized into a few small crates. This crate acts as a blanket and provides a simple interface made from those crates to decode the actual image.

Decoding an image

Decoding a JPEG XL image starts with constructing JxlImage. First create a builder using JxlImage::builder, and use open to read a file:

let image = JxlImage::builder().open("input.jxl").expect("Failed to read image header");
println!("{:?}", image.image_header()); // Prints the image header

Or, if you're reading from a reader that implements Read, you can use read:

let image = JxlImage::builder().read(reader).expect("Failed to read image header");
println!("{:?}", image.image_header()); // Prints the image header

In async context, you'll probably want to feed byte buffers directly. In this case, create an image struct with uninitialized state using build_uninit, and call feed_bytes and try_init:

#
let mut uninit_image = JxlImage::builder().build_uninit();
let image = loop {
    uninit_image.feed_bytes(reader.read().await?);
    match uninit_image.try_init()? {
        InitializeResult::NeedMoreData(uninit) => {
            uninit_image = uninit;
        }
        InitializeResult::Initialized(image) => {
            break image;
        }
    }
};
println!("{:?}", image.image_header()); // Prints the image header

JxlImage parses the image header and embedded ICC profile (if there's any). Use JxlImage::render_frame to render the image.

use jxl_oxide::{JxlImage, RenderResult};

for keyframe_idx in 0..image.num_loaded_keyframes() {
    let render = image.render_frame(keyframe_idx)?;
    present_image(render);
}

Color management

jxl-oxide has basic color management support, which enables color transformation between well-known color encodings and parsing simple, matrix-based ICC profiles. However, jxl-oxide alone does not support conversion to and from arbitrary ICC profiles, notably CMYK profiles. This includes converting from embedded ICC profiles.

Use JxlImage::request_color_encoding or JxlImage::request_icc to set color encoding of rendered images. Conversion to and/or from ICC profiles may occur if you do this; in that case, external CMS need to be set using JxlImage::set_cms.

let mut image = JxlImage::builder().read(reader).expect("Failed to read image header");
image.set_cms(MyCustomCms);

let color_encoding = EnumColourEncoding::display_p3(RenderingIntent::Perceptual);
image.request_color_encoding(color_encoding);

External CMS is set to Little CMS 2 by default if lcms2 feature is enabled. You can explicitly disable this by setting CMS to NullCms.

let mut image = JxlImage::builder().read(reader).expect("Failed to read image header");
image.set_cms(NullCms);

Not using set_cms for color management

If implementing ColorManagementSystem is difficult for your use case, color management can be done separately using ICC profile of rendered images. JxlImage::rendered_icc returns ICC profile for further processing.

use jxl_oxide::{JxlImage, RenderResult};

let icc_profile = image.rendered_icc();
for keyframe_idx in 0..image.num_loaded_keyframes() {
    let render = image.render_frame(keyframe_idx)?;
    present_image_with_cms(render, &icc_profile);
}

Feature flags

  • rayon: Enable multithreading with Rayon. (default)
  • image: Enable integration with image crate.
  • lcms2: Enable integration with Little CMS 2.

Dependencies

~2.8–4MB
~67K SLoC