A fast, reliable, cross-platform image optimizer in the spirit of ImageOptim — but it runs anywhere (Linux, macOS, Windows) as a single self-contained binary, with no runtime dependencies and no external tools to install.
It strips junk and recompresses images to the smallest valid output, reports how much space was saved, and is designed to drop straight into CI/CD pipelines (there's a ready-made GitHub Action).
FILE FORMAT ORIGINAL NEW SAVED STATUS
hero.png png 625.7 KB 822 B 99.9% optimized
photo.jpg jpeg 14.7 KB 9.6 KB 34.6% optimized
icon.svg svg 383 B 327 B 14.6% optimized
image.webp webp 1.5 KB 694 B 53.6% optimized
static.gif gif 1.9 KB 1.7 KB 7.7% optimized
─────────────────────────────────────────────────────────────
TOTAL 5 optimized 644.0 KB → 13.1 KB (-98.0%, saved 630.9 KB)
- One binary, everywhere. All codecs (mozjpeg, oxipng, libimagequant,
libwebp) are compiled in. Nothing to
apt installat runtime, no Node, no Python, no shelling out. - Never corrupts or enlarges a file. Every re-encode is validated by re-decoding it before it's written, the smallest valid result wins, and files are written atomically (temp file → fsync → rename). If nothing smaller can be made, the original is left exactly as-is.
- Lossless by default, opt-in lossy (
--lossy) for much smaller files. - Parallel. Files are optimized across all CPU cores.
| Format | Lossless (default) | Lossy (--lossy) |
Engine |
|---|---|---|---|
| JPEG | ✅ jpegtran-style coefficient re-optimization (progressive, optimized Huffman) | ✅ re-encode at quality | mozjpeg |
| PNG | ✅ IDAT recompress + reductions (+Zopfli at --png-level 6) |
✅ palette quantization | oxipng + libimagequant |
| WebP | ✅ lossless re-encode | ✅ re-encode at quality | libwebp |
| SVG | ✅ normalize + minify (text preserved) | ✅ reduced coordinate precision | usvg/resvg |
| GIF | ✅ static GIFs re-encoded losslessly | — | gif (pure Rust) |
Notes:
- JPEG lossless is truly lossless — the DCT coefficients are re-written, so pixels are bit-for-bit identical.
- Animated GIFs are left untouched (skipped). Robust animated optimization needs gifsicle, which is not safe to call from the parallel engine; this is a planned enhancement.
- SVGs using SMIL animation,
<script>, event handlers (on*=), CSS animations,<foreignObject>, or external/data:<use>references are left untouched so nothing is ever silently dropped. Optimization normalizes and reserializes the SVG (visually lossless, not byte-for-byte). - AVIF files are detected but left untouched until an AVIF optimizer is added.
Download the archive for your platform from the
Releases page, extract,
and put imageopt on your PATH. Targets: macOS (arm64, x64), Linux (x64,
arm64), Windows (x64).
Requires a Rust toolchain plus a C toolchain with nasm and cmake (needed to build mozjpeg/libwebp):
# macOS: brew install nasm cmake
# Ubuntu: sudo apt-get install -y nasm cmake
# Windows: choco install nasm cmake (use the MSVC toolchain)
cargo install --path crates/cli # or: cargo build --releaseimageopt [PATHS...] [OPTIONS]PATHS can be files, directories, or glob patterns.
imageopt logo.png photo.jpg # optimize specific files in place
imageopt assets/ # every image in a directory
imageopt -r assets/ # …and all subdirectories
imageopt "src/**/*.{png,jpg}" # glob (quote so the tool expands it)
imageopt --dry-run assets/ # preview savings, change nothing
imageopt --lossy -q 75 photos/ # lossy, quality 75
imageopt --backup logo.png # keep logo.png.orig
imageopt --json assets/ > report.json| Flag | Description |
|---|---|
-r, --recursive |
Recurse into subdirectories. |
--lossy |
Allow lossy recompression. |
-q, --quality <1-100> |
Quality for lossy encoders (implies --lossy). |
--png-level <0-6> |
oxipng effort; 6 enables Zopfli (slowest). Default 3. |
--min-savings <PERCENT> |
Only rewrite a file if it shrinks by at least this much. Default 0 (lossless) / 10 (--lossy); see the idempotency note. |
--strip <all|color|none> |
Metadata: strip all, keep ICC color profile, or keep everything. Default: keep the color profile — but with --lossy the default becomes all (see note). |
--dry-run |
Report what would change without modifying files. |
--backup |
Copy each original to <name>.orig before overwriting. |
--check |
CI gate: write nothing; exit non-zero if any file could be optimized. |
--json |
Machine-readable JSON output. |
-j, --jobs <N> |
Parallel workers (default: CPU cores). |
--max-in-flight-mb <MB> |
Cap the combined size of files processed at once, to bound memory on large batches. Default: unbounded. |
--keep-larger |
Keep a re-encode even if larger than the original. |
--quiet |
Only print the final summary. |
By default imageopt optimizes files in place (writes are atomic). Use
--dry-run to preview or --backup to keep originals.
Lossy and metadata: lossy re-encoders (JPEG/PNG/WebP) rebuild the image from pixels and cannot preserve an embedded ICC profile, so
--lossydefaults to stripping all metadata. If you pass--strip color/--strip nonetogether with--lossy, the metadata policy is honored and those files fall back to lossless optimization (the lossy candidate is skipped). SVG--lossyonly reduces coordinate precision and is unaffected.
Lossless optimization is deterministic and idempotent: the transforms are
exact, and a candidate is only written if it's strictly smaller than the
current file, so the second run on the same files finds nothing to do (already optimal) and never rewrites or degrades them. Safe to run on every CI push.
Lossy re-encoding, by contrast, can shave a sliver on every run, which
would slowly degrade an image across repeated runs. To prevent that, --lossy
defaults --min-savings to 10%: after the first pass, re-encoding no longer
clears the threshold, so the file is left untouched — lossy becomes effectively
idempotent (it converges after one pass). Pass --min-savings 0 to squeeze every
last byte, but don't do that in a repeated commit-back workflow.
This matters most in CI. If you run imageopt on every push, prefer lossless
mode or keep the default lossy threshold so JPEG/WebP assets are not recompressed
again and again for marginal savings. JPEG lossy mode also checks the source
quantization table and skips destructive re-encoding when the file already
appears to be at or below the requested quality.
0— success.1— with--check, at least one file could be optimized (or failed).2— no matching input files.
--json emits a stable top-level summary plus per-file results, intended
for CI systems and dashboards:
{
"summary": {
"total": 1,
"optimized": 1,
"already_optimal": 0,
"skipped": 0,
"failed": 0,
"original_size": 1024,
"optimized_size": 768,
"saved_bytes": 256,
"saved_percent": 25.0,
"elapsed_ms": 12,
"formats": { "png": 1 }
},
"results": [
{
"file": "assets/logo.png",
"format": "png",
"status": "optimized",
"error": null,
"original_size": 1024,
"optimized_size": 768,
"saved_bytes": 256,
"saved_percent": 25.0,
"elapsed_ms": 12
}
]
}status is one of optimized, already_optimal, skipped, or failed.
Skipped files are left untouched and do not fail --check; failed files do.
This repo ships a composite action (Linux, macOS, and Windows runners).
Optimize images and commit the result:
- uses: actions/checkout@v4
- uses: newmatik/image-optimizer@v1
with:
paths: "src/assets"
recursive: "true"
- run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git diff --cached --quiet || git commit -m "chore: optimize images"
git pushFail a PR if images aren't optimized (a lint gate):
- uses: actions/checkout@v4
- uses: newmatik/image-optimizer@v1
with:
paths: "src/assets"
recursive: "true"
check: "true"Action inputs: paths (required), lossy, quality, min-savings, recursive, strip,
check, dry-run, json, version, extra-args.
You can also just download the binary in any workflow and run it directly — see the release assets.
The engine is a small library crate (imageopt-core) consumed by the CLI. For
each file it detects the format by content, asks the matching codec to propose
candidate encodings, then keeps the smallest candidate that re-decodes
cleanly — and only if it's smaller than the original. Codec calls (which cross
into C libraries) are run on a panic-catching boundary, so a single malformed
image is reported as failed and never takes the process down or corrupts the
original.
The library has no async or HTTP dependencies; an HTTP API and a desktop GUI can be added later as additional front-ends without touching the engine.
Architecture decisions and the improvement roadmap live in
docs/architecture.
GPL-3.0-or-later. This project uses GPL-licensed compression libraries (libimagequant) to match ImageOptim's compression quality. See LICENSE.