HSL Color Consistency Advanced
Your Klein edit came back with a warm cast — this node drags it back in one pass
- reference
- target
- external_mask
- IMAGE
If you've edited a photo with FLUX.2-Klein and wondered why everything came back with a slightly warmer, slightly off color temperature - it's not you, it's the model. That shift is reproducible and documented on both fp8 and fp16 Klein, and the community fix has long been "histogram/color match it back against the source." This node is that fix, done properly in LAB color space and generalized past Klein: drop in your original as reference, your edited image as target, and it re-matches luminance, hue, and saturation so the edit keeps its content but stops wearing the model's color fingerprint.
It's a single-input-single-output postprocessor that lives in the image/postprocessing menu and works with any editing, enhancement, or upscaling workflow - its pitch, and it holds. No models to download, no API key, no VRAM cost to speak of. Pure per-pixel math on images you already have.
How it actually works
The node converts both images to CIELAB, then turns the a/b color channels into polar form: hue becomes an angle, saturation a magnitude. That's the trick that makes it different from a dumb "match the RGB average" node - it can manipulate brightness, hue, and saturation as independent knobs without cross-contamination.
For each knob you get two philosophies. Statistical matching renormalizes mean and standard deviation, so the target adopts the reference's color "personality" while keeping its own local contrast and texture. Pixel-perfect just copies the reference value at every pixel - exact, but only sensible when content hasn't moved between the two images.
The four anchor_mode choices combine the two:
statistical match- mean/std on every channel you selectpixel-perfect- hard per-pixel copyluminance stat + color exact- brightness renormalized, hue/saturation copied. This is the Klein sweet spotcolor exact- pixel-perfect hue/saturation, luminance left completely alone. Great when you only want the cast fixed
Luminance stats can run on the perceptual LAB L channel or physical linear RGB luminance (luma_space); hue/saturation always live in LAB. When a mask is present, it computes stats over the stable (non-edited) pixels only, so a local edit won't pollute the whole-image color model.
The inputs that matter
You'll actually touch about half of them. reference and target are the two images. Then:
mode- which channels to match: luminance, hue, saturation, or any combinationanchor_mode- the matching philosophy aboveluma_strength- how hard to apply luminance matching (0 = leave it, 1 = full)strength- global blend of the whole corrected result over the targetprotect_strength- how much of the original edited image survives inside the edit regionauto_mask/external_mask/feather_radius- how the edit region gets detected (auto = difference threshold, external = a mask you paint) and feathered
The single output is a corrected IMAGE, wired into a Save node or further postprocessing. For Klein edits the README's recommended combo is mode = luminance+hue+saturation, anchor_mode = luminance stat + color exact, luma_strength = 0.5, protect_strength = 0.3, feather_radius = 15 - a solid starting point.
Installing it
Two ways, same result:
# ComfyUI Manager (easiest): search "Color Consistency HSL" and hit install
# Or by hand:
cd ComfyUI/custom_nodes/
git clone https://github.com/overcloud81/ComfyUI-ColorConsistencyHSL
Then restart ComfyUI - it appears as "HSL Color Consistency Advanced". One dependency: scipy, used only for mask feathering. It's declared in the pack's pyproject, but the code degrades gracefully - if scipy is missing, feathering silently disables and the node still works. Worth a pip install scipy if you're going to feather. (The README's clone URL literally says yourusername - ignore it, the repo above is real.)
Gotchas worth knowing
The UI is bilingual, based on your ComfyUI language setting. An English ComfyUI shows English dropdowns (luminance+hue+saturation, statistical match...); a Chinese locale shows Chinese. If you see 亮度 etc., that's the locale, not a broken install.
Auto-mask can fight a global cast fix. auto_mask marks everything that differs from the reference - and a global warm cast differs everywhere, so the mask goes mostly white, then protect_strength re-blends the shifted original back in. For a whole-image cast, leave auto_mask off and let it correct globally; bring it in only when guarding a genuinely edited region.
Hue is an angle, and the statistical path averages it like a scalar. Near-red hues (the ±π wraparound) can average to something wrong. The color exact variants sidestep this entirely by copying, which is part of why they're the recommended path.
force_match_size is quieter than it looks. If reference and target differ in resolution, the reference gets scaled to target size unconditionally - the flag only controls whether you see a console warning. Sizes are always aligned, so mismatched inputs won't error - unlike mismatched batch counts, which raise a ValueError.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| reference | IMAGE | — | |
| target | IMAGE | — | |
| mode | COMBO | 亮度+色相+饱和度 | 7 options: 亮度, 色相, 饱和度, 亮度+色相, 亮度+饱和度, 色相+饱和度, +1 |
| anchor_mode | COMBO | 统计匹配 | 色彩精确:色相和饱和度像素级精确,亮度不变 / Color exact: pixel-perfect hue/saturation, luminance unchanged |
| luma_space | COMBO | LAB L通道 | 亮度统计匹配的空间(仅当亮度参与匹配时有效) / Space for luminance statistical matching (only when luminance is matched) |
| align_corners | BOOLEAN | true | 对齐四角,True可减少像素偏移 / Align corners, True reduces pixel shift |
| interpolation | COMBO | bilinear | 缩放插值方式 / Interpolation method for scaling |
| force_match_size | BOOLEAN | false | 强制尺寸一致(不一致时自动缩放) / Force size match (auto-scale if different) |
| luma_strength | FLOAT | 1.000–1 | 亮度统计匹配强度(仅亮度参与统计匹配时有效) / Strength of luminance statistical matching (only when luminance is matched statistically) |
| strength | FLOAT | 1.000–1 | 整图混合强度 / Global blend strength |
| protect_strength | FLOAT | 0.000–1 | 编辑区域保留原图的比例 / Proportion of original image retained in edit area |
| feather_radius | INT | 100–100 | 蒙版羽化半径(像素) / Mask feather radius (pixels) |
| auto_mask | BOOLEAN | false | 启用自动蒙版 / Enable auto mask |
| mask_threshold | FLOAT | 0.100–1 | 自动蒙版阈值 / Auto mask threshold |
| external_maskopt | MASK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |