Computer Graphics · Color and Display
Why Tone Mapping Shifts Hue, and the Two Fixes That Keep Colour Stable
The nonlinear RGB curve that pushes highlights toward the last-saturating primary, why luminance-only compression avoids it, and what each approach costs.
A tone curve applied to the three RGB channels independently is not a hue-preserving operation, and the reason is purely algebraic. Hue, in any representation that separates it from luminance and chroma, is a function of the ratios of the channels. A nonlinear curve changes those ratios. The result is the well-known behaviour of a bright saturated colour drifting toward cyan, or toward the last-saturating primary, as it is compressed into the display range. Fixing it means moving the compression out of the RGB channels and into a space where luminance is one of the coordinates and the other two carry colour.
The algebra of the drift
Take a linear RGB colour and apply a compressive curve $f$ to each channel. The output is $(f(R), f(G), f(B))$. For a nonlinear $f$, the ratios $f(R)/f(G)$ and $f(B)/f(G)$ differ from $R/G$ and $B/G$, so the hue, which is defined by those ratios, changes. The direction of the change depends on which channel saturates last: a channel whose value is large relative to the others is compressed less in relative terms by a saturating curve, so it grows in relative weight, pulling the hue toward that primary.
The classic case is a red-orange highlight under a curve that saturates toward 1.0. The red channel is already large and compresses little; the green and blue channels are smaller and compress more in relative terms; the result is a colour that is more red, then more yellow as the red and green both approach saturation, then white. A sunlit skin tone compresses toward orange, and a blue sky compresses toward cyan or white depending on the curve’s shoulder. The specific hue that emerges is a function of the curve shape, not of the content, which is why the artifact is consistent across images and recognizable.
The magnitude of the shift is not small. On a curve with a pronounced shoulder, a saturated colour can shift by tens of degrees in a hue circle before it desaturates to white. The shift is invisible in a neutral image and prominent in exactly the images that have bright saturated regions, which is why it survives unnoticed in test suites that use grey or pastel content.
The luminance-only fix
The first fix applies the curve to luminance and reinstates colour. Convert the linear RGB to a space that has a luminance coordinate $Y$ and two colour-difference or chroma coordinates, apply $f$ to $Y$ alone, then convert back. Because the colour coordinates are unchanged, the hue and the chromatic structure are preserved, and the compressed image has the same colour relationships as the original with a reduced brightness range.
The simplest such conversion is to apply the curve to a weighted luminance and scale the RGB channels by the ratio of the new to the old luminance. Scaling all three channels by the same factor $k = f(Y)/Y$ preserves the ratios exactly, so hue and saturation are both preserved. This is the “luminance-preserving” tone map, and its failure is the opposite of the RGB curve’s: with the colour coordinates untouched, a bright colour compressed down toward the display range keeps its saturation, which can look unnaturally vivid in the highlights, and can exceed the display gamut, at which point the value clips in a different and worse way.
/* Luminance-only compression: preserve ratios, so preserve hue and saturation. */
void tone_map_luminance(float rgb[3], float curve(float)) {
float y = 0.2126f * rgb[0] + 0.7152f * rgb[1] + 0.0722f * rgb[2];
float k = (y > 0.0f) ? curve(y) / y : 1.0f;
rgb[0] *= k; rgb[1] *= k; rgb[2] *= k;
}
The coefficients are the Rec. 709 luminance weights, which differ slightly from the sRGB ones; the difference is a fraction of a percent on neutral content and matters only for exact colourimetric work. The subtlety is that the ratio $k = f(Y)/Y$ is the same for all three channels, so the operation is a per-pixel scalar multiply, which is cheap and parallelizable and, importantly, invertible where $f$ is invertible.
The cost of the luminance-only approach is the saturation question. Preserving saturation through a compression that folds bright colours down into a narrow top range means bright colours stay vivid right up to white, which can look electronic. The standard response is to reduce saturation in the highlights after the curve, reintroducing a hue-shift-free desaturation that must itself be designed carefully to avoid clipping.
The perceptual-space fix
The second fix moves the curve entirely into a perceptual space such as CIE Lab* or its more recent relative OKLab. These spaces are built so that the three coordinates roughly correspond to lightness, red-green, and yellow-blue, and so that equal coordinate distances correspond roughly to equal perceived differences. Applying a compressive curve to the lightness coordinate alone compresses the image’s brightness range while leaving the colour coordinates, and therefore the hue, unchanged.
The advantage over the luminance-ratio method is that the colour coordinates are already separated and can be manipulated independently. Desaturating highlights is a simple scale of the two colour coordinates; a hue adjustment is a rotation in that plane. The disadvantage is cost: converting to and from a perceptual space involves a nonlinearity, a cube root per channel for Lab and a slightly different root for OKLab, plus the matrix conversions. The cube root is the same class of cost as the sRGB power function and is applied per pixel per frame, which is why perceptual-space tone mapping appears in offline and post-processing pipelines more often than in a fill-rate-bound real-time pass.
The two perceptual spaces differ in a way that matters for hue stability. Lab* has a known problem in the blue region, where equal coordinate steps do not correspond to equal perceived steps and a hue-preserving compression can still appear to shift hue because the space itself is not uniform. OKLab was derived specifically to improve the blue behaviour and the hue linearity of saturated colours, which is the exact property a hue-preserving tone map needs. The derivation note for OKLab describes the error of Lab in blue hues and the construction that fixes it.
| Approach | Hue | Saturation | Cost | Invertible |
|---|---|---|---|---|
| Per-channel RGB curve | shifts toward last-saturating primary | depends on curve | low | yes |
| Luminance-ratio scale | preserved | preserved (may look vivid) | one multiply per pixel | yes where $f$ is |
| Lab* lightness curve | approximately preserved | adjustable | cube root + matrices | approximately |
| OKLab lightness curve | better in blues | adjustable | cube root + matrices | approximately |
Hue shift as a deliberate look
The drift is not always a defect. The filmic look is partly defined by the way highlights shift toward a warm or cool tint as they compress, and a grade that needs a highlight to “bloom” white through a particular tint deliberately chooses a curve that pulls the hue in a chosen direction. The ACES reference transform, for example, produces a characteristic highlight hue as part of its output rendering, and the look is specified and reproducible rather than accidental.
The distinction is between a curve that shifts hue because the author chose it and one that shifts hue because the author applied a per-channel curve without realizing the consequence. The former is grading; the latter is a bug that shows up as “my highlights look cyan” and is fixed by one of the two approaches above. Recognizing which one is in play requires knowing what the curve was supposed to do, which is the design question behind tone mapping operators.
Interaction with gamut
A hue-preserving tone map can push a colour outside the display gamut, because preserving hue and saturation while compressing luminance leaves the colour’s chromatic position unchanged while the gamut boundary is a function of luminance. The result is a colour that is hue-correct but outside the reproducible region, and the response — clipping, desaturating, or mapping it back inside — is a gamut operation, not a tone-map operation. The two are entangled in practice because the tone map decides how bright a colour is and the gamut map decides whether it fits, and the combined behaviour is the subject of gamut mapping and rendering intents.
The same entanglement appears in the PQ pipeline: an HDR colour compressed by a hue-preserving curve to fit a lower-peak display can still land outside the display’s chromatic gamut, and the two corrections must be applied in the right order. The PQ curve’s role in setting the brightness range is covered in the ST 2084 perceptual quantizer.
References
- Perceptually uniform color spaces (2006) — CIE 1976 L*a*b* color space, CIE 015
- Academy Color Encoding System — Technical Bulletin TB-2014-001 — Academy of Motion Picture Arts and Sciences
- A perceptual color space for image processing (2020) — Björn Ottosson, OKLab derivation note