Combine 2+ images into one composite from the command line — horizontal or vertical strips. Single binary, no runtime dependencies.
Give it two (or more) images; you get back one image with them side by side, heights normalized so the top and bottom edges line up:
examples/red.png (240×160) |
examples/blue.png (120×80) |
|---|---|
![]() |
![]() |
stitch examples/red.png examples/blue.png -o examples/montage.pngThe result is a single 240×80 PNG: the taller red image was downscaled to
120×80 to match the smallest input height (the default --fit min, which
never upscales; --fit 160 would have upscaled the blue one instead). Add
--vertical to stack instead, --gap/--bg for spacing and background,
--cols 2 for grids — full option list below.
cargo install --path .# Side by side, heights auto-normalized (default: downscale to the smallest)
stitch a.png b.png c.png -o out.png
# Stack vertically, 10px gap, white background, JPEG output
stitch *.jpg -o out.jpg --vertical --gap 10 --bg white
# Normalize all tile heights to exactly 480px
stitch a.png b.png -o out.png --fit 480
# 2x2 grid with gaps on a white background
stitch *.png -o grid.png --cols 2 --gap 8 --bg white
# Fixed 1200x630 canvas, every tile fills its cell (crop overflow)
stitch a.jpg b.jpg c.jpg -o banner.jpg --width 1200 --height 630 --mode cover
# Contact sheet: rounded corners, borders, labels
stitch *.jpg -o wall.png --cols 4 --radius 12 --border 2 --label "a,b,c,d"
# Compose in a pipe
cat cover.jpg | stitch - hero.jpg -o - --format jpg > banner.jpg| Option | Default | Meaning |
|---|---|---|
-o, --output PATH |
required | output file; format from extension (png, jpg, webp); - writes stdout (needs --format) |
--format FMT |
— | png/jpg/webp; overrides the extension, required for -o - |
-d, --direction |
horizontal |
horizontal/h or vertical/v |
-g, --gap N |
0 |
gap between images: single value or WxH (e.g. 4x8) |
-m, --margin N |
0 |
outer margin in px |
-b, --bg COLOR |
transparent |
named color or hex (#rgb, #rgba, #rrggbb, #rrggbbaa); JPEG output flattens transparent onto white |
--fit FIT |
min |
tile size along the cross axis: min (never upscales), max, first, or exact px (e.g. 480); in grids, normalizes tile heights (ignored by --mode none) |
--grid RxC |
— | grid layout like 2x3; unfilled cells stay empty |
--cols N / --rows N |
— | grid with the other axis flowing automatically |
--align A |
center |
tile position in grid cells (start/center/end); also the crop anchor for --mode cover |
--mode M |
contain |
how images fill grid cells: contain (fit fully), cover (fill and crop), none (no cell-based scaling) |
--width/--height PX |
— | fixed canvas size; without grid flags, the strip becomes a single-row/column grid |
--quality N |
90 |
JPEG quality, 1–100 |
--border N |
0 |
border thickness in px, drawn over tile edges |
--border-color C |
white |
border color (syntax as --bg) |
--radius N |
0 |
rounded-corner radius; keep alpha with PNG/WebP output |
--label TEXTS |
— | comma-separated caption per tile (final, sorted order) |
--label-size N |
24 |
label font size in px |
--label-color C |
white |
label color |
--font PATH |
system font | font file for labels (.ttf/.ttc/.otf) |
--sort KEY |
argument order | name, size, or time |
--shuffle |
off | randomize input order |
-v, --verbose |
off | print the computed layout to stderr |
Notes:
- JPEGs are auto-rotated per their EXIF orientation.
- Transparent PNGs are alpha-blended over
--bg; PNG output keeps alpha. - Grids flow row-major by default;
--verticalswitches to column-major (down first, then across). - Grid cells are the bounding box of the (height-normalized) images, or
equal divisions of a fixed
--width/--heightcanvas. - Labels use a system font when
--fontis not given (macOS: Helvetica, Linux: DejaVu/Liberation). Label strips are reserved space under each tile (strips) or at the bottom of each grid cell. -as an input path reads one image from stdin.- HEIC input is not supported yet (convert first, e.g.
sips -s format jpeg).
MIT — see LICENSE. The bundled test font (Silkscreen,
tests/fixtures/fonts/) is separately licensed under the
SIL Open Font License 1.1; it is not
compiled into the binary.
cargo test # unit + CLI smoke + golden-image tests
cargo run -- a.png b.png -o out.png --verbosetests/golden.rs renders fixed gradient inputs and compares pixel-exact
against references in tests/fixtures/goldens/ (tolerance: 2 per channel).
Label goldens bundle the Silkscreen font (SIL OFL, tests/fixtures/fonts/)
so text renders identically on every platform. After an intentional
rendering change, regenerate and commit the references:
UPDATE_GOLDENS=1 cargo test --test golden

