Skip to content

jskoiz/imx

Repository files navigation

IMX

CI

A fast, memory-safe, differentially-verified image conversion library and CLI for Rust. IMX decodes, identifies, transcodes, and resizes images through a small imx binary and a set of focused codec crates, with byte-identical, deterministic output you can rely on in pipelines and tests.

IMX is not a fork or port of ImageMagick. It shares no code with it, links nothing from it, and does not provide convert, magick, or mogrify. ImageMagick is used only as an external oracle in IMX's own compatibility tests and benchmarks.

Why trust it

This is the headline. IMX is built to be trustworthy on real and hostile input:

  • Differential testing against the real ImageMagick binary as an oracle. Every codec's identify/decode/transcode behavior is checked against ImageMagick's output, so IMX is verified against a mature reference implementation rather than only against itself.
  • Per-codec fuzzing and a malformed-input corpus. Coverage-guided fuzz targets exercise the BMP, FARBFELD, GIF, JPEG, PNG, PNM, QOI, TIFF, and WebP decode/identify entrypoints, backed by seeded corpora and a malformed-input suite, with crash artifacts retained from scheduled long-running fuzz runs.
  • Deterministic, byte-identical output. The same input always produces the same bytes. Output writes use a temp file plus atomic rename, so a malformed input never leaves a partial output behind.
  • Checked, bounded allocation. Decoded pixel buffers are capped (MAX_PIXEL_BYTES = 512 MiB; JPEG decode capped at 128 MiB for decoder overhead) and every allocation goes through try_reserve_exact, so hostile dimensions return an error instead of aborting. CLI input reads are capped at 513 MiB.
  • Memory-safe by construction. The product decode/encode paths are safe Rust. Runtime dependencies are the local IMX crates plus pure-Rust PNG and JPEG codecs.

Install

Install the current source tree directly:

git clone https://github.com/jskoiz/imx.git
cd imx
cargo install --path crates/cli --bin imx --locked
imx --version
imx self-test

The source install path is verified by scripts/verify-install.sh from a fresh checkout in CI.

Install the published release archive directly:

IMX_VERSION=v0.20.0
curl -fsSL "https://raw.githubusercontent.com/jskoiz/imx/${IMX_VERSION}/scripts/install.sh" | sh

The installer verifies the published SHA256SUMS, installs imx, asserts the installed version, checks for glibc 2.34 or newer on Linux, and runs a small imx self-test plus identify/report JSON and identify/transcode/resize/resize-fit/batch-convert smoke. Hosted tag automation publishes Linux archives for:

  • imx-preview-0.20.0-x86_64-unknown-linux-gnu.tar.gz
  • imx-preview-0.20.0-aarch64-unknown-linux-gnu.tar.gz

The existing v0.19.0 GitHub release predates this release-candidate proof. See PRODUCTION_READINESS.md before cutting or publishing a public release from main.

Install from the tap after the release flow has updated jskoiz/homebrew-imx:

brew tap jskoiz/imx
brew install imx
imx --version

This uses the jskoiz/homebrew-imx tap formula generated from each published release's SHA256SUMS. It is not a Homebrew/core formula. Published Linux archives require glibc 2.34 or newer; release/archive smoke asserts that published Linux binaries do not reference GLIBC_* symbols newer than GLIBC_2.34. Hosted GitHub Actions for the tap are Linux-only; macOS install proof must be run locally or manually after explicit approval.

macOS archives or tap blocks require recorded local/manual proof before being claimed. No Windows, Homebrew/core, or package-manager distribution beyond Cargo and the jskoiz/imx tap is claimed for the binary.

Cargo package installation is available only after the crates.io publish step has completed:

cargo install imx-cli
imx --version
imx self-test

imx-cli is the Cargo package name; the installed binary is named imx.

New in this release

This release sharpens resize quality and broadens the output surface ahead of a 1.0 tag (see CHANGELOG.md and docs/v1.0-readiness.md):

  • Real resampling filters for resize/resize-fit via --filter (point, box, triangle, catmull-rom, lanczos3), with Lanczos3 the default so downscales are no longer nearest-neighbor aliased.
  • Animated GIF output via imx assemble, composing ordered frames with per-frame delays.
  • Color and tone pipeline ops in imx pipeline, alongside the existing geometry ops.
  • ICC profile passthrough with --strip to drop ICC/ancillary metadata for a minimal, reproducible output.
  • docs.rs build metadata, an explicit Rust MSRV, and full decode-fuzz coverage across the current codec set.

Quick examples

# Identify an image (one stable key-value line)
imx identify photo.png
# format=PNG width=1920 height=1080 channels=RGB depth=8

