All formats

jpegxl-rs

LanguageRust
LicenseGPL-3.0-or-later
Repositorygithub.com/inflation/jpegxl-rs
Documentationdocs.rs/jpegxl-rs
Depends onlibjxl (system library, or built from source with the vendored feature)
Read / writeyes / yes, lossy, lossless, animation, lossless JPEG recompression and reconstruction

jpegxl-rs is a safe Rust wrapper of libjxl, the reference encoder. It’s the way to encode JPEG XL from Rust: lossy and lossless, with quality and speed settings, metadata, animation, and lossless JPEG transcoding. Decoding gives 8-bit, 16-bit, half or float pixels.

The crate is GPL-3.0, so a program that links it must be distributed under GPL-compatible terms. If you only need to decode, the MIT/Apache jxl-oxide avoids both the GPL and the C dependency.

Installation#

Shell
# with libjxl installed on the system (libjxl-dev, brew install jpeg-xl, vcpkg)
cargo add jpegxl-rs
# or build and link libjxl statically (needs CMake and a C++ compiler)
cargo add jpegxl-rs --features vendored

The image feature (on by default) adds conversion to and from image::DynamicImage.

Basic usage: decode and encode#

Rust
use jpegxl_rs::decode::{Metadata, Pixels};
use jpegxl_rs::{decoder_builder, encoder_builder};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = std::fs::read("in.jxl")?;

    let decoder = decoder_builder().build()?;
    let (Metadata { width, height, num_color_channels, has_alpha_channel, .. }, pixels) =
        decoder.decode(&data)?;
    println!("{width} x {height}, {num_color_channels} color channels, alpha: {has_alpha_channel}");

    if let Pixels::Uint8(rgb) = pixels {
        let mut encoder = encoder_builder().quality(2.0).build()?; // quality is a distance: 1.0 visually lossless
        let jxl: Vec<u8> = encoder.encode::<u8>(&rgb, width, height)?;
        std::fs::write("out.jxl", jxl)?;
    }
    Ok(())
}

The decoder keeps the bit depth of the file: an 8-bit image gives Pixels::Uint8, a 16-bit or HDR image Uint16 or Float. Use decoder.decode_with::<u8>(&data) to always get 8-bit samples.

How to convert PNG to JPEG XL#

Rust
use jpegxl_rs::encode::EncoderSpeed;
use jpegxl_rs::encoder_builder;

let img = image::open("photo.png")?.to_rgb8();
let mut encoder = encoder_builder()
    .quality(1.0)                  // distance: 0.0 lossless .. 15.0
    .speed(EncoderSpeed::Squirrel) // effort 7 (default); Lightning = 1 .. Glacier = 10
    .build()?;
let jxl = encoder.encode::<u8>(img.as_raw(), img.width(), img.height())?;
std::fs::write("photo.jxl", jxl)?;

How to save a lossless JPEG XL#

Rust
let mut encoder = encoder_builder()
    .lossless(true)
    .uses_original_profile(true)
    .build()?;
let jxl = encoder.encode::<u8>(img.as_raw(), img.width(), img.height())?;

How to recompress a JPEG losslessly#

Pass the JPEG file bytes; the result can be turned back into the identical JPEG:

Rust
let jpeg = std::fs::read("photo.jpg")?;
let mut encoder = encoder_builder().use_container(true).build()?;
let jxl = encoder.encode_jpeg(&jpeg)?;
std::fs::write("photo.jxl", &jxl)?;

// back to JPEG (falls back to pixels if the file has no reconstruction data)
use jpegxl_rs::decode::Data;
let decoder = decoder_builder().build()?;
if let (_, Data::Jpeg(restored)) = decoder.reconstruct(&jxl)? {
    std::fs::write("restored.jpg", restored)?;
}

How to decode to an image::DynamicImage#

Rust
use jpegxl_rs::decoder_builder;
use jpegxl_rs::image::ToDynamic;

let data = std::fs::read("in.jxl")?;
let decoder = decoder_builder().build()?;
let img = decoder.decode_to_image(&data)?; // Option<DynamicImage>
if let Some(img) = img {
    img.save("out.png")?;
}

Tips#