Image Compare (HTML)
The A/B gallery you used to build by hand — as one node
- images
- settings_data
- reference
- images_captioned
- html_path
- url
Every serious workflow eventually hits the same wall: you generated twenty variations of a LoRA, a checkpoint, a cfg sweep, and you need to actually look at them side by side before picking a winner. In core ComfyUI that means Save Image nodes, a file explorer, and a lot of alt-tabbing. Image Compare (HTML) is the node that collapses that whole ritual into one run: it takes your batch and writes an interactive HTML gallery you open in the browser - grid, before/after slider, opacity overlay, synced zoom and pan, even a loupe. It's the best node in Kinburg-Nodes, and honestly the reason to install the pack at all.
It's also the node in the pack that leans hardest on the pack's wider ecosystem. It's designed to sit at the end of an accumulator-driven sweep - wire it the outputs of Get Accumulator (images), (captions), (prompts), (texts) - so a batch of runs becomes a browsable page with each image's caption, full generation prompt, and per-image generation time underneath. The settings_data input (from the pack's Generation Info Filter) renders each run's actual sampler parameters under the image, which is the thing you genuinely need when you're comparing a 12-step recipe against a 30-step one and you've forgotten which was which.
The headline trick is the reference input. Wire in a baseline - the source of an upscale, or the fp16 output when you're checking a quantized model - and the node computes SSIM + PSNR for every image against it and adds a "Similarity" sort to the page. That's a real objective tiebreaker on top of your eyes, and it turns the node from a gallery into an evaluation tool. (No reference? reference_index uses one image in the batch as the baseline instead; -1 turns metrics off.)
Only images is strictly required. The rest of the inputs are sockets, not widgets - wire captions (one per line), prompts (one block per image, separated by a --- line), times, judge_data (a Vision LLM Judge's results, if you want AI verdicts rendered per image). For output: images_captioned is a list of images with captions drawn on (so mixed-size inputs stay separate rather than being forced into one batch), html_path is the file, and url is a clickable served link - open the page straight from the node.
How it's saved
By default it writes a portable folder - <prefix>_<datetime>/ with a light index.html and an images/ subfolder using relative links. Zip it, share it, open it offline, it all works. Flip embed_images on and you get one self-contained .html with every image inlined as base64 - single file, much larger. Both open from the url output. Your review state - hide, star rating, tags, comments - persists in browser localStorage, so you can come back to a sweep the next day and still see which one you rejected.
Installing
It's part of Kinburg-Nodes (ComfyUI Manager → search "Kinburg-Nodes", or git clone https://github.com/Kinburg/Kinburg-Nodes into custom_nodes and restart). Per the README, Image Compare needs nothing extra beyond ComfyUI's bundled deps. The full sweep setup - accumulators, Generation Info - comes with the same pack, so it all slots together.
Where people get tripped up
The auto_collect toggle defaults to on and quietly re-wires every accumulator in your graph right before the workflow queues, so a Set node you just added or muted is picked up without a click. If you ever see images appearing on the page that you didn't think you'd collected, that's the culprit - flip it off and use the manual 🔌 Collect All button. And don't forget the page's visibility toggles: captions, prompts, settings, metrics and judge sections each have their own on-page switch, so a "cluttered" comparison is usually just one toggle away from clean.
Inputs (20)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | Images to compare — a batch OR an image list (e.g. from Get Accumulator (images list)); different sizes are fine. | |
| title | STRING | Image comparison | — |
| columns | INT | 31–12 | Default number of columns in Grid mode |
| overlay_captions | BOOLEAN | true | Draw captions onto the 'images_captioned' output |
| caption_position | COMBO | bottom | 2 options: bottom, top |
| font_size | INT | 00–200 | Caption font size in px; 0 = auto from image width |
| filename_prefix | STRING | compare | — |
| save_captioned_images | BOOLEAN | false | Also save the captioned images as PNG files in the output folder |
| auto_collect | BOOLEAN | true | Re-wire every Get Accumulator in the graph automatically, right before the workflow is queued — so a Set you just added, removed, muted or bypassed is picked up without a click. Off: only the '🔌 Collect All' button collects. (Purely an editor convenience; the backend ignores this value.) |
| captionsopt | STRING | One caption per line, aligned with the image batch (e.g. from Get Accumulator (captions)). Missing lines become empty. | |
| promptsopt | STRING | Full generation prompts, one block per image separated by a '---' line (e.g. from Get Accumulator (prompts)). Shown on the page (toggleable). Multi-line prompts are fine. | |
| timesopt | STRING | Per-image generation time — one entry per line, aligned with the image batch (e.g. Stop Timer's 'elapsed' collected via Get Accumulator (texts) with a newline separator). Shown under each image and used for the grid's 'Time' sort. Strings like '12.34 s', '1m 30s', '890 ms' or '00:01:30' are parsed for sorting. | |
| output_diropt | STRING | Custom save folder (absolute path). Empty = ComfyUI output. A served copy is also written to output so 'Open comparison' keeps working. | |
| embed_imagesopt | BOOLEAN | false | How the comparison is saved. Off (default): a portable FOLDER '<prefix>_<datetime>/' with a light index.html + an images/ subfolder (relative links) — open it offline, zip/share it, or open it in-app. On: a single self-contained .html with every image inlined as base64 (one file, much larger). Both open from the node's URL output. |
| save_prompts_txtopt | BOOLEAN | false | Save each prompt to a .txt file named like its image |
| settings_dataopt | GEN_SETTINGS | Structured per-image settings from Generation Info Filter's 'settings_data' output. Rendered under each image (one '[Class] param: value' line per field; toggleable) and stored by field (EAV) when you 'Save run to report'. | |
| report_dbopt | STRING | SQLite file the page's 'Save run to report' button writes to. Empty = <output>/kinburg/reports.db. The value is shown (and editable) on the page. | |
| referenceopt | IMAGE | Optional baseline to measure similarity against (e.g. the source of an upscale/img2img, or the fp16 output when checking a quantized model). When connected, SSIM + PSNR are computed for every image vs this reference and shown on the page (with a 'Similarity' sort). | |
| reference_indexopt | INT | -1-1–4096 | Used only when no 'reference' image is connected: 0-based index of the image IN THIS BATCH to use as the baseline (every image is measured against it). -1 = off (no metrics). |
| judge_dataopt | STRING | Optional AI verdicts — wire the Vision LLM Judge's 'results_json' here. Each image gets a read-only judge section (stars / tags / comment), separate from your own review, with its own page toggle. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| images_captioned | IMAGE | — |
| html_path | STRING | — |
| url | STRING | — |