blazediff-shared 6.0.0

Shared BlazeDiff primitives: the RGBA8 image type, YIQ color math, and PNG/JPEG/QOI I/O
Documentation

blazediff-shared

The primitives every BlazeDiff crate sits on: the RGBA8 Image buffer, YIQ color math, and decode/encode for PNG, JPEG and QOI.

Why it exists

The crates above it form a chain — blazediff depends on blazediff-ssim, which depends on blazediff-interpret — so anything two of them share has to live below all of them. Two things qualify:

  • Image I/O. Everyone needs to turn a path or an encoded buffer into pixels, and nobody wants to be a codec. Keeping it here also means one copy of the format dispatch; it used to be pasted into the CLI, the N-API binding and the Python extension separately, which is exactly the kind of thing that drifts.
  • YIQ color math. Both the pixel diff and the region classifier measure perceptual distance between two pixels, and they now live in different crates.

Usage

use blazediff_shared::{load_image_pair, save_image, Image, ImageFormat};

let (a, b) = load_image_pair("expected.png", "actual.jpg")?;
println!("{}x{}", a.width, a.height);

// `Image` is RGBA8, 4 bytes per pixel, row-major.
let out = Image::new(a.width, a.height);
save_image(&out, "diff.png", /* compression */ 0, /* quality */ 90)?;
# Ok::<(), blazediff_shared::ImageError>(())

Format is taken from the file extension for paths and from the magic bytes for buffers:

use blazediff_shared::{decode_image, ImageFormat};

assert_eq!(ImageFormat::from_path("a.JPEG"), Some(ImageFormat::Jpeg));
assert_eq!(ImageFormat::from_bytes(b"qoif...."), Some(ImageFormat::Qoi));

let image = decode_image(&encoded_bytes)?;
# Ok::<(), blazediff_shared::ImageError>(())

API

Item Purpose
Image RGBA8 buffer plus dimensions, with as_u32 / get_pixel / set_pixel helpers
ImageError Io, Png, Jpeg, Qoi, UnsupportedFormat
ImageFormat from_path, from_bytes, as_str
load_image, load_image_pair path in, format auto-detected; the pair loads in parallel
decode_image, decode_image_pair encoded bytes in, format sniffed from magic bytes
save_image format from the output extension
load_png … save_qoi the per-codec entry points, when you already know the format
yiq::color_delta squared YIQ distance between two packed pixels, the perceptual metric behind both the diff and the classifier
yiq::{unpack_pixel, pack_pixel, is_opaque} packed-u32 pixel helpers

Per-codec modules (png_io, jpeg_io, qoi_io) are public too, for callers that want to skip detection.

Codecs

  • PNG — vendored libspng, compiled with its SIMD paths. Setting BLAZEDIFF_PNG_ENABLED to a truthy value (1/true/yes/on) routes decode and level-0 encode through the in-house blazediff-png codec instead, with spng staying as a defensive fallback. The toggle lives here rather than in any one front-end, so everything built on this crate inherits it — the blazediff CLI, its N-API and Python bindings, and @blazediff/ssim-native. Read once per process; query it with blazediff_shared::blazediff_png_enabled().
  • JPEG — vendored libjpeg-turbo via the TurboJPEG API.
  • QOI — qoi-rust, pure Rust.

Adler-32 verification stays on for PNG decode: these entry points read arbitrary, possibly untrusted files, so a corrupt zlib stream must error rather than hand back wrong pixels.

Features

  • codecs (default) — everything above. Requires a C toolchain and cmake.
  • Without it the crate is pure Rust and compiles to wasm32, leaving only Image, ImageError and ImageFormat. That is what the wasm build of blazediff links.
  • fuzzing — internal only; exposes the spng reference decoder for blazediff-png's differential tests.

Error messages are contract

ImageError's Display strings are surfaced verbatim by the CLI, the N-API binding, the Python extension and the JS wrappers, and @blazediff/core-native pattern-matches on them to tell a missing file from a malformed one. Changing their wording is a breaking change.

License

MIT