# Identify as deterministic JSON
imx identify --json photo.png
# {"schema_version":1,"format":"PNG","width":1920,"height":1080,"channels":"RGB","depth":8}

# Transcode between formats
imx photo.png photo.jpg

# JPEG output quality
imx --quality 85 photo.png photo.jpg

# Exact and aspect-preserving resize
imx resize 800x600 photo.png thumb.png
imx resize-fit 800x600 photo.png thumb.png

# Geometric operations
imx crop 100x100+10+10 photo.png cropped.png
imx rotate 90 photo.png rotated.png
imx flip photo.png flipped.png
imx flop photo.png flopped.png

# Streaming through stdin/stdout with a FORMAT: prefix
cat photo.png | imx PNG:- JPEG:- > photo.jpg

# Batch conversion to a directory
imx batch-convert --to PNG --output-dir out/ a.jpg b.bmp c.qoi

# Offline self-test of the installed binary
imx self-test

Exact uppercase format prefixes (BMP:, FARBFELD:, GIF:, JPEG:, QOI:, PBM:, PGM:, PNG:, PPM:, TIFF:, WEBP:) may be attached to operands to assert the expected format; they are stripped before file IO and must match the detected input format or output extension. (JPG: is intentionally not a prefix.)

Shell completions & man page

imx ships hand-written shell completion scripts and a checked-in roff manual page so the CLI is discoverable from your shell and man.

Completions

imx completions <bash|zsh|fish> prints a completion script to standard output. The output is deterministic (identical bytes on every run), so it can be checked in or regenerated freely. An unknown shell or a missing argument exits with status 2.

# bash: source once per shell, or install system-wide
source <(imx completions bash)
imx completions bash > /usr/local/etc/bash_completion.d/imx

# zsh: install onto an fpath directory, then restart the shell
imx completions zsh > "${fpath[1]}/_imx"

# fish: install into the user completions directory
imx completions fish > ~/.config/fish/completions/imx.fish

Man page

A roff manual page is checked in at man/imx.1. It mirrors the --help text: synopsis, every subcommand, options, exit status (0 success, 1 operation error, 2 usage error), supported formats and prefixes, and the FORMAT:- streaming convention.

# read it directly
man ./man/imx.1

# install system-wide
install -m 0644 man/imx.1 /usr/local/share/man/man1/imx.1

Supported formats & operations

Container formats

Format Extensions / prefix Input Output Notes
PNG .png / PNG: yes yes Static non-interlaced 8/16-bit grayscale, RGB, RGBA, grayscale-alpha. No APNG, interlace, indexed/palette, low-bit, or metadata preservation.
JPEG .jpg, .jpeg / JPEG: yes yes 8-bit grayscale/RGB baseline + progressive input. Read-only EXIF Orientation (values 1-8) normalized on decode. Output is baseline JPEG; quality selectable via --quality 1..=100.
BMP .bmp / BMP: yes yes Uncompressed 24-bit BGR/RGB and 32-bit BGRA/RGBA; top-down and bottom-up input. No indexed/RLE/bitfields/OS2/high-depth.
QOI .qoi / QOI: yes yes RGB8 / RGBA8.
FARBFELD .ff, .farbfeld / FARBFELD: yes yes RGBA16BE.
PBM .pbm / PBM: yes yes ASCII P1 + binary P4 bilevel in; deterministic binary P4 out.
PGM .pgm / PGM: yes yes ASCII P2 + binary P5 GRAY8/GRAY16BE in; deterministic binary P5 out.
PPM .ppm / PPM: yes yes ASCII P3 + binary P6 RGB8/RGB16BE in (maxval up to 65535); deterministic binary P6 out.
TIFF .tif, .tiff / TIFF: yes yes First-IFD 8/16-bit grayscale, 8/16-bit RGB, 8-bit RGBA. Deterministic little-endian uncompressed baseline out. No multi-page, compression, palette, CMYK, or YCbCr.
WebP .webp / WEBP: yes yes Still and animated decode; --frame selects a composited frame. Output is a single lossless still WebP frame. No animated WebP output.
GIF .gif / GIF: yes yes Decode supports composited frame selection. Single-image output writes a deterministic still GIF palette; imx assemble writes animated GIF output with uniform delay/loop settings.

Operations

