ApoGalleria-Flux3
Ideogram 4 drew the boxes. This node hands them to FLUX 3 Klein in the format it reads.
- flux3_prompt
- width
- height
- info
The problem this solves
Ideogram 4 is the odd model that made bounding boxes part of the caption instead of a thing you bolt on afterwards - [y_min, x_min, y_max, x_max] on a 0–1000 grid, per-element palettes, typed text elements. Genuinely useful information, and it's trapped in Ideogram's JSON schema. The Klein line reads the opposite way: one continuous prose caption, plus in layout mode a small JSON table of boxes tacked on the end - exactly what BFL's own prompt upsampler writes.
ApoGalleria-Flux3 is the translator between those two. Feed it the export_json string from ApoGalleria-Ideo4 and you get a Klein-ready prompt plus a width and height chosen so your boxes don't get stretched. No UI, no storage, no model files - one of three passthrough converters in the pack (Flux2 for structured JSON, NL for generic prose, this one for FLUX 3 Image Klein). Since FLUX 3 spent most of 2026 behind early access, expect the ComfyUI-side loaders to still be catching up; this node's output is text and two integers, so it isn't the part that breaks.
How it works
The caption assembles in a fixed order: medium and aesthetics first ("A photograph with a muted, salt-worn look."), then photo/style details, the scene, lighting, one sentence per element, background last, and a closing palette sentence if palettes are on. Nothing is invented - a missing field is omitted, not backfilled.
In layout mode the caption is followed by a table of rows on the 0–1000 grid. Ideo4 already uses [y_min, x_min, y_max, x_max], so boxes pass straight through untouched. In the default cited format each element sentence carries a tag - <obj_1>, <Text_1> - and that tag is what ties a sentence to a rectangle; it's also what BFL's own finished-prompt check looks for. Elements with a missing or malformed box stay in the prose untagged, and info tells you which.
Sizing is less obvious than it looks. megapixels sets the area; each side is then snapped to a multiple of 16 and clamped between 64 and 4096, so 1.0 MP gives 1024×1024 at 1:1 and 1360×768 at 16:9. Render at a ratio other than the one the caption was drawn at and the boxes get remapped about the centre - fit shrinks them into the canvas, fill pushes some off the edge, stretch does what it says.
A real output, 3:2 source, defaults on:
A photograph with a muted, salt-worn look. Photographic details: 35mm documentary,
slight grain. A weathered fisherman mends a net on a harbour wall at dawn. Lighting:
low rim light from the east. <obj_1> shows an older man in a faded blue jumper, hands
working the mesh. <Text_1> shows the text "HARBOUR", a painted sign on the wall. In the
background, trawlers idle in grey water behind him. Overall palette in tones of gray,
khaki, and charcoal. [{"id": "obj_1", "bbox": [120, 300, 780, 620], ...}]
The inputs that matter
ideo4_json is required and is the whole point - wire ApoGalleria-Ideo4's export_json into it. It's declared as a forced input, so there's no text box: you must connect something, and the Ideo4 node has to be in Pass to Output mode before it emits anything worth using.
source_ratio is the ratio the caption was drawn on. Get it wrong and nothing else can save you, because boxes are stored as fractions of the image. If your ratio isn't in the list, pick custom and fill in custom_w / custom_h. render_ratio defaults to same as source, which is right most of the time; if_ratio_differs only does anything once those two disagree.
output_format has three settings, and the default - layout: boxes + citations - is the one BFL trained. boxes, no citations is their fallback shape; prose only drops the table and writes positions in words ("in the upper left").
Then megapixels, plus two booleans I'd leave alone: include_palettes adds colour names and an overall palette sentence; double_quote_text turns 'SMALL' into "SMALL" while leaving apostrophes like Pelé's alone.
Outputs: flux3_prompt into your CLIPTextEncode, width/height into the latent or image-size node, info into a text preview. Read info - it's where the warnings live.
Installing it
ComfyUI Manager, search APO, install, restart. Or do it by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/ApoloniArt/ApoGalleria ComfyUI-ApoGalleria
pip install -r ComfyUI-ApoGalleria/requirements.txt
The dependency list is Pillow and that's it - nothing here can torch your working ComfyUI install. All four nodes land under the ApoGalleria category.
Where people get burned
Leaving source_ratio on 1:1. Nearly every Ideo4 entry is captioned at whatever ratio its reference image had. Say 1:1 for a 3:2 caption and the node faithfully squeezes the boxes. Fix the ratio, not the prompt.
Rendering at a different ratio without reading the warning. The node prepends a line to info saying the boxes were remapped and how. With fill, boxes that fall entirely outside the crop are dropped from the table and listed - the prose still describes them, so you get a sentence pointing at nothing.
Leaving a prompt upsampler in the chain. The node already writes the finished format. Anything downstream that rewrites your caption will mangle the <obj_1> tags and the table, and the model stops respecting the layout.
Blank or weird prompt text. The node doesn't throw on bad JSON - it returns ERROR: … as the prompt string and reports it on info. If your encoder suddenly gets a sentence starting with "ERROR", the caption JSON never parsed. Related: a box written [0.1, 0.1, 0.5, 0.5] (0–1 normalised) is rejected as invalid, so that element goes prose-only.
Very long prompts. The encoder caps at 4096 tokens and the node warns around an estimated 3500. Lots of chatty element descriptions will get you there - trim the prose, not the boxes.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| ideo4_json | STRING | — | |
| output_format | COMBO | layout: boxes + citations (recommended) | 3 options: layout: boxes + citations (recommended), layout: boxes, no citations, prose only (no boxes) |
| source_ratio | COMBO | 1:1 | 12 options: 1:1, 5:4, 4:3, 3:2, 16:9, 21:9, +6 |
| render_ratio | COMBO | same as source | 12 options: same as source, 1:1, 5:4, 4:3, 3:2, 16:9, +6 |
| megapixels | FLOAT | 1.000.1–16 | — |
| include_palettesopt | BOOLEAN | true | — |
| double_quote_textopt | BOOLEAN | true | — |
| if_ratio_differsopt | COMBO | fit (whole source inside, empty margins) | 3 options: fit (whole source inside, empty margins), fill (source covers canvas, edges cropped), stretch (boxes distort) |
| custom_wopt | INT | 10241–100000 | — |
| custom_hopt | INT | 10241–100000 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| flux3_prompt | STRING | — |
| width | INT | — |
| height | INT | — |
| info | STRING | — |