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.
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 throughtry_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 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-testThe 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" | shThe 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.gzimx-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 --versionThis 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-testimx-cli is the Cargo package name; the installed binary is named imx.
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-fitvia--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
--stripto 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.
# 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-testExact 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.)
imx ships hand-written shell completion scripts and a checked-in roff manual
page so the CLI is discoverable from your shell and man.
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.fishA 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| 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. |
| 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.
- 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/<32768to 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.
By design, IMX does not (yet) provide:
- Full ImageMagick CLI parsing, the
magick/convert/mogrifycommands, 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.
- 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.
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.
Run the local gates:
./scripts/ci.shRequire ImageMagick oracle differentials:
IMAGEMAGICK_MAGICK=/path/to/magick IMX_REQUIRE_ORACLE=1 ./scripts/ci.shRun the corpus differential report directly:
IMAGEMAGICK_MAGICK=/path/to/magick ./scripts/differential-corpus.shRun coverage-guided fuzz smoke:
IMX_FUZZ_MAX_TOTAL_TIME=5 ./scripts/run-fuzz.shScheduled 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.shCapture the pre-1.0 public API surface before tagging a release candidate:
cargo install cargo-public-api --locked
bash scripts/api-freeze.shGenerate machine-readable benchmark evidence:
IMAGEMAGICK_MAGICK=/path/to/magick ./scripts/bench-release.shCompare 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.shPackage a release archive:
./scripts/package-release.shRelease 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.shVerify 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.shVerify 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.
See COMPATIBILITY.md for the exact behavior contract and PRODUCTION_READINESS.md for current release evidence, known gaps, and the next adoption milestone.
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.shruns a realimxbinary against generated fixtures for JSON identify/report, representative prefixed transcodes, stable unsupported diagnostics, andidentify --jsonfailure 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 --jsonandimx report --json, with deterministic output limited to the existing identify metadata plus reportstatus/diagnostic_code. - v0.17.0 —
imx 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-convertwith preflighted, deterministic output names and no overwrite/recurse/glob. - v0.14.0 —
imx resize-fit, aspect-preserving nearest-neighbor resize. - v0.13.0 —
imx 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/.jpegandJPEG: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 (
maxvalup 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.