Operation Command Notes
Identify imx identify [--json] <input> Stable key-value line, or deterministic JSON.
Report imx report --json <input> Identify fields plus status and diagnostic_code.
Transcode imx <input> <output> Cross-format and deterministic same-format rewrite.
Exact resize imx [--filter <filter>] resize <geometry> <input> <output> Exact, single-axis, or percent geometry; default lanczos3, with point for byte-exact nearest-neighbor.
Aspect-preserving fit imx [--filter <filter>] resize-fit <w>x<h> <in> <out> Largest integer box that preserves aspect ratio.
Crop imx crop ... Extract a rectangular region.
Rotate imx rotate <90|180|270> ... Right-angle rotation.
Flip / Flop imx flip ... / imx flop ... Vertical / horizontal mirror.
Pipeline imx pipeline <in> <out> --op <op>... Chained geometry and color/tone ops in one decode/encode pass.
Compare imx compare [--metric <ae|mae|psnr>] <a> <b> Deterministic pixel diff after RGBA8 normalization.
JPEG quality --quality <1..=100> Quality for JPEG output.
Streaming FORMAT:- Read stdin / write stdout via a - operand with a FORMAT: prefix.
Batch convert imx batch-convert --to <FMT> --output-dir <dir> [--resize|--resize-fit] <input>... Preflighted; deterministic output names; no overwrite/recurse/glob.
Self-test imx self-test Offline installed-binary smoke check.

Successful transcodes and resizes are silent and write the output file. Data, IO, malformed-input, and validation failures exit 1 with error: ...; usage and unsupported command shapes exit 2.

Known lossy paths

  • FARBFELD/PPM/PNG to QOI quantizes 16-bit samples to 8-bit.
  • FARBFELD/QOI/PPM/PGM to PBM uses Rec.709 luma where needed, then thresholds <128 / <32768 to black and higher values to white.
  • FARBFELD to PGM converts RGBA16BE to GRAY16BE via Rec.709 luma, ignoring alpha.
  • 8-bit QOI/PBM/PGM/PPM to FARBFELD expands samples to 16-bit by byte replication and adds opaque alpha where needed; high-depth PGM/PPM keeps 16-bit samples.
  • Any output to JPEG quantizes to 8-bit grayscale or RGB and is lossy; non-opaque alpha is rejected for JPEG output.
  • Any output to BMP preserves alpha only for RGBA output.
  • Output to PPM drops alpha; output to PGM drops color/alpha; output to PBM drops color, alpha, and grayscale precision.

Same-format rewrites are deterministic decode/re-encode operations and do not preserve source bytes, comments, whitespace, Netpbm ASCII/binary form, QOI opcode choices, or other incidental representation details.

Not yet supported

By design, IMX does not (yet) provide:

  • Full ImageMagick CLI parsing, the magick/convert/mogrify commands, delegates, MagickCore, or MagickWand.
  • General metadata preservation beyond read-only JPEG/TIFF EXIF Orientation and ICC profile passthrough.
  • APNG, interlaced PNG, indexed/palette PNG, low-bit PNG, tRNS.
  • CMYK/YCCK JPEG, 12-bit JPEG, arithmetic-coded JPEG, lossless JPEG / JPEG-LS, JPEG 2000, JPEG XL.
  • Indexed/compressed/bitfields/OS2/high-depth BMP.
  • PAM, PFM, and other container formats not listed above.

Items addressed in this release (see the new-in-this-release list and CHANGELOG.md): animated GIF output (imx assemble), real resampling filters beyond nearest-neighbor (--filter, Lanczos3 default), color and tone pipeline ops, and ICC profile passthrough with --strip.

Safety posture

  • Product decode/encode paths are safe Rust.
  • Runtime dependencies are the local IMX crates plus pure-Rust codec dependencies used by the format crates.
  • Decoded pixel buffers are capped at 512 MiB, with JPEG decode capped at 128 MiB to account for decoder working-memory overhead.
  • CLI input reads are capped at 513 MiB.
  • Output writes use a temp file plus rename; malformed input does not leave the requested output behind.
  • Fuzz targets cover BMP, FARBFELD, GIF, JPEG, PNG, PNM, QOI, TIFF, and WebP decode/identify entrypoints with seeded corpora.

Library

The core image model is published as the imx-core crate (crates/core). It provides the codec-free Image type and the deterministic pixel-format conversions used by the CLI and codec crates:

use imx_core::{Image, PixelFormat};

let rgb = Image::new(2, 1, PixelFormat::Rgb8, vec![255, 0, 0, 0, 255, 0])?;
let gray = rgb.to_gray8()?;
assert_eq!(gray.pixels(), &[54, 182]);
# Ok::<(), imx_core::ImageError>(())

The codec and CLI crates depend on imx-core by path in this workspace and are published together with the same version when a release is cut.

Release gates

Run the local gates:

./scripts/ci.sh

Require ImageMagick oracle differentials:

IMAGEMAGICK_MAGICK=/path/to/magick IMX_REQUIRE_ORACLE=1 ./scripts/ci.sh

