PS Normal Layer Composite (RGBA)
Stack two transparent images like Photoshop, without leaving ComfyUI
- bottom_image
- top_image
- bottom_alpha
- top_alpha
- rgba_image
- rgb_preview
- alpha
- comfy_transparency_mask
You've got a transparent subject - from LayerDiffusion, a Qwen layer decomposition, or a BiRefNet cutout - and you want it sitting on top of a background, offset, at 70% opacity, with the edges staying clean. ComfyUI core can paste one image on another, but it doesn't do proper alpha blending on the way, and the result is a hard-edged rectangle or a fake-looking composite. This node does the thing you'd normally open Photoshop for: a normal-mode layer stack, straight from the graph.
It's from a tiny, no-dependency pack by Doki21yy - two nodes total, no models to download, one real job each. This one composites; the sibling (SaveTransparentPNG) saves.
How it works
The math is the classic "source-over" straight-alpha blend you know from every image editor. The node multiplies the top layer's alpha by top_opacity, then combines: final alpha is top alpha plus bottom alpha scaled by what's left uncovered. Colors are blended in premultiplied space and divided back out, which is exactly how Photoshop's Normal mode behaves - soft edges stay soft instead of picking up a dark halo.
The part that actually bites people is the mask convention. ComfyUI's built-in IMAGE is RGB-only, so transparency travels separately as a MASK - and ComfyUI's masks come in two contradictory flavors. LoadImage's mask output is white where transparent (an inverted alpha, the engine's quirk). Mask-edit nodes usually output white = selected/opaque. This node's mask_mode maps to those two worlds:
comfy_transparency_mask(default) - treats the MASK as 1 = transparent. Correct forLoadImagemasks.alpha_mask- treats the MASK as straight alpha, 1 = opaque. Use it when your mask came from a selection tool.
The inputs that matter
You set maybe half of these:
bottom_image/top_image- the stack, bottom first. Optionally wirebottom_alpha/top_alpha(MASK) if your images arrived as RGB.top_x/top_y- where the top layer sits, in pixels. Negative values move it up/left; you're not limited to inside the canvas.top_opacity- 0 to 1, default 1. Your "layer opacity" slider.canvas_mode-bottom_size(default),top_size,union_size(bounds of both), orcustom_sizewithcanvas_width/canvas_height. The canvas is the one thing you'll flip if your top layer hangs off the bottom edge.
Outputs
rgba_image is a real 4-channel IMAGE with the alpha baked back in - the one to save. rgb_preview is a flattened preview for screenshots, and you get two masks: alpha (1 = opaque) and comfy_transparency_mask (1 = transparent) if you want to feed the result into something that expects either convention.
Install
No dependencies, no model files - the pack only uses numpy, torch, and PIL, all of which ship with ComfyUI already. Install via ComfyUI Manager (search "comfyui-ps-normal-layer-composite") or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/Doki21yy/comfyui-ps-normal-layer-composite.git
Restart ComfyUI. That's the whole install.
Where people get burned
- Everything composites opaque. You fed a
LoadImagebut didn't wire its MASK output tobottom_alpha/top_alpha. LoadImage gives you RGB in one socket and alpha in another; this node can't see the second one unless you connect it. - Inverted transparency - white where it should be clear, or the reverse. That's the
mask_modetrap above; flip it and it fixes itself. - Soft or bleeding edges. When a canvas mode resizes a layer it uses bilinear interpolation on the alpha too, so semi-transparent pixels stay semi-transparent. That's a feature - it's what keeps cutouts from looking pasted-on.
Worth knowing: it's a brand-new, single-commit pack with zero track record, so don't build a production pipeline on it without a quick sanity check. But for a two-node job like this, it's refreshingly simple - and it does the one thing ComfyUI core refuses to.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| bottom_image | IMAGE | — | |
| top_image | IMAGE | — | |
| top_x | INT | 0-16384–16384 | — |
| top_y | INT | 0-16384–16384 | — |
| top_opacity | FLOAT | 1.000–1 | — |
| canvas_mode | COMBO | bottom_size | 4 options: bottom_size, top_size, union_size, custom_size |
| canvas_width | INT | 5121–16384 | — |
| canvas_height | INT | 5121–16384 | — |
| mask_mode | COMBO | comfy_transparency_mask | 2 options: comfy_transparency_mask, alpha_mask |
| bottom_alphaopt | MASK | — | |
| top_alphaopt | MASK | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| rgba_image | IMAGE | — |
| rgb_preview | IMAGE | — |
| alpha | MASK | — |
| comfy_transparency_mask | MASK | — |