Rolling Color Match (EMA)
Rolling Color Match (EMA)
- image
- anchor_in
- matched
- anchor_out
Color Match LAB pins your palette to one fixed image, which is exactly right for a locked look and exactly wrong when the colors are supposed to evolve with the scene. Rolling Color Match (EMA) is the moving-anchor version: the same histogram match, but the reference is an exponential moving average of past frames. The palette can drift with the animation - sunset to dusk, warm to cool - while staying coherent frame to frame. No frame-0 jail, no acid.
How it works
The mechanism is identical to its fixed-anchor sibling - full per-channel histogram remap in LAB (default), HSV, or RGB - but the reference isn't a static image. It's an anchor that updates each frame: new_anchor = (1 - alpha) * old_anchor + alpha * current_frame, roughly speaking. alpha controls how fast the anchor chases the animation:
alpha = 0- frozen anchor. This node becomesColor Match LABwith a fixed reference.alpha = 1- follow the previous frame. Very fast memory, which is also very dangerous: one dark or dull frame drags everything down.- Middle values - a genuine exponential moving average, a fading memory of the last handful of frames.
The node itself is stateless. The anchor doesn't live inside it - it's threaded through the loop via anchor_in and anchor_out, bootstrapped at frame 0 with a reference image. That's why you have to wire anchor_out back into ForLoopClose, or the whole thing falls apart.
Inputs and outputs
image- the frame to recolor.anchor_in- the current anchor, pulled from the loop's carry slot; bootstrap with your frame-0 / reference image.alpha- anchor update speed (0–1). Schedulable.color_space,strength,preserve_luminance- as inColor Match LAB.
Outputs: matched (the recolored frame) and anchor_out (the updated anchor, back into the loop). No third output - the "does the sampler run" decision belongs to Cadence, not here.
The tuning that actually matters
alpha and strength are independent, and the pack's own docs are refreshingly blunt about it: strength is how much recolor (0 = none, 1 = full), alpha is what you match toward. Two settings worth remembering:
- To kill acid while keeping the original palette, use a very low
alpha- around 0.02 to 0.1. Small is strong. Analphaof one frame is a fast memory, and fast memory is how a dull frame degenerates the whole loop. - Schedule
strength(e.g. ramp 0 → 0.7 over the first frames) so early frames aren't slammed to the anchor before the scene forms. Ramping strength is cheap and prevents the most common "everything turns into the reference" complaint.
Two more honest caveats, inherited from the LAB matcher: a poor or dark bootstrap palette can't be enriched by matching, so feed it a rich image and the whole loop inherits that; and preserve_luminance makes this a color-only control - it will not stop a loop that's darkening on its own, because that's a denoise/luminance problem.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/Statistical-Impossibility/comfyui-Stat-Imp-nodes
Pure torch, no extra dependencies. The one real install caveat: the loop it runs in needs the pack's fork of deforum-comfy-nodes (https://github.com/Statistical-Impossibility/deforum-comfy-nodes), because the upstream harness only carries one value through the loop and this node needs a carry slot for the anchor. Restart, hard-refresh, and it's under Stat-Imp / Deforum / Color.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| anchor_in | IMAGE | — | |
| alpha | FLOAT | 0.000–1 | — |
| color_space | COMBO | 3 options: LAB, HSV, RGB | |
| strength | FLOAT | 1.000–1 | — |
| preserve_luminance | BOOLEAN | false | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| matched | IMAGE | — |
| anchor_out | IMAGE | — |