Run the corpus differential report directly:

IMAGEMAGICK_MAGICK=/path/to/magick ./scripts/differential-corpus.sh

Run coverage-guided fuzz smoke:

IMX_FUZZ_MAX_TOTAL_TIME=5 ./scripts/run-fuzz.sh

Scheduled CI runs the same cargo-fuzz targets for a longer window and retains crash artifacts under the fuzz evidence directory.

Check the declared Rust MSRV:

rustup toolchain install 1.85.0 --profile minimal
IMX_MSRV_TOOLCHAIN=1.85.0 ./scripts/check-msrv.sh

Capture the pre-1.0 public API surface before tagging a release candidate:

cargo install cargo-public-api --locked
bash scripts/api-freeze.sh

Generate machine-readable benchmark evidence:

IMAGEMAGICK_MAGICK=/path/to/magick ./scripts/bench-release.sh

Compare current benchmark/RSS evidence against the v0.5.0 baseline:

IMAGEMAGICK_MAGICK=/path/to/magick IMX_BENCH_BASE_REF=v0.5.0 ./scripts/bench-regression.sh

Package a release archive:

./scripts/package-release.sh

Release archives use deterministic tar/gzip metadata and aggregate SHA256SUMS entries so repeated packaging of the same built payload is byte-for-byte comparable. For v0.6.0 and later tags, hosted Linux release automation packages x86_64-unknown-linux-gnu and aarch64-unknown-linux-gnu archives. No hosted macOS or iOS runner is used for release proof.

Verify source installation from a fresh checkout:

IMX_INSTALL_REPO_URL=https://github.com/jskoiz/imx.git ./scripts/verify-install.sh

Verify published Linux release archives after GitHub release publication:

IMX_VERSION=v0.20.0 IMX_RELEASE_TARGET=x86_64-unknown-linux-gnu ./scripts/verify-release-archive.sh

Verify the Homebrew tap install smoke after the tap update:

brew tap jskoiz/imx
brew install imx
brew test imx
imx --version
imx self-test
test "$(imx --version)" = "imx 0.20.0"

brew test verifies installation only. Compatibility remains covered by the CI differential corpus, fuzz, benchmark, and conformance gates.

Documentation

See COMPATIBILITY.md for the exact behavior contract and PRODUCTION_READINESS.md for current release evidence, known gaps, and the next adoption milestone.

Version history

IMX is developed one verified slice at a time. Each release adds a tightly scoped, fully tested capability rather than a broad surface.

  • v0.19.0 — Daily-use corpus hardening gate. scripts/daily-use-corpus.sh runs a real imx binary against generated fixtures for JSON identify/report, representative prefixed transcodes, stable unsupported diagnostics, and identify --json failure JSON. A no-oracle install/package/release confidence gate, not a new format or command surface.
  • v0.18.0 — Machine-readable daily-use surface: imx identify --json and imx report --json, with deterministic output limited to the existing identify metadata plus report status/diagnostic_code.
  • v0.17.0imx self-test, an offline installed-binary smoke check, plus tightened CLI diagnostics and exit-code tests.
  • v0.16.0 — Bounded uncompressed Windows BMP support for 24-bit BGR/RGB and 32-bit BGRA/RGBA rasters.
  • v0.15.0 — Safe imx batch-convert with preflighted, deterministic output names and no overwrite/recurse/glob.
  • v0.14.0imx resize-fit, aspect-preserving nearest-neighbor resize.
  • v0.13.0imx resize, center-sampled nearest-neighbor exact resize.
  • v0.12.0 — Representative generated/in-test intake reliability coverage for already-supported formats.
  • v0.11.0 — Bounded progressive JPEG input for 8-bit grayscale/RGB.
  • v0.10.0 — Read-only JPEG EXIF Orientation handling (values 1-8).
  • v0.9.0.jpg/.jpeg and JPEG: support for 8-bit grayscale/RGB JPEG identify and transcode.
  • v0.8.0 — Static non-interlaced PNG identify/decode/encode for 8/16-bit grayscale, RGB, RGBA, and grayscale-alpha rasters.
  • v0.7.0 — High-depth PPM (maxval up to 65535).
  • v0.6.0 — Exact uppercase format-prefix surface and the first published release checklist.

Per-version contracts are tracked under docs/, including the v0.16.0 BMP, v0.17.0 self-test/diagnostics, v0.18.0 JSON identify/report, and v0.19.0 daily-use corpus contracts, with the earlier resize, JPEG, and PNG contracts alongside them.

About

Standalone Rust image-tool preview with ImageMagick oracle compatibility tests

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors