Image Collage (Canvas mini)
Lay out the grid on the node, get one image back
- image
- mask
Most ComfyUI utility nodes ask you to build a pipeline. This one asks you to stop wiring: drop images onto the node's own canvas, nudge them with the mouse, run the workflow. Out come one composited image and an optional mask.
Reach for it for a contact sheet of four seeds, a storyboard panel, a reference sheet you're about to hand to an edit model, or a plain resize-and-pad job. It's the deterministic pixel layer - milliseconds, no sampler, no seed lottery.
How it works
The node looks input-less because it is. There are no image sockets; images go in by drag-and-drop (or Load image, or Ctrl+V on the canvas) and are stored in input/imagecanvas_mini/. What the backend actually receives is one hidden STRING input, canvas_state - JSON describing the grid, output size, background colour, and every image's position, size, angle and stacking order; the frontend JS widget writes it, and you never touch it.
The backend replays that state with Pillow: each image Lanczos-resampled into its slot, rotated, clipped to its cell; then the border, then the optional overlay layer. The render mirrors the on-node canvas on purpose, so what you arranged is what you get. The fingerprint includes each file's mtime, so editing an image on disk and re-running does pick up the change.
Zero Python dependencies - dependencies = [] in its pyproject; Pillow, numpy and torch are already there. It's written against ComfyUI's newer backend API (io.ComfyNode / comfy_entrypoint), so finding no NODE_CLASS_MAPPINGS in the source means new, not broken.
Inputs and outputs that matter
canvas_state(required, STRING) - the serialized canvas, hidden by design. Don't type into it.image(IMAGE) - the composite. Wire it toPreview Image,Save Image, a VAE Encode, an upscaler, whatever wants one image.mask(MASK) - what you painted with theMaskbutton: 1 where painted, all zeros if you never touched it. Rasterized at output resolution, drawn at 2× and downsampled, so edges are smooth instead of stair-stepped. Brush widths are in output pixels. That 1-means-regenerate convention is what inpaint nodes expect, so it drops straight into an inpaint graph - keep denoise low if you only want the painted region to move.
This isn't an output node, so nothing renders if you leave the outputs dangling - get silence after a run and that's usually why.
The toolbar, briefly
Grid at 1×1 is a free canvas: as many images as you like, overlapping, reorderable with Front/Back, and the output frame moves and resizes independently of the images (Adjust frame, or hold Ctrl). Any other grid - up to 8×8 - is one image per cell, each clipped to its cell and sized to fill it. Size (16–8192), Aspect (fixed ratios or a custom 5:4 / 1.85), BG and Border shape the output. Border colour auto-picks black or white to contrast your background, and its thickness scales with output size.
The paint buttons are more useful than they sound. Paint draws on a selected image and saves the result as a new file replacing the cell's content; with an empty cell selected you start from a blank sheet. Paint all draws over the whole collage on a separate layer, leaving the images underneath movable - how you annotate a comparison sheet without flattening it. Resize the canvas and images keep their zoom relative to a fitted cell, while mask strokes scale around the centre: small resizes are fine, a big aspect jump will smear a painted mask. Settle the shape first.
Install
Manager: search ImageCanvas mini (publisher id-fa). Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/id-fa/ComfyUI-ImageCanvas-mini
Restart ComfyUI. No pip step, no models to fetch. If the node loads but the canvas is blank, hard-refresh the browser - the widget is custom JS and a stale cached bundle shows you an empty node.
Where people get burned
Moving a workflow to another machine. The state stores paths into input/imagecanvas_mini/, so copy that folder too or the node fails validation with "Image not found in input folder". Same failure mode as LoadImage in any shared workflow.
Rotating inside a grid. Items are clipped to their cell, so a tilted image in a 2×2 loses its corners. Rotate on a 1×1 free canvas instead, where the clip region is the whole output.
Frontend updates. ComfyUI's Nodes 2.0 rewrite swapped LiteGraph canvas rendering for a Vue-based one, and third-party node UIs were the casualty. If the controls render oddly after an update, switch back to the legacy canvas before declaring the pack dead.
Expecting a retoucher. There isn't one, and that's deliberate - the author's take is that fine retouching belongs to AI passes. No layers, no clone stamp. Use it for layout, padding and masking, then send the result onward.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| canvas_state | STRING | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |