{"id":51697905,"url":"https://github.com/newmatik/image-optimizer","last_synced_at":"2026-07-16T07:05:11.584Z","repository":{"id":370160385,"uuid":"1284122362","full_name":"newmatik/image-optimizer","owner":"newmatik","description":null,"archived":false,"fork":false,"pushed_at":"2026-07-08T11:35:04.000Z","size":170,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-08T12:12:45.506Z","etag":null,"topics":["ci","cli","github-action","image-compression","image-optimization","imageoptim","mozjpeg","oxipng","rust","webp"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/newmatik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-29T14:45:01.000Z","updated_at":"2026-07-08T11:35:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/newmatik/image-optimizer","commit_stats":null,"previous_names":["newmatik/image-optimizer"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/newmatik/image-optimizer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newmatik%2Fimage-optimizer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newmatik%2Fimage-optimizer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newmatik%2Fimage-optimizer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newmatik%2Fimage-optimizer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/newmatik","download_url":"https://codeload.github.com/newmatik/image-optimizer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/newmatik%2Fimage-optimizer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35534116,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","cli","github-action","image-compression","image-optimization","imageoptim","mozjpeg","oxipng","rust","webp"],"created_at":"2026-07-16T07:05:10.812Z","updated_at":"2026-07-16T07:05:11.574Z","avatar_url":"https://github.com/newmatik.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# image-optimizer (`imageopt`)\n\nA fast, reliable, **cross-platform** image optimizer in the spirit of\n[ImageOptim](https://imageoptim.com/) — but it runs anywhere (Linux, macOS,\nWindows) as a single self-contained binary, with **no runtime dependencies** and\n**no external tools to install**.\n\nIt strips junk and recompresses images to the smallest valid output, reports how\nmuch space was saved, and is designed to drop straight into **CI/CD pipelines**\n(there's a ready-made GitHub Action).\n\n```\nFILE          FORMAT  ORIGINAL   NEW        SAVED    STATUS\nhero.png      png     625.7 KB   822 B      99.9%    optimized\nphoto.jpg     jpeg    14.7 KB    9.6 KB     34.6%    optimized\nicon.svg      svg     383 B      327 B      14.6%    optimized\nimage.webp    webp    1.5 KB     694 B      53.6%    optimized\nstatic.gif    gif     1.9 KB     1.7 KB     7.7%     optimized\n─────────────────────────────────────────────────────────────\nTOTAL  5 optimized   644.0 KB → 13.1 KB  (-98.0%, saved 630.9 KB)\n```\n\n## Why\n\n* **One binary, everywhere.** All codecs (mozjpeg, oxipng, libimagequant,\n  libwebp) are compiled in. Nothing to `apt install` at runtime, no Node, no\n  Python, no shelling out.\n* **Never corrupts or enlarges a file.** Every re-encode is validated by\n  re-decoding it before it's written, the smallest valid result wins, and files\n  are written atomically (temp file → fsync → rename). If nothing smaller can be\n  made, the original is left exactly as-is.\n* **Lossless by default**, opt-in lossy (`--lossy`) for much smaller files.\n* **Parallel.** Files are optimized across all CPU cores.\n\n## Supported formats\n\n| Format | Lossless (default) | Lossy (`--lossy`) | Engine |\n|--------|:---:|:---:|--------|\n| JPEG | ✅ jpegtran-style coefficient re-optimization (progressive, optimized Huffman) | ✅ re-encode at quality | mozjpeg |\n| PNG  | ✅ IDAT recompress + reductions (+Zopfli at `--png-level 6`) | ✅ palette quantization | oxipng + libimagequant |\n| WebP | ✅ lossless re-encode | ✅ re-encode at quality | libwebp |\n| SVG  | ✅ normalize + minify (text preserved) | ✅ reduced coordinate precision | usvg/resvg |\n| GIF  | ✅ static GIFs re-encoded losslessly | — | gif (pure Rust) |\n\nNotes:\n* **JPEG lossless is truly lossless** — the DCT coefficients are re-written, so\n  pixels are bit-for-bit identical.\n* **Animated GIFs are left untouched** (skipped). Robust animated optimization\n  needs gifsicle, which is not safe to call from the parallel engine; this is a\n  planned enhancement.\n* **SVGs using SMIL animation, `\u003cscript\u003e`, event handlers (`on*=`), CSS\n  animations, `\u003cforeignObject\u003e`, or external/`data:` `\u003cuse\u003e` references are left\n  untouched** so nothing is ever silently dropped. Optimization normalizes and\n  reserializes the SVG (visually lossless, not byte-for-byte).\n* AVIF files are detected but left untouched until an AVIF optimizer is added.\n\n## Install\n\n### Prebuilt binaries\n\nDownload the archive for your platform from the\n[Releases](https://github.com/newmatik/image-optimizer/releases) page, extract,\nand put `imageopt` on your `PATH`. Targets: macOS (arm64, x64), Linux (x64,\narm64), Windows (x64).\n\n### From source\n\nRequires a Rust toolchain plus a C toolchain with **nasm** and **cmake** (needed\nto build mozjpeg/libwebp):\n\n```bash\n# macOS:    brew install nasm cmake\n# Ubuntu:   sudo apt-get install -y nasm cmake\n# Windows:  choco install nasm cmake   (use the MSVC toolchain)\n\ncargo install --path crates/cli   # or: cargo build --release\n```\n\n## Usage\n\n```bash\nimageopt [PATHS...] [OPTIONS]\n```\n\n`PATHS` can be files, directories, or glob patterns.\n\n```bash\nimageopt logo.png photo.jpg          # optimize specific files in place\nimageopt assets/                     # every image in a directory\nimageopt -r assets/                  # …and all subdirectories\nimageopt \"src/**/*.{png,jpg}\"        # glob (quote so the tool expands it)\nimageopt --dry-run assets/           # preview savings, change nothing\nimageopt --lossy -q 75 photos/       # lossy, quality 75\nimageopt --backup logo.png           # keep logo.png.orig\nimageopt --json assets/ \u003e report.json\n```\n\n### Options\n\n| Flag | Description |\n|------|-------------|\n| `-r, --recursive` | Recurse into subdirectories. |\n| `--lossy` | Allow lossy recompression. |\n| `-q, --quality \u003c1-100\u003e` | Quality for lossy encoders (implies `--lossy`). |\n| `--png-level \u003c0-6\u003e` | oxipng effort; 6 enables Zopfli (slowest). Default 3. |\n| `--min-savings \u003cPERCENT\u003e` | Only rewrite a file if it shrinks by at least this much. Default 0 (lossless) / 10 (`--lossy`); see the idempotency note. |\n| `--strip \u003call\\|color\\|none\u003e` | Metadata: strip all, keep ICC color profile, or keep everything. Default: keep the color profile — but with `--lossy` the default becomes `all` (see note). |\n| `--dry-run` | Report what would change without modifying files. |\n| `--backup` | Copy each original to `\u003cname\u003e.orig` before overwriting. |\n| `--check` | CI gate: write nothing; exit non-zero if any file could be optimized. |\n| `--json` | Machine-readable JSON output. |\n| `-j, --jobs \u003cN\u003e` | Parallel workers (default: CPU cores). |\n| `--max-in-flight-mb \u003cMB\u003e` | Cap the combined size of files processed at once, to bound memory on large batches. Default: unbounded. |\n| `--keep-larger` | Keep a re-encode even if larger than the original. |\n| `--quiet` | Only print the final summary. |\n\nBy default `imageopt` **optimizes files in place** (writes are atomic). Use\n`--dry-run` to preview or `--backup` to keep originals.\n\n\u003e **Lossy and metadata:** lossy re-encoders (JPEG/PNG/WebP) rebuild the image\n\u003e from pixels and cannot preserve an embedded ICC profile, so `--lossy` defaults\n\u003e to stripping all metadata. If you pass `--strip color`/`--strip none` together\n\u003e with `--lossy`, the metadata policy is honored and those files fall back to\n\u003e lossless optimization (the lossy candidate is skipped). SVG `--lossy` only\n\u003e reduces coordinate precision and is unaffected.\n\n### Idempotency (safe to re-run)\n\n**Lossless optimization is deterministic and idempotent**: the transforms are\nexact, and a candidate is only written if it's *strictly smaller* than the\ncurrent file, so the second run on the same files finds nothing to do (`already\noptimal`) and never rewrites or degrades them. Safe to run on every CI push.\n\n**Lossy** re-encoding, by contrast, can shave a sliver on *every* run, which\nwould slowly degrade an image across repeated runs. To prevent that, `--lossy`\ndefaults `--min-savings` to **10%**: after the first pass, re-encoding no longer\nclears the threshold, so the file is left untouched — lossy becomes effectively\nidempotent (it converges after one pass). Pass `--min-savings 0` to squeeze every\nlast byte, but don't do that in a repeated commit-back workflow.\n\nThis matters most in CI. If you run `imageopt` on every push, prefer lossless\nmode or keep the default lossy threshold so JPEG/WebP assets are not recompressed\nagain and again for marginal savings. JPEG lossy mode also checks the source\nquantization table and skips destructive re-encoding when the file already\nappears to be at or below the requested quality.\n\n### Exit codes\n\n* `0` — success.\n* `1` — with `--check`, at least one file could be optimized (or failed).\n* `2` — no matching input files.\n\n### JSON output\n\n`--json` emits a stable top-level `summary` plus per-file `results`, intended\nfor CI systems and dashboards:\n\n```json\n{\n  \"summary\": {\n    \"total\": 1,\n    \"optimized\": 1,\n    \"already_optimal\": 0,\n    \"skipped\": 0,\n    \"failed\": 0,\n    \"original_size\": 1024,\n    \"optimized_size\": 768,\n    \"saved_bytes\": 256,\n    \"saved_percent\": 25.0,\n    \"elapsed_ms\": 12,\n    \"formats\": { \"png\": 1 }\n  },\n  \"results\": [\n    {\n      \"file\": \"assets/logo.png\",\n      \"format\": \"png\",\n      \"status\": \"optimized\",\n      \"error\": null,\n      \"original_size\": 1024,\n      \"optimized_size\": 768,\n      \"saved_bytes\": 256,\n      \"saved_percent\": 25.0,\n      \"elapsed_ms\": 12\n    }\n  ]\n}\n```\n\n`status` is one of `optimized`, `already_optimal`, `skipped`, or `failed`.\nSkipped files are left untouched and do not fail `--check`; failed files do.\n\n## Use in GitHub Actions\n\nThis repo ships a composite action (Linux, macOS, and Windows runners).\n\n**Optimize images and commit the result:**\n\n```yaml\n- uses: actions/checkout@v4\n- uses: newmatik/image-optimizer@v1\n  with:\n    paths: \"src/assets\"\n    recursive: \"true\"\n- run: |\n    git config user.name  \"github-actions[bot]\"\n    git config user.email \"github-actions[bot]@users.noreply.github.com\"\n    git add -A\n    git diff --cached --quiet || git commit -m \"chore: optimize images\"\n    git push\n```\n\n**Fail a PR if images aren't optimized (a lint gate):**\n\n```yaml\n- uses: actions/checkout@v4\n- uses: newmatik/image-optimizer@v1\n  with:\n    paths: \"src/assets\"\n    recursive: \"true\"\n    check: \"true\"\n```\n\nAction inputs: `paths` (required), `lossy`, `quality`, `min-savings`, `recursive`, `strip`,\n`check`, `dry-run`, `json`, `version`, `extra-args`.\n\nYou can also just download the binary in any workflow and run it directly — see\nthe release assets.\n\n## How it works\n\nThe engine is a small library crate (`imageopt-core`) consumed by the CLI. For\neach file it detects the format by content, asks the matching codec to *propose*\ncandidate encodings, then keeps the **smallest candidate that re-decodes\ncleanly** — and only if it's smaller than the original. Codec calls (which cross\ninto C libraries) are run on a panic-catching boundary, so a single malformed\nimage is reported as `failed` and never takes the process down or corrupts the\noriginal.\n\nThe library has no async or HTTP dependencies; an HTTP API and a desktop GUI can\nbe added later as additional front-ends without touching the engine.\n\nArchitecture decisions and the improvement roadmap live in\n[`docs/architecture`](docs/architecture/README.md).\n\n## License\n\nGPL-3.0-or-later. This project uses GPL-licensed compression libraries\n(libimagequant) to match ImageOptim's compression quality. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnewmatik%2Fimage-optimizer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnewmatik%2Fimage-optimizer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnewmatik%2Fimage-optimizer/lists"}