ImagesGridByRows
Same Grid Idea, but You Pick the Row Count Instead of Columns
- images
- annotation
- IMAGE
ImagesGridByRows is the mirror image of ImagesGridByColumns from the same pack: same idea, different knob. Instead of saying "put N images per row and let the rows fall where they may," you say "I want exactly N rows" and it computes how many columns that implies. If that distinction doesn't sound like it matters much, you're right most of the time - but it does change how you think about a comparison grid.
Why you'd reach for it
Think about what you're actually comparing. If you're A/B-ing three checkpoints at four seeds each, that's a 3-wide, 4-tall grid - max_columns = 3 is the natural phrasing, and ImagesGridByColumns nails it. But if you're scanning a seed sweep and want one seed per row so you can read down a column, you want max_rows. The node fills left-to-right, top-to-bottom, same as its sibling, so "one seed per row" means you set max_rows to your seed count. It's a preference thing, but for row-oriented comparisons it's the one you'll reach for.
How it works
The mechanism is identical to ImagesGridByColumns under the hood: it takes your IMAGE batch, converts each frame to PIL, pastes them on a white canvas, and converts the finished grid back to a single IMAGE. The only difference is the math. Given max_rows, it derives columns as ceil(count / max_rows) - 9 images at 3 rows gives you 3 columns, 10 images at 3 rows gives you 4. As with the sibling, tile size comes from the first image in the batch, so every image must be the same resolution or the layout goes sideways.
The inputs that matter
images(IMAGE) - your batch, straight out of VAEDecode, an ImageCombine/LatentCombine merge, or an Efficiency XY Plot.max_rows(INT, default 1) - how many rows of tiles you want. This is the one you actually set.gap(INT, default 0) - pixels of white space between tiles. A small gap makes dense grids readable.annotation(GRID_ANNOTATION, optional) - feed a GridAnnotation output to label each row and column. Given the row-oriented use case, this is where labels earn their keep: you can literally write the seed or checkpoint into each row label.
Output is one IMAGE - the assembled grid - for PreviewImage or SaveImage.
Installing the pack
Same pack as ImagesGridByColumns: ImagesGrid, repo LEv145/images-grid-comfy-plugin. Install it once and both grid nodes (and the rest of the pack) show up together. ComfyUI Manager: search "ImagesGrid". Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/LEv145/images-grid-comfy-plugin ImagesGrid
then restart ComfyUI. No extra dependencies, no model files, no keys - it uses Pillow and torch, both already in ComfyUI.
Where people get burned
Everything from the sibling node's troubleshooting applies here verbatim: uniform resolutions or bust, no downscaling (big batches make genuinely huge grids), and an annotation with both text fields empty throws an error. The one gotcha unique to this orientation is the surprise at how many columns you get. If you set max_rows = 1 on a 20-image batch, you've just made a 20-wide strip and will be scrolling sideways forever. With a row-based layout, thinking about columns as the derived value takes a second to internalize. Community feedback on the pack overall is that the simple grid is solid but the big XY-plot workflows built around these nodes involve a lot of duplicated samplers - worth keeping your use of it scoped to "show me this batch in a grid."
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| gap | INT | 0 | — |
| max_rows | INT | 1 | — |
| annotationopt | GRID_ANNOTATION | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |