CS Image Composite
The layer stack core ComfyUI never gave you
- background_image
- layer_image
- layer_mask
- image
- composit_mask
What it is, and why you'd want it
Core ComfyUI's compositing story is one node. ImageCompositeMasked puts an image over another, cut by a mask, and stops - no blend modes, no rotation, no scale. Want a soft-light pass, a multiplied grain plate, or a logo rotated 12° over 300 video frames? You were leaving ComfyUI to do it.
CS Image Composite is that missing layer stack. It's from chflame163 - author of LayerStyle, one of the most-installed packs in the ecosystem - and reuses LayerStyle's blend math: 30 modes, the ImageBlendAdvance V2 set, from normal to grain merge. What's different is the batch handling: a whole video's worth of frames goes through in one execution, on the GPU.
How it actually works
The background defines everything. Its size is the output canvas, its frame count is the output length. The layer broadcasts to match - one layer image repeats over every background frame, and a longer layer batch gets truncated down with a log warning rather than an error. Aligning frame counts before you blend matters more than picking a blend mode.
The transform baseline surprises people. Scale X/Scale Y are relative to a letterbox-fit of the layer, not its native size: internally min(bg_w/layer_w, bg_h/layer_h) is treated as 1.0. Same aspect ratio as the background, and 1.0 fills it. A square 1024×1024 layer on a 1920×1080 canvas lands at about 56% of the width - you'd need roughly 1.78 to make it cover.
The warp is GPU and mask-safe. Position, scale and rotation collapse into one 3×3 affine per frame, which the node inverts and feeds to torch.nn.functional.grid_sample - bilinear, zero padding. That's why a rotated layer has clean air around it instead of smeared edge pixels, and why frames move in batches, up to 32 at a time.
Blending is LayerStyle's, done right. Opacity folds into the blend itself - multiply at 50% is a real half-strength multiply, not a fade toward gray - then the result is composited premultiplied-alpha and un-premultiplied again. That last step is why soft mask edges don't pick up a dark fringe.
The inputs and outputs you'll touch
background_image- canvas and frame count. WireCS Load Video'sIMAGEhere for video frames.layer_image- a single frame is fine; it broadcasts.layer_mask(optional) - white visible, black hidden, greys by coverage. The node deliberately undoes the1 - alphainversion ComfyUI'sLoadImageMaskproduces on its "alpha" channel, so a mask off a cutout node behaves as expected.blend_mode(defaultnormal) andopacity- fixed for the whole batch.x,y- layer centre, normalised to the background.0.5, 0.5is dead centre.scale_x,scale_y,sync_scale,rotation- see the letterbox note.wait_for_input_cache- a deliberate stop, explained below.
Two outputs. image is the composite - to CS Save Video, a preview, or further nodes. composit_mask follows ComfyUI's convention where 1 means transparent, so with an opaque background it's all zeros. It isn't a cutout matte; for the layer's coverage downstream, use the mask you fed in.
There's an Edit Timeline button, but no keyframes: one shared transform for the entire layer sequence. Whole-batch positioning, corner-drag scaling, apply. Need motion over time? Animate upstream. One trick worth knowing - right-click x, y or opacity, pick Convert widget to input, and you can drive a crossfade from a primitive node.
Install
No weights, no downloads - just the pack.
# ComfyUI Manager: search "ComfyUI_CineStyle", install, restart
# or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/chflame163/ComfyUI_CineStyle
python -m pip install -r ComfyUI_CineStyle/requirements.txt
Find it under right-click → Add Node → 😺dzNodes/CineStyle, or double-click the canvas and type cinestyle. The menu path is dzNodes, not CineStyle, and the README is Chinese, so plenty of people hunt the wrong folder. requirements.txt pulls a real pile (av, opencv, transformers, timm, hydra-core, scipy, soxr) but deliberately doesn't pin torch - it won't swap your CUDA build out from under you. That's the good version of a heavy dependency list.
Troubleshooting
The node isn't in the menu. This pack registers through ComfyUI's V3 node API (from comfy_api.latest import ComfyExtension, io), so an old ComfyUI won't list it. And the loader is unusually quiet: __init__.py imports each node module in a try/except, logs Failed to load CineStyle node module <file>, and carries on. One broken import silently drops nodes. Check the console before reinstalling anything.
Execution stops when wait_for_input_cache is on. That's the feature. The node writes both inputs to the shared preview cache, then raises an interrupt on purpose so the timeline editor can inspect tensors that only exist after an upstream run. Build the cache, edit, apply, turn it off, run for real.
The layer is the wrong length. A one-frame layer broadcasts up; a longer layer batch is truncated. Warning, not error - check the console if the tail of your composite looks odd.
The mask seems inverted. Check where it came from. LoadImageMask's alpha output is compensated for; any other mask follows white-is-visible, and a mask that's 1 everywhere hides the layer completely.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| background_image | IMAGE | Background IMAGE batch and output canvas. | |
| layer_image | IMAGE | Layer IMAGE; a single frame broadcasts to the background batch. | |
| x | FLOAT | 0.500-10–10 | Layer center X, normalized to the background canvas. |
| y | FLOAT | 0.500-10–10 | Layer center Y, normalized to the background canvas. |
| scale_x | FLOAT | 1.0000.0001–10 | Layer horizontal scale relative to letterbox-fit size. |
| scale_y | FLOAT | 1.0000.0001–10 | Layer vertical scale relative to letterbox-fit size. |
| sync_scale | BOOLEAN | true | Keep Scale X and Scale Y linked. |
| rotation | FLOAT | 0.0-360–360 | Layer rotation in degrees. |
| opacity | INT | 1000–100 | Layer opacity, fixed for the complete batch. |
| blend_mode | COMBO | normal | LayerStyle ImageBlendAdvance V2 blend mode; fixed for the complete batch. |
| wait_for_input_cache | BOOLEAN | false | Cache both image inputs and interrupt execution so Edit Timeline can inspect generated tensors. |
| layer_maskopt | MASK | Layer coverage mask: white (1) is visible and black (0) is hidden. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | Composited RGB image. |
| composit_mask | MASK | Standard transparent MASK: 1 means transparent. With an opaque background this is normally all zeros. |