Repository navigation
imop
Optimize and convert images for the web. Automatically selects the best available tool for each format with fallback chains.
imop photo.jpg # Optimize in place
imop photo.jpg out.jpg # Optimize to new file
imop photo.jpg photo.webp # Convert to WebP
imop images/ # Batch optimize in place
imop images/ output/ # Batch to output dir
imop --to webp images/ # Batch convert to WebP
| Command | Description |
|---|---|
optimize, o
|
Re-compress in same format (default) |
convert, c
|
Convert to format of output file |
batch, b
|
Process all images in a directory |
Commands can be omitted — imop infers the operation from arguments (file vs directory, same vs different extension).
| Option | Description |
|---|---|
-q, --quality N |
Quality 0–100 (default: 80) |
--lossy |
Force lossy compression |
--lossless |
Force lossless compression |
--best |
Use slow-but-best compressor (guetzli/zopflipng) |
--to EXT |
Target format for batch conversion |
--suffix TAG |
Append tag to output names |
--no-suffix-on-top |
In batch --to, replace the source extension instead of appending |
--include-variants |
Don't skip pre-generated .webp/.avif/.jxl siblings in batch |
--min-ratio PCT |
Drop output unless at least PCT% smaller; 0 = unconditional (default: 0) |
-j, --jobs N |
Parallel jobs for batch (default: 1, 0 = nproc) |
-n, --dry-run |
Show commands without running |
Supported formats: JPEG, PNG, WebP, AVIF, JXL.
| Format | Mode | Tools (preference order) |
|---|---|---|
| JPEG | lossless | jpegoptim |
| JPEG | lossy |
mozjpeg (cjpeg) > jpegoptim
|
| JPEG | best |
guetzli (very slow, quality ≥ 84) |
| PNG | lossless |
oxipng > optipng
|
| PNG | lossy |
pngquant + oxipng > pngquant + optipng
|
| PNG | best |
zopflipng (very slow) |
| WebP | lossless source | cwebp -lossless |
| WebP | lossy source | cwebp -q |
| AVIF | avifenc |
|
| JXL | cjxl |
imop photo.webp asks webpinfo (installed with cwebp) whether the bitstream is lossy or lossless and re-encodes it the same way: a lossy file at --quality, a lossless file losslessly. Re-encoding a lossy WebP with -lossless is a format change that always grows the file, which is what happened before 1.4.0 and rolled back every time. --lossy and --lossless override the detection.
Converting a JPEG source to JXL defaults to cjxl's bit-exact lossless transcode — the JPEG's DCT coefficients are repacked, not re-encoded, typically ~20% smaller and byte-reversible with djxl --jpeg_to_pixels=0. --quality is not consulted on this path; pass --lossy to opt into a real re-encode at the requested quality. Non-JPEG sources encode lossily at --quality (or losslessly with --lossless), like WebP/AVIF.
The encoders read JPEG and PNG. cwebp also reads WebP and cjxl reads JXL; neither reads the other's format, and avifenc reads nothing but JPEG, PNG, and Y4M. When the target encoder can't read the source, imop decodes it to a PNG beside the source first (photo.avif.tmp.png, removed afterwards): dwebp for WebP, djxl for JXL, and ImageMagick (magick) for AVIF, with avifdec as the fallback. That covers re-optimizing an AVIF in place and every cross-codec conversion (WebP to AVIF, AVIF to JXL, and so on).
ImageMagick is preferred over avifdec for one reason: avifdec copies the AVIF's colour signalling into a PNG cICP chunk, avifenc's default transfer there is "unspecified", and cjxl 0.12 refuses such a PNG (Getting pixel data failed). Without ImageMagick, AVIF to JXL fails with that error and every other pair works.
Re-encoding a lossy source this way is a generation loss, the same as re-optimizing a JPEG; the in-place rollback below still guards against growth.
brew install jpegoptim mozjpeg oxipng pngquant webp libavif jpeg-xl imagemagick
brew install guetzli zopfli # slow-but-best compressors (optional)In batch --to <ext>, output files default to suffix-on-top — the source extension is preserved and the target is appended:
photo.jpg → photo.jpg.webp
icon.png → icon.png.avif
scan.jpg → scan.jpg.jxl
This matches the cxop family (name.html.br) and the lambda variant probe (name + '.webp'). Pass --no-suffix-on-top to revert to extension-replace (photo.webp).
In batch, imop skips files that look like the output of a prior batch pass — .webp/.avif/.jxl files whose .jpg/.jpeg/.png source exists alongside, in either naming form (foo.jpg.webp next to foo.jpg, or foo.webp next to foo.jpg). Re-encoding a .webp as a .webp source typically grows it; skipping them avoids that whole class of regression.
Pass --include-variants to process them anyway.
When optimizing in place, imop always snapshots the source before re-encoding and rolls back if the result grew the file — even at the default --min-ratio 0. This makes imop optimize safe to run on directories that mix already-optimized and unoptimized images: bigger results never overwrite the original.
--min-ratio N (N > 0) keeps the source unless the result is at least N% smaller, both in-place and for separate-output. For separate-output (convert or optimize <in> <out>), --min-ratio 0 keeps the output regardless of size — the source is unaffected, so there's no destructive risk to guard against.
When an encoder or the decode step fails, imop restores an in-place source from its snapshot, removes a partial separate output, prints encoder failed, and exits non-zero; in batch the file counts as failed. Before 1.3.0 a failed encode was reported as 0% saved with exit 0.
For general compression (.gz/.br/.zst sibling variants for HTTP content negotiation), reach for cxop. imop handles raster images (JPEG/PNG/WebP/AVIF/JXL); cxop produces generic compression siblings for anything else worth compressing. Same scaffolding (options.bash, batch + parallel + dry-run), different codecs.
Initial setup
General information
- Shell Environment
- Git Configuration
- Application Notes
- Remote GUI
- Utilities
- Playbash Server Management
Day-to-day operations