Montage grid of effect variants
Generates a 3x3 contact sheet from a single input image by applying various effects (grayscale, sepia, invert, etc.) and stitching them into a grid with customizable padding and background color.
INPUT
OUTPUT
JavaScript
// Montage grid of effect variants
// demo_contact_sheet.js
//!OUTPUT: OUTPUT
//!PARAM: COLS:integer=3,min=2,max=100, PADDING:integer=8,min=0,max=200, BG_COLOR:string=#1a1a2e
// demo_contact_sheet.js — montage grid of effect variants
//
// Takes one input image, applies a set of looks, then stitches them into
// a grid using montageH (row) + montageV (combine rows).
// Padding between cells is added with padLeft / padTop on each cell.
// The background color comes from BG_COLOR (CSS hex).
const src = Engine.loadImage(INPUT);
const COLS_N = Math.max(1, Math.round(COLS));
const PAD = Math.max(0, Math.round(PADDING));
const BG = BG_COLOR || '#1a1a2e';
// --- produce the effect variants ---
const cells = [
src.clone(), // 0 original
src.clone().grayscale(), // 1 B&W
src.clone().sepia(), // 2 sepia
src.clone().brightness(1.25).saturation(1.5), // 3 vivid
src.clone().invert(), // 4 negative
src.clone().hue(180).saturation(1.3), // 5 complementary hue
src.clone().posterize(5), // 6 poster
src.clone().threshold(0.5).grayscale(), // 7 binary
src.clone().pixelate(8), // 8 mosaic
];
// add pad between cells (left + top) so there's gutter space
const padded = cells.map(img =>
img.padLeft(PAD, BG).padTop(PAD, BG)
);
// split into rows of COLS_N, then combine rows vertically
const rows = [];
for (let i = 0; i < padded.length; i += COLS_N) {
const row = padded[i].montageH(...padded.slice(i + 1, i + COLS_N));
rows.push(row);
}
const sheet = rows[0].montageV(...rows.slice(1));
// add trailing right + bottom border to close the grid
sheet.padRight(PAD, BG).padBottom(PAD, BG);
sheet.save(OUTPUT);
// Free everything explicitly so the per-iteration RSS in batch /
// live mode stays flat. The FinalizationRegistry would catch these
// on the next GC sweep, but explicit .free() is the convention in
// our reference samples — readers learn the cleanup pattern.
//
// padLeft / padTop / padRight / padBottom are in-place (return
// `this`), so `padded[i]` and `cells[i]` are the same handle — free
// once. montageH / montageV allocate a fresh handle each.
src.free();
for (const c of cells) c.free();
for (const r of rows) r.free();
sheet.free();
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0