Image Color Balance
A film grade in one node
- images
- images
Look at any still from a movie that's been through a colorist and you'll see the same shape: shadows pushed cool, highlights pushed warm, skin sitting somewhere comfortably in the middle. That teal-and-orange split isn't a filter, it's three wheels of color balance - separate control over the dark, middle and bright parts of the frame. Image Color Balance gives you exactly those wheels on your render, and it's the difference between an image that looks generated and one that looks graded.
It lives in WAS Node Suite under WAS Suite/Image/Adjustment, alongside the rest of WASasquatch's big MIT toolkit (457 nodes in v3). Where the pack's Image White Balance node neutralizes one cast across the whole frame, this one works per tonal range - which is why it's the one a colorist reaches for.
The nine sliders
The interface is admirably boring: three bands (shadow, midtone, highlight) × three channels (red, green, blue), each a float from −1 to 1, stepped fine enough (0.005) that you can nudge subtly. Positive pushes the named channel into that range; negative pushes its opposite.
- Push shadow_blue slightly positive and shadow_red a touch negative → the classic cool shadow.
- Push highlight_red positive → warm highlights.
- Leave the midtones nearly alone unless you're correcting skin, which is where a little midtone_red warms a face.
The tooltips tell you the calibration: 0.05 of shadow red is a warm shadow, −0.05 a cyan one; 0.06 of highlight blue is the cool side of a teal-and-orange grade. Small numbers do real work, so start around ±0.03–0.06 and walk up.
The one behavioral switch is preserve_luminosity, default on. On, it puts every pixel back to the brightness it had after the grade - the push moves color and never exposure. Off, a push can brighten or darken too, which is what a lift-and-gain does. For a pure cast correction or a stylized split tone, leave it on; the image stays put and only the color drifts.
How it works, briefly
It's a per-band recoloring, not a full transform: the frame is split into shadow/midtone/highlight weightings and each channel gets its push scaled by how much each pixel belongs to that band. That's why the ranges don't fight each other the way a single hue shift would, and why correcting a cast that lives in one range (a magenta veil in the shadows, a green spill in the highlights) is as simple as pushing the opposite color in that band. There's also a WAS Image White Balance sibling that handles the whole-frame neutralization if the cast is everywhere.
Using it in practice
The move that sells renders is subtle: cool the shadows and warm the highlights by a hair while the midtones - where the subject's skin lives - barely move. That single three-wheel gesture is most of "why does that image look cinematic." You can also use it as a correction stage after a bad color profile, pushing opposite to whatever range carries the cast.
It's pure pixel math, torch on ComfyUI's device, and it operates on batches frame-by-frame, so it slots into a video grade pass too - every frame gets the same balance. Nothing to install, no model weights.
Install and notes
WAS Node Suite installs the standard way: ComfyUI Manager, search "WAS Node Suite v3", or git clone https://github.com/WASasquatch/was-node-suite-comfyui into ComfyUI/custom_nodes and restart. Needs ComfyUI 0.14.0+ / Python 3.10+. v3 runs the pixel work itself - no OpenCV, no scikit-image, none of the dependency collisions the old v2 used to cause.
The classic beginner mistake is going too far, fast - ±0.2 pushes make a muddy mess because the bands overlap and everything tints. Start tiny, zoom out, and remember the whole point of three wheels over one slider is that you can move the shadows without touching the face.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | The frames to grade. Each one gets the same balance and comes back at the size it went in at. | |
| shadow_red | FLOAT | 0.000-1–1 | Red pushed into the dark parts. 0.0 = none, 0.05 = a warm shadow, -0.05 = a cyan one. |
| shadow_green | FLOAT | 0.000-1–1 | Green pushed into the dark parts. 0.0 = none, 0.05 = greener, -0.05 = a magenta shadow. |
| shadow_blue | FLOAT | 0.000-1–1 | Blue pushed into the dark parts. 0.0 = none, 0.06 = the cool shadow of a teal and orange grade, -0.05 = a yellow one. |
| midtone_red | FLOAT | 0.000-1–1 | Red pushed through the middle of the range, where skin sits. 0.0 = none, 0.03 = a warmer face, -0.03 = a cooler one. |
| midtone_green | FLOAT | 0.000-1–1 | Green pushed through the middle of the range. 0.0 = none, 0.03 = greener, -0.03 = a magenta cast lifted off it. |
| midtone_blue | FLOAT | 0.000-1–1 | Blue pushed through the middle of the range. 0.0 = none, 0.03 = cooler, -0.03 = warmer. |
| highlight_red | FLOAT | 0.000-1–1 | Red pushed into the bright parts. 0.0 = none, 0.06 = warm highlights, -0.06 = cyan ones. |
| highlight_green | FLOAT | 0.000-1–1 | Green pushed into the bright parts. 0.0 = none, 0.03 = greener highlights, -0.03 = magenta ones. |
| highlight_blue | FLOAT | 0.000-1–1 | Blue pushed into the bright parts. 0.0 = none, -0.04 = the warm highlight of a teal and orange grade, 0.04 = a cooler one. |
| preserve_luminosity | BOOLEAN | true | `on` puts every pixel back to the brightness it had, so the grade moves colour and never exposure; `off` lets a push brighten or darken, which is what a lift and gain do. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | The graded frames. |