The egami command
egami converts, inspects, resizes and edits images from a shell or a build script, with no C# to write. It is the
Egami.Cli package, a .NET tool built on Egami's public API, and reads every format Egami and its packages read: SVG,
WMF, EMF and Egami.Formats.Extended's PCX, SGI, Sun raster, XBM, XPM, WBMP, Farbfeld, JPEG-LS, DDS,
KTX, DPX, Cineon, FITS and camera RAW included. It has three layers:
- Single commands for everyday jobs:
info,convert,resize,thumbnail,crop,rotate,flip,auto-orient,compare,stats,frames,metadata,color,text,overlay,montageandformats. run, which chains any number of steps over each image. Every Egami operation is a step, and the same steps can be saved as a JSON workflow file.- Batches: every command takes many files, globs and folders, and works on several at once.
The command reference lists every option, and steps for run every step. egami mcp serves the same
commands to an AI assistant: see egami for AI assistants.
egami info photo.jpg
egami convert in.png -o out.webp --quality 80
egami resize in.jpg -o out.jpg --size 800x600
egami thumbnail "photos/*.jpg" -o thumbs/{name}.webp --size 256
egami run in.jpg auto-orient + resize 1200x + sharpen 0.6 + save out.webp --quality 82
cat in.png | egami convert - -o - --format qoi > out.qoi
Installing
Egami.Cli is a .NET tool, so it needs the .NET 10 runtime:
dotnet tool install --global Egami.Cli
dnx Egami.Cli info photo.jpg runs it once without installing. The tool is built free of reflection, with the trimming and
Native AOT analyzers on, so it also publishes as a standalone native program (see Native binaries).
Inputs
Inputs are files, globs or - for standard input. egami expands globs itself, so they work the same in bash, zsh, cmd and
PowerShell: * and ? match within a name, [abc] a set, and ** any depth of folders. Quote them so the shell leaves them
alone. A folder needs --recursive, and then means every image below it. Files come in sorted order, so {index} and the
order of a batch never depend on the file system.
Outputs
-o names where to write:
-o |
Writes |
|---|---|
out.png |
That file. The format comes from the extension, or --format. |
out/ or an existing folder |
out/<name>.<ext>, keeping each input's name. |
web/{dir}/{name}.webp |
A template: {name} (the input's name without extension), {ext} (its extension), {dir} (its folder below the glob's start), {index} (its position from 0, padded with {index:000}), {format} (the output format's extension). {{ and }} write braces. |
- |
Standard output. The format is --format, or the input's. |
With several inputs, -o must be a folder or a template that names each input. An output never replaces its input unless
--in-place is given; --in-place without -o writes each file over itself. Files are written to a temporary name first and
replace the target only when complete.
Formats and writer settings
The output format comes from the extension or --format. The common settings have flags: --quality, --lossless, --effort,
--bit-depth and --strip, which leaves out EXIF, XMP, IPTC, text and resolution but keeps the color profile, so colors
stay right (ImageMagick's -strip drops the profile too). A flag the format does not have is skipped with a warning. Every other writer
setting is --set fmt.key=value, where the keys are the format's WriterOptions properties in kebab case:
egami formats png # lists png.compression-level, png.filter, png.palette.max-colors, ...
egami convert in.tif -o out.png --set png.compression-level=9 --set png.filter=paeth
egami formats lists what egami reads and writes. The keys come straight from the library, so the tool never lags it. HEIC is
not part of the tool, since HEVC is covered by patents. Formats Egami.Formats.Extended has in preview are read and written only
with --preview-formats, which every command takes; they may change or go in any minor release. In this release that is
DICOM: egami info scan.dcm --preview-formats shows a file, and egami convert scan.dcm -o scan.png --preview-formats saves its first frame as a viewer shows it.
Camera RAW files (DNG, CR2, NEF, ARW, RAF and the rest; Camera RAW) are developed as they load, and the
--raw-* options, which every command takes, set how: --raw-output preview (or half-size, mosaic) loads something other
than a full develop, --raw-white-balance auto, --raw-exposure +0.5, --raw-demosaic malvar, --raw-highlights rebuild,
--raw-noise-reduction 30, --raw-tone-curve, --raw-lens-profiles <folders> and the rest map to RawReaderOptions. They
change nothing for other inputs.
egami convert IMG_0001.CR2 -o photo.jpg --raw-exposure +0.5 --raw-tone-curve
egami convert IMG_0002.CR3 -o preview.jpg --raw-output preview
Sizes
resize, thumbnail and the resize step take the size syntax, the common subset of ImageMagick's geometry:
| Size | Means |
|---|---|
800 |
800 wide, height in proportion |
x600 |
600 high, width in proportion |
800x600 |
Fit inside 800 × 600, aspect ratio kept |
50%, 50%x25% |
A percentage of the size |
800x600! |
Exactly 800 × 600, aspect ratio ignored |
800x600^ |
At least 800 × 600, aspect ratio kept (fills the box, nothing cropped) |
800x600> |
Only shrink: images already smaller are left alone |
800x600< |
Only enlarge |
--fit says how a box is filled: contain (inside it, the default), cover (filled and cropped, with --gravity choosing what
stays), fill (stretched) or pad (inside, then padded with --background). --filter picks the resampling kernel (bicubic,
Catmull-Rom, by default, as in the library) and --linear blends in linear light.
Defaults
Every command keeps the library's defaults, except thumbnail, which is the web-image shortcut: it turns the image upright from
its EXIF orientation, shrinks it (never enlarges) to fit --size (256 by default), converts it to sRGB and leaves out
metadata. The other commands do those only when asked: --auto-orient, --to srgb and --strip.
rotate, flip and auto-orient turn a JPEG written as JPEG without decoding it, so no quality is lost, when the angle
allows; --reencode decodes instead. flip has no default direction: give horizontal, vertical or both, since
ImageMagick's -flip and a photo app's flip mirror opposite ways. metadata strip without --keep rewrites a JPEG without
decoding it too; any other metadata edit that would write a lossy format again (JPEG, lossy WebP, AVIF, JPEG XL) asks for
--reencode.
Chaining steps with run
egami run in.jpg auto-orient + resize 1200x + sharpen 0.6 + save out.webp --quality 82
egami run in.jpg save full.png + resize 256x256 + save thumb.webp
egami run "photos/**/*.jpg" grayscale + contrast 1.2 -o gray/{dir}/{name}.jpg --jobs 8
Steps are separated by a bare +, which no shell treats specially. Each step's options follow its name: positional values fill
the parameters in order, --name value sets one by name. save writes the image as it is at that point, and may come more than
once; a plan without a save writes to -o. Writer flags outside a save apply to every save. --dry-run prints the steps and
the files each input would write.
Every single command is turned into the same kind of plan, so egami resize --size 800x and egami run resize 800x give the
same bytes. A plan of steps that can stream (crop, resize and the per-pixel color steps) runs through Raster.Transcode, a
band of rows at a time; anything else loads the image, runs the steps and saves. When a plan starts with a resize, the image
is decoded at no less than one and a half times the result (ReaderOptions.MinimumSize), so a large JPEG is decoded at a half,
a quarter or an eighth of its size and the resize filter still has real work to do. Either way the result is what the same
RasterOps chain with the same reader options gives in C#.
Workflow files
A workflow file holds the steps of run as JSON, one object per step with the step's options as properties. It is checked
against the published schema:
{
"egami": 1,
"steps": [
{ "op": "auto-orient" },
{ "op": "resize", "size": "1200x", "filter": "lanczos3" },
{ "op": "sharpen", "sigma": 0.6 },
{ "op": "save", "format": "webp", "quality": 82, "set": { "webp.effort": 5 } }
]
}
egami run in.jpg resize 1200x + sharpen 0.6 --save-workflow web.json --dry-run # write the file from a command line
egami run "photos/*.jpg" --workflow web.json -o web/{name}.webp
A save in a workflow may give a path; one without writes to -o. Branching and conditions stay out: a script is the right
tool for logic.
Batches
Every command takes many inputs. --jobs sets how many files run at once (one per processor by default); each image is then
worked on by one core, and since Egami gives the same bytes at any core count, --jobs never changes an output. A file that
fails is reported and the rest carry on; --fail-fast stops at the first. A summary goes to standard error when anything failed.
Exit codes, messages and limits
| Code | Means |
|---|---|
| 0 | Everything worked |
| 1 | A file failed (missing, corrupt, unwritable), or compare found a difference above --threshold |
| 2 | The command line is wrong: an unknown command, option or value |
| 3 | An input exceeded a safety limit |
Messages and Egami's own diagnostics go to standard error: warnings by default, -v adds information, -vv debugging detail,
and -q keeps only errors. Results (info, stats, compare) go to standard output, so they can be piped.
The library's safety limits apply to every input. --limit raises or lowers them, and the EGAMI_LIMITS environment variable
sets them for a service:
egami convert big.tif -o big.png --limit pixels=400M,memory=4GB
EGAMI_LIMITS=pixels=20M,frames=100 egami thumbnail uploads/* -o thumbs/
Names: pixels (a count such as 50M, or WxH), width, height, frames, memory and metadata (bytes, such as 512MB).
JSON output
info --json, stats --json, compare --json, metadata show --json and formats --json write JSON for scripts. info's
has a version, "schema": 1, which changes only when a field changes meaning or goes away; new fields may be added:
{
"schema": 1,
"files": [
{
"path": "photo.jpg", "format": "JPEG", "mimeType": "image/jpeg", "width": 4032, "height": 3024,
"frames": 1, "images": 1, "loopCount": null, "orientation": "rotate90",
"pixels": { "channels": "rgb", "channelCount": 3, "bitsPerChannel": 8, "channelType": "unorm8", "alpha": "none" },
"hasMetadata": true, "estimatedBytes": 36578304, "streams": true
}
]
}
With --all, each file also has metadata: the blocks it holds (icc, exif, xmp, iptc, text, gain-map), the color
profile's name and size, resolution, gamma, EXIF's camera, date, orientation and location, the XMP size, IPTC datasets and
text entries. info never decodes pixels; --all reads the metadata, which may decode a pixel.
Text in images
ocr reads the text of images through an OCR program you install, such as Tesseract, run once per image as
CommandOcrEngine runs it (OCR). It writes plain text to standard output by default, or text, hOCR or
ALTO files by -o's extension (.txt, .hocr, .xml) or --as. egami mcp does not offer it, since it runs the program
the call names.
egami ocr scan.png --engine-command "tesseract {image} {output} -l {langs} --dpi {dpi} hocr" --lang eng
egami ocr "scans/*.png" --engine-command "tesseract {image} {output} -l {langs} hocr" --lang eng+nor -o "text/{name}.hocr"
Other tools
A command egami does not know runs the program egami-<name> from the PATH with the rest of the arguments, as git does, so
egami video info clip.webm starts egami-video once Egami.Video's tool is installed. Egap (documents) has its own egap tool
with the same conventions.
Native binaries
Egami.Cli publishes as a single native program that needs no .NET runtime. It answers egami info in about 14 ms, where the
dotnet tool takes about 140 ms, most of it starting the runtime:
dotnet publish src/Egami.Cli -c Release -r linux-x64 -p:EgamiNativeAot=true
The cli-native workflow builds and checks these for win-x64, linux-x64 and osx-arm64 on every change to the tool, and
keeps them as the run's artifacts. Each release puts them on the releases page.