Canvas (RMBG)
The compositor hiding inside a background-removal pack
- background_color
- Background
- layer_1
- IMAGE
- MASK
Every cutout workflow ends with the same embarrassing step. You've got a beautiful transparent PNG, and now you need the subject at a decent size, off-centre, on a coloured background, next to a logo - and you're doing it with Image Composite, three ImageScale nodes, and a Transform whose rotation eats your edges. Six nodes to do what Photoshop does in a drag.
This is the pack's answer: a multi-layer, drag-it-around compositing canvas inside ComfyUI. It landed in v3.3.0 in October 2026, it's the only node here that isn't about removing a background, and in a cutout pipeline it's the natural last stop.
How it works
The compositor runs in torch, on the GPU, on a 4-channel RGBA canvas. Each layer goes through the same sequence: optional flips, scale, rotation with expand=True, then a paste at an arbitrary position - clipped to the canvas bounds so a layer hanging off the edge just hangs off the edge. Layers are blended with correct straight-alpha "over" math, which is the part most hand-rolled compositing graphs get wrong; that's how you get a subject with a grey fringe against a dark background. There isn't one here.
Ordering matters and it comes from canvas_state, a plain string widget holding serialized JSON. That JSON is what the front-end writes when you drag things around: each layer's position, scale, rotation, flip, opacity and stacking order. The node then sorts layers by that z-index and draws them in order, scaling coordinates relative to the canvas size recorded in the JSON so a layout survives you changing resolution. You are not meant to hand-edit it - the visual editor is the editor, and the JSON is its save file.
Two behaviours from the source worth knowing up front. Layers bigger than the canvas are auto-fit down to 85% of it so nothing lands half off-screen on first connect; layers that are already small are left at native scale. And canvas dimensions are rounded down to a multiple of 8, which is ComfyUI doing you a favour.
Inputs
Six required widgets, and honestly you'll touch three of them:
fit_background_size- whentrue, canvas dimensions come from theBackgroundimage andcanvas_width/canvas_heightare ignored. Whenfalse, you get the explicit size. Default isfalse, so if you wire in a background and wonder why it's cropped, that's the switch.background-Alphafor a transparent output,Colorto fill withbackground_color(hex, default#222222).canvas_width/canvas_height- 640×360 by default, 8px steps.
Then the two image inputs: Background (optional) and layer_1 (optional). The node is dynamic - it grows additional layer sockets as you start using them, which is why the schema only lists the first. The one quirk: layer sockets only appear once connected, so a fresh node looks like it has fewer inputs than it does.
Outputs
IMAGE is the composite. MASK is the accumulated coverage of your layers - the union of every layer's alpha as composited, not the background's. That's genuinely useful: wire MASK into an inpainting or regional-conditioning path and you're targeting everything you laid down, in the exact place you laid it down.
The node is also an OUTPUT_NODE, so it saves previews to the temp directory and always executes. Whatever it composited is visible whether or not you've got a Preview Image downstream.
Install
Nothing extra. No models, no new dependencies - it ships with the pack and works the moment the pack loads:
cd ComfyUI/custom_nodes
git clone https://github.com/1038lab/ComfyUI-RMBG
./ComfyUI/python_embeded/python -m pip install -r ComfyUI-RMBG/requirements.txt
Or ComfyUI Manager → search ComfyUI-RMBG. The pack is GPL-3.0, which is worth a thought if you're redistributing anything built around it.
Where it bites
Everything is tiny or in the wrong place. Layers over 85% of the canvas get scaled on first connect; layers under it don't. So a 300px logo stays 300px while your 2000px subject shrinks. Set scale per layer in the editor rather than trusting the defaults.
Output has no alpha even though background is Alpha. If you fed a 3-channel image into Background, the node composites in RGB and hands back RGB - there's nothing transparent left to keep. Feed it an RGBA background, or leave Background empty.
Layout jumps after you change resolution. canvas_state positions are stored relative to the canvas size they were authored at and rescaled on load. Large size changes will visibly move things. Re-drag after a big change.
Copy-pasting the node between workflows scrambles it. The JSON travels with the copy but the layers don't unless you copy them too. If a pasted node has one layer floating in a corner, that's what happened.
Rotation crops the corners. It shouldn't, given expand=True, but it will if the layer is large enough that the rotated bounding box exceeds the canvas - then the paste is clipped by the canvas edge. Scale down first, rotate second.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| canvas_width | INT | 64064–8192 | Canvas width in pixels when not fitting background image size. |
| canvas_height | INT | 36064–8192 | Canvas height in pixels when not fitting background image size. |
| background | COMBO | Alpha | Choose background type: Alpha (transparent) or Color (custom background color). |
| background_color | COLORCODE | #222222 | Pick background color (use color picker). |
| fit_background_size | BOOLEAN | false | When True, canvas dimensions match the background image. When False, canvas dimensions use canvas_width and canvas_height. |
| canvas_state | STRING | {} | Serialized JSON layout containing layer coordinates, scales, rotations, and order. |
| Backgroundopt | IMAGE | Optional background image input. | |
| layer_1opt | IMAGE | Layer 1 image input. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |
| MASK | MASK | — |