GPU LayerStyles CrossFade
Hide the seam between two clips without a jump cut
- images_1
- images_2
- image
Chained video clips have a visible join. You generate 81 frames, take the last one as the first frame of the next generation, and something small but unmistakable happens at the boundary - the light shifts a hair, the grain resets, a limb lands somewhere slightly different. GPULayerStyles_CrossFade is the blunt, deterministic fix: overlap the two batches and blend across N frames so the join is a transition instead of a cut.
What it does
It takes two batches and builds one linear ramp between them. The output is exactly start_index + length(images_2) frames long, made of three segments:
- Everything from
images_1beforestart_index, copied as-is. - A fade of exactly
framesframes, where frame 1 is 100%images_1and frame N is 100%images_2, linear in between. - The rest of
images_2after the fade - everything from its frameframesonward.
Anything in images_1 after start_index + frames is discarded. That's worth reading twice: this node doesn't splice two whole clips, it keeps a prefix of the first, uses a window of frames as the transition, and keeps the rest of the second. Frames from images_1 that land inside the fade window are your overlap - you've deliberately double-used them.
The blend is done in float32 with the alpha ramp computed from absolute transition position, not from the chunk being processed, so the weights don't change if the node has to split the work into chunks. Alpha channels fade along with RGB, and there's no clamping, so a fade of a semi-transparent batch stays soft rather than snapping to the edges.
Inputs you set
images_1,images_2- the two batches. They must match on height, width and channel count (RGB or RGBA). Frame counts do not have to match.start_index- zero-based frame inimages_1where the transition begins. Default 0, negative values rejected, and it must be less than the length ofimages_1.images_2always starts from its own frame 0.frames- the transition length including both endpoints, minimum 2. It has to fit in what's left ofimages_1afterstart_indexand withinimages_2.output_deviceandbatch_size- the pack's standard execution controls.cpuby default;gpuwhen the next node is also on-device;batch_sizechunks frames and auto-halves on allocation failure with a log warning.
One output, image.
Scoring a seam, practically
A frames value of 8–16 at 16fps reads as a deliberate dissolve; 2–4 reads as a soft cut. Going longer than about a second starts looking like a mistake rather than a transition. If the two clips are close - same prompt, same seed lineage, chained generation - you often only need 4–6 frames to kill the flicker, because what you're hiding is a small discontinuity, not a scene change.
Note what this is not: it isn't a morph. There's no optical flow, no motion interpolation. If the two clips have genuinely different content at the join, crossfading them produces the familiar ghosting double-exposure. That's a job for a model that generates a bridge, or a joiner node with a Make Loop toggle - and if you want a loop rather than a splice, TwoBatchLoop handles both boundaries at once.
Install
ComfyUI Manager → search GPU LayerStyles. Or:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/lany42/gpu-layerstyles
# restart ComfyUI
No pip step: the pack's runtime dependency list is empty, there are no model files and no API key. It does want Python 3.13+ and a current ComfyUI (comfy_api-era node schema). AGPL-3.0-only. The canonical repo is git.colorized.life, mirrored to GitHub as lany42/gpu-layerstyles.
Getting burned
Out-of-range frames is the classic. The node needs those frames in both batches, so if images_1 has 81 frames and you ask for a 16-frame fade starting at index 70, it errors with the arithmetic spelled out: eleven frames remain in images_1 at that start index. Read the message, lower start_index or frames, and it'll run.
Memory, at the end of a long chain. CrossFade has to reserve the whole output before it starts - a crossfade of two 500-frame 1080p batches is a 500-odd-frame float32 tensor plus the chunk buffers. output_device: cpu puts that in host RAM instead of VRAM, which is the safer default on a GPU you're also sampling on.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| images_1 | IMAGE | — | |
| images_2 | IMAGE | — | |
| start_index | INT | 00–2147483647 | Zero-based start of the transition in images_1. Negative indices are rejected. images_2 always starts at index 0. |
| frames | INT | 22–2147483647 | Exact number of frames from each batch used for the fade, including both endpoints. At least 2; must fit after start_index in images_1 and within images_2. |
| output_device | COMBO | cpu | Return output on CPU (default) after float32 processing on ComfyUI's selected compute device. GPU keeps output on that device to avoid transfers between compatible nodes. ComfyUI CPU mode returns CPU output. |
| batch_size | INT | 00–2147483647 | Frames per processing chunk. 0 starts at up to 64 frames. Positive values can exceed 64, capped by the output length. Allocation failures halve the chunk size for this run; each run starts fresh. Complete inputs and outputs still need memory. Pass smaller batches through the entire workflow to reduce complete-batch memory. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |