jpegxl-rs
| Language | Rust |
|---|---|
| License | GPL-3.0-or-later |
| Repository | github.com/inflation/jpegxl-rs |
| Documentation | docs.rs/jpegxl-rs |
| Depends on | libjxl (system library, or built from source with the vendored feature) |
| Read / write | yes / 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#
# 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#
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#
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#
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:
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#
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#
- Encoding and decoding are single-threaded by default. Create a
jpegxl_rs::ThreadsRunner::default()and pass it with.parallel_runner(&runner)on the builder. - Encode animations with
encoder.session(&info)andEncoderFrame::new(&pixels).duration(...).