Scene Composer (834t)
Stop wiring Conditioning (Set Area) nodes — draw your composition instead
- clip
- scene_data
- latent
- positive
Multi-concept regional prompting in vanilla ComfyUI is a chore. You need an empty latent, a CLIP Text Encode per concept, a Conditioning (Set Mask) per region, all those masks… and the graph grows a new limb every time you add an object to the scene. Scene Composer (834t) collapses that whole tree into a single node with a drawing canvas. Click a palette color to activate a layer, paint where "a beautiful mountain range" goes, pick another color, paint where the lake goes, and the node hands you a ready positive conditioning for your KSampler. No twenty-node area tree in sight.
How it works
The node is two halves glued together. A JavaScript extension renders the canvas widget - layers, brush/line/eraser tools, per-layer prompt, opacity, visibility, background image, plus S and L buttons to save/load a scene as a .json file. Everything you draw gets serialized into the scene_json field and stored inside the workflow itself, so your drawing survives a save, a reload, and even re-uploading. That persistence alone is worth the install.
The Python side does the actual conditioning work. For each visible layer with a non-empty prompt it decodes the mask (the alpha channel of a base64 PNG, scaled by the layer's opacity), resizes it to latent size, and appends a conditioning entry with a mask - the exact mechanism Conditioning (Set Mask) uses under the hood. During sampling, ComfyUI weights each prompt by its mask, so "a serene lake in the foreground" only pushes the pixels you painted for it. The node also builds the empty latent at width/8 × height/8, so you don't need a separate Empty Latent Image.
Two booleans do the heavy lifting. normalize_masks (default true) divides overlapping masks by their total weight so painted-over regions sum to 1 - without it, overlap areas get double-baked and you get concept "burn-in" where two prompts fight over the same pixels. Keep it on. add_base_prompt (default true) joins all layer prompts into one unmasked conditioning, scaled by base_prompt_strength, which anchors overall style so the result feels like one image instead of six stickers taped together.
The inputs that actually matter
You'll touch maybe four things:
width/height- canvas and latent size; steps of 8, so keep them multiples of 8.normalize_masks- leave true unless you're deliberately stacking layers.add_base_prompt+base_prompt_strength- dial strength to ~0.3–0.6 if the base prompt overpowers your layers.clip- your CLIP model, usually from a CLIP Loader.
Ignore scene_data (internal custom type; leave it unconnected) and scene_json (the widget writes it for you). And mask_downscale_factor? It's in the schema and the README, but as of the current code the value is never read - masks always get resized to latent size anyway. It's a no-op. Don't chase it.
Outputs: latent → your KSampler's latent_image, positive → the KSampler's positive. That's the whole wiring. Wire negative however you normally do.
Installing it
ComfyUI Manager: search "Scene Composer". Or the manual way:
cd ComfyUI/custom_nodes
git clone https://github.com/834t/ComfyUI_834t_scene_composer.git
Restart ComfyUI. That's it - no model downloads, no extra pip dependencies. It only uses torch, PIL, and numpy, which ComfyUI already ships.
Where people get burned
The one real complaint I've seen on r/comfyui is LoRAs: a LoRA without a trigger word fires across all your regions, because masks weight the text conditioning, not the LoRA's effect on the UNet. You can't isolate "character A's LoRA" to zone A with this node alone. Trigger-word LoRAs behave; triggerless ones leak everywhere.
Other things to know: it's a young, single-author pack (MIT, and the README credits Gemini 2.5 Pro as a coding partner), so expect rough edges. Masks are embedded as base64 PNGs in scene_json, so elaborate drawings bloat your saved workflow files. And if you get an empty or randomly-looking render, check the console for [SceneComposer] No visible layers found - a hidden layer or empty prompt quietly returns blank conditioning instead of erroring. That's a trap worth knowing about, because nothing visibly breaks.
For one-image regional prompting it's currently the nicest manual option that actually works on modern ComfyUI - the closest thing to a maintained ComfyCouple. Finicky at the edges, genuinely good at the core.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| clip | CLIP | — | |
| width | INT | 51264–4096 | — |
| height | INT | 51264–4096 | — |
| normalize_masks | BOOLEAN | true | — |
| add_base_prompt | BOOLEAN | true | — |
| base_prompt_strength | FLOAT | 1.000–1 | — |
| mask_downscale_factor | COMBO | 8 | 7 options: 1, 2, 4, 8, 16, 32, +1 |
| scene_json | STRING | — | |
| scene_data | B34T_SCENE_COMPOSER | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | — |
| positive | CONDITIONING | — |