ColorCorrect
Exposure, contrast and lift/gain that don't clip your HDR
- image
- image
Most "color correct" nodes in this ecosystem are a brightness slider with ambitions. Brightness is additive - it adds a constant to every pixel, which flattens contrast and blows highlights before you've done anything useful. This one does the operations in an order that a person who's sat at a grading console would recognise, in 32-bit float, without clamping.
The order matters, and it's documented
The node applies, in this sequence:
- Lift (
lift_r/g/b) - a flat per-channel offset added at every level, applied first so exposure and gain scale it afterward. The tooltip is honest that this is a flat offset, not a shadow-weighted lift, which is not what a colourist means by the word. Know that going in. - Exposure in stops. +1 doubles brightness, -1 halves it. Linear-domain maths, which is why it behaves properly on float.
- Gain per channel (
gain_r/g/b, default 1) - a multiplier, so this is the one that does the "tint the highlights" job. - Contrast, pivoting around 0.18 in linear rather than 0.5. That's the fix that matters: pivoting contrast at 0.5 on linear values moves your mid-grey and makes everything look muddy. 0.18 is linear mid-grey, so contrast expands and contracts around the tone your eye reads as neutral.
- Gamma, computed as sign-preserving
x^(1/gamma), so negative values survive instead of being clamped to zero. On >1 gamma you brighten midtones without moving white. - Saturation, weighted by
luma_space- pickRec.709 / sRGB,ACEScg / AP1orRec.2020to match your working space. Getting this wrong shifts hue as you saturate, which is the classic "why did the skin go orange" moment. - Brightness, an additive offset, applied last so it acts in the output space.
That ordering is the node's actual value. Same controls, wrong order, and lift/gain behaves nothing like the grading tool you learned on.
clamp_output is off by default and should stay off in an HDR chain - it preserves super-whites and negatives, which is the entire reason to be in float. Turn it on only at the very end if something downstream can't cope.
Where it fits
This is the grade node you put at the end of an HDR pipeline, or anywhere you need an exposure trim without a colour-space conversion. If you're doing a creative look - curves, split-toning, per-channel shaping - RadianceCurves is the better tool. If you're doing a technical pass - this is 1.5 stops hot, lift the blacks 0.02, add a touch of contrast - this is it, and it's also the node you'd use to make a linear image viewable before a preview.
Install
Ships in Radiance. Manager → search Radiance → install, restart, refresh the browser. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
Windows portable users: python_embeded\python.exe for the pip line. Pure tensor maths - no models, no downloads, nothing to configure.
Where people get burned
Grading in the wrong space. These operations assume linear values. Feed it sRGB-encoded input and exposure-by-stops is no longer physically meaningful, the 0.18 contrast pivot lands somewhere unhelpful, and everything feels like it's fighting you. Decode first.
Expecting the lift to be a lift. Per the tooltip: flat offset, applied first, so a gain of 2 doubles it. If you want a shadow-weighted lift you're looking at the wrong control, and no amount of fiddling will make it behave like one.
Leaving clamp_output on out of habit. It's off for a reason. An aggressive grade on float data can push highlights above 1.0 temporarily - that's fine, it's data, and you may well bring it back down later. Clamping at every step is how you get a chain of nodes that each quietly destroy a little range.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Input image to color correct. Processed in 32-bit float precision. | |
| exposure | FLOAT | 0.0-10–10 | Exposure adjustment in stops. +1 = double brightness, -1 = half brightness. |
| contrast | FLOAT | 1.000–4 | Contrast multiplier around mid-gray (0.18 in linear). >1 = more contrast, <1 = less. |
| brightness | FLOAT | 0.00-1–1 | Additive brightness offset. Applied last so it works in the final output space. |
| saturation | FLOAT | 1.000–3 | Color saturation. 0 = grayscale, 1 = original, >1 = boosted. |
| gammaopt | FLOAT | 1.000.1–4 | Gamma correction: values are raised to 1/gamma. >1 = brighten midtones, <1 = darken. Sign-preserving for HDR. |
| lift_ropt | FLOAT | 0.00-0.5–0.5 | Value added to the red channel at every level, applied first, so exposure and gain then scale it. A flat offset, not a shadow-weighted lift. |
| lift_gopt | FLOAT | 0.00-0.5–0.5 | Value added to the green channel at every level, applied first, so exposure and gain then scale it. A flat offset, not a shadow-weighted lift. |
| lift_bopt | FLOAT | 0.00-0.5–0.5 | Value added to the blue channel at every level, applied first, so exposure and gain then scale it. A flat offset, not a shadow-weighted lift. |
| gain_ropt | FLOAT | 1.000–2 | Red channel gain (multiplier). Applied after lift, before contrast. |
| gain_gopt | FLOAT | 1.000–2 | Green channel gain (multiplier). Applied after lift and exposure, before contrast. |
| gain_bopt | FLOAT | 1.000–2 | Blue channel gain (multiplier). Applied after lift and exposure, before contrast. |
| luma_spaceopt | COMBO | Rec.709 / sRGB | Luminance weights for saturation. Match your working colorspace. |
| clamp_outputopt | BOOLEAN | false | Clamp output to [0,1]. OFF = HDR pass-through (preserves super-whites/negatives). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | Color corrected image in 32-bit float. |