Create Bounding Boxes
The box-drawing node that works without Node 2.0
- editor_state
- background
- bboxes
- last_incoming
- preview
- bboxes
- elements
What this actually is (and why it exists)
ComfyUI added a built-in Create Bounding Boxes node for drawing layout boxes on a canvas. Nice idea, annoying catch: its editor is a Vue widget, so it only renders with the new Node 2.0 frontend on. On the classic LiteGraph renderer most of us still run, you get nothing usable.
This pack is a straight port of that node - same backend logic, same outputs - with the editor re-implemented as a plain DOM widget under web/bounding_boxes.js. So you draw boxes exactly like the official node, without migrating your whole ComfyUI to a beta renderer. The credit line says it plainly: logic adapted from ComfyUI and ComfyUI_frontend, both GPL-3.0, so this is GPL-3.0 too.
Why you'd want boxes at all: this is the layout-control end of regional prompting. The old answer - mask the cross-attention per region - leaks, produces collage seams, and needs re-implementing on every architecture change. Putting boxes in the prompt instead is the newer, stabler route. Ideogram 4 made it normal: boxes are part of the JSON caption schema it was trained on, [y_min, x_min, y_max, x_max], may overlap and nest, and each can carry its own hex palette. Design-tool control instead of hoping a prompt lands.
How it works, mechanically
The canvas is a custom widget on editor_state. Boxes are stored as pixel coordinates against your width/height grid. On execute, the Python side converts each box to fractions of the canvas, builds the outputs from that, and renders a preview with PIL: a numbered tag per box (01, 02…), up to five palette swatches as a colour bar, your description text wrapped inside the rectangle. A background image is resized to a 1024 long edge and dimmed to 25% brightness so the overlay reads.
Two output shapes to know before you wire anything:
bboxescomes out as a frame of pixel boxes:x,y,width,height. That's what typedBOUNDING_BOXconsumers expect.elementscomes out on a 0–1000 normalised grid as[ymin, xmin, ymax, xmax], withtype(objortext),textfor text elements,desc, and up to five uppercase hex colours incolor_palette. That's the Ideogram-style element shape, and the README points it straight at MOSS-Image Layout Prompt.
The inputs and outputs that matter
You set three required things. width and height default to 1024 and step in 16s - match them to the model you're prompting (Ideogram 4 wants multiples of 16 between 256 and 2048), since every box you draw gets scaled into that pixel grid. editor_state isn't a value you type; it's the canvas itself.
Optional background takes an IMAGE, so you can trace boxes over an existing render. Optional bboxes takes BOUNDING_BOX, ARRAY, or a STRING of JSON, and eats either pixel boxes or element dicts with a bbox, converting between them for you. That's the input that makes this composable: round-trip boxes out of a detector or an LLM, drop them in, nudge them by hand.
last_incoming is internal bookkeeping. Don't wire it.
Outputs: preview (IMAGE), bboxes (BOUNDING_BOX), elements (ARRAY). The node is an output node, so the preview also shows up right on the canvas. Wire preview to a Save Image node if you want the annotated layout as a file, elements to a prompt-builder or MOSS layout node, bboxes to anything that crops, pastes, or conditions on a rectangle.
Install
No pip dependencies - pyproject.toml declares dependencies = [], and there's no requirements.txt, so nothing to break in your env. Search 4A Bounding Boxes in ComfyUI Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/tsukino4a/ComfyUI-4A-BoundingBoxes
Restart ComfyUI, then hard-refresh the browser (Ctrl+Shift+R / Cmd+Shift+R). The node's whole UI is JavaScript, and a cached frontend bundle is exactly how you end up with a node that has no canvas. It lands under the 4A-BoundingBoxes category.
Where people get burned
The precedence rule is the real trap, and the tooltip states it: a new upstream bboxes value re-initialises the canvas, and your on-canvas edits win until that upstream value changes again. So if something upstream is re-emitting boxes each run, your hand-tuning gets silently overwritten mid-session. Feed boxes in, edit, then disconnect the input if you care about the edits.
Draw in paint order - background element first, foreground last. The author put that in the tooltip for a reason, and it's the difference between an element list that reads sensibly and one that fights the model.
Empty canvas, empty bboxes list - check the preview before you blame the next node. A malformed JSON string on the bboxes input raises out loud instead of guessing, which is the right call.
One honest caveat on the pack: it's one author, small, and a fairly literal port. That's fine - you're relying on upstream ComfyUI's behaviour, not a novel invention - but don't expect a big issue tracker if the Vue and classic paths ever drift apart.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| width | INT | 102464–16384 | Width of the canvas and the pixel grid for the bounding boxes. |
| height | INT | 102464–16384 | Height of the canvas and the pixel grid for the bounding boxes. |
| editor_state | BBOX4A_EDITOR | Draw bounding boxes and set each box type, text, description, color palette. Start with background element first and foreground last. | |
| backgroundopt | IMAGE | Optional image used as background in the canvas and preview. | |
| bboxesopt | BOUNDING_BOX,ARRAY,STRING | Bounding boxes, elements, or a JSON string to initialize the canvas. A new upstream value initializes the canvas; edits made on the canvas take priority and are kept until the upstream value changes again. | |
| last_incomingopt | BBOX4A_STATE | Internal state managed by the canvas: the upstream bboxes value that last initialized it. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| preview | IMAGE | — |
| bboxes | BOUNDING_BOX | — |
| elements | ARRAY | — |