All formats

jpegxr

LanguageRust (with bundled C code)
LicenseBSD-3-Clause (bundled jxrlib: BSD-2-Clause)
Repositorygithub.com/brion/jpegxr
Documentationdocs.rs/jpegxr
Depends onC compiler and libclang (the build uses cc and bindgen)
Read / writeyes / no; all jxrlib pixel formats, in their native layout

jpegxr wraps Microsoft’s jxrlib, whose source is included in the crate, with a safe Rust API. ImageDecode::with_reader takes any Read + Seek source, such as a File. You get the size, DPI and pixel format, then copy the decoded pixels into a buffer, either the whole image or a rectangle.

There’s no pixel format conversion: the buffer gets the file’s native format, for example 32bpp BGRA for 8-bit images or 64bpp RGBA half float for Windows HDR screenshots. PixelInfo tells you the bits per pixel and channel layout so you can interpret the bytes. The crate also builds to WebAssembly for use from JavaScript. Encoding isn’t implemented.

Installation#

Shell
cargo add jpegxr

Building needs a C compiler and libclang for bindgen (on Windows, install LLVM and set LIBCLANG_PATH if bindgen can’t find it).

Basic usage: decode a JXR file#

Rust
use jpegxr::{ImageDecode, PixelInfo};
use std::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = File::open("in.jxr")?;
    let mut decoder = ImageDecode::with_reader(input)?;

    let (width, height) = decoder.get_size()?;
    let format = decoder.get_pixel_format()?;
    let info = PixelInfo::from_format(format);
    println!(
        "{} x {}, {:?}, {} channels, {} bits per pixel, alpha: {}",
        width,
        height,
        format,
        info.channels(),
        info.bits_per_pixel(),
        info.has_alpha()
    );

    let stride = (width as usize * info.bits_per_pixel() + 7) / 8;
    let mut pixels = vec![0u8; stride * height as usize];
    decoder.copy_all(&mut pixels, stride)?;
    println!("decoded {} bytes", pixels.len());
    Ok(())
}

How to detect an HDR screenshot#

Windows HDR screenshots use 16-bit half-float RGBA (scRGB, 1.0 = 80 nits):

Rust
use jpegxr::PixelFormat;

match decoder.get_pixel_format()? {
    PixelFormat::PixelFormat64bppRGBAHalf => println!("HDR half float RGBA"),
    PixelFormat::PixelFormat128bppRGBAFloat => println!("HDR 32-bit float RGBA"),
    PixelFormat::PixelFormat32bppBGRA | PixelFormat::PixelFormat24bppBGR => println!("8-bit SDR"),
    other => println!("other format: {:?}", other),
}

To turn half-float bytes into f32 values, use the half crate: half::f16::from_le_bytes([lo, hi]).to_f32().

How to decode a region#

copy decodes only the macroblocks a rectangle needs. The crate’s documentation notes that this mode is less tested than copy_all:

Rust
use jpegxr::{ImageDecode, PixelInfo, Rect};
use std::fs::File;

let mut decoder = ImageDecode::with_reader(File::open("big.jxr")?)?;
let info = PixelInfo::from_format(decoder.get_pixel_format()?);
let rect = Rect::new(256, 256, 512, 512);   // x, y, width, height
let stride = (512 * info.bits_per_pixel() + 7) / 8;
let mut pixels = vec![0u8; stride * 512];
decoder.copy(&rect, &mut pixels, stride)?;

How to get the resolution#

Rust
let (dpi_x, dpi_y) = decoder.get_resolution()?;
println!("{dpi_x} x {dpi_y} DPI");

Tips#