**Disclaimer**: The following is not an approach I would suggest be used for producing good looking images, but merely an interesting and informative exercise to better understand the problem at hand.

**Back to Basics**

The last several days I’ve been thinking about the highlight desaturation problem. With display rendering there are a lot of intertwined problem areas, and it’s very easy (at least for me) to get confused. Like earlier when I was thinking about gamut compression and highlight desaturation as one problem. They are two different problems. Related, but not the same.

So how can we simplify and isolate these problem areas? We could ignore “out of display gamut” issues. We could ignore tonemapping or intensity scaling issues.

**NaiveDisplayTransform**

In an effort to simplify and focus on highlight compression and desaturation, I made a nuke node called NaiveDisplayTransform. As the name suggests, it undertakes a very naive approach to mapping scene-linear to display-linear.

- Maps an
**input range** in scene-linear from `linear_start`

to `linear_end`

, defined in stops above and below middle gray, to an **output range** in display linear from 0 to `display linear end`

.
- From
`display linear end`

to 1.0, apply a compression curve to the scene linear values. The compression curve compresses infinity to a value of `limit`

stops above `linear end`

. In other words: if limit is 1, all highlight values from `linear end`

to infinity will be compressed into a range of 1 stop. The bigger `limit`

is, the more range there will be for the compressed highlight values.
- Where highlight values are compressed, desaturate.

The simplest possible display transform would be a linear to linear mapping. We take a range of scene-linear values and remap them to a range of display-linear values. We apply the inverse EOTF. The scene linear values are displayed directly on the display, up until the values clip. For the range that can be displayed, (ignoring spectral emission differences, calibration, and other complications) there should be a 1 to 1 correspondence between scene luminance and display luminance.

Since pictures are easier to understand than words (at least for me), **here’s a video demonstration of the concept and the tool.**

**When Channels Clip**

With a simple 1 to 1 mapping, we can focus on how highlight values behave as they approach 100% display emission.

With no highlight compression and no desaturation, hue shifts are introduced for non achromatic colors, because as one component clips, the other components continue to increase. So we need some way to “handle” these components as they increase in brightness, to remove hue shifts as the components approach clipping. To do this we can move the components toward the achromatic axis as their luminance increases.

Here’s a video demo with some interesting plots of what’s going on.

**Technical Approach**

Amidst an extended conversation with @Troy_James_Sobotka (thanks for your patience with my stupid questions), I got to thinking about the Inverse RGB Ratios approach that we ended up using in the Gamut Mapping VWG. In that approach we were using the inverse rgb ratios to push out of gamut values back towards the achromatic axis.

```
inverse_rgb_ratio = max(r,g,b) - rgb
```

If we instead used a constant value that we wanted to “collapse” our components towards, we could do

```
lin_max = 4.0
inverse_rgb_ratio = lin_max - rgb
```

If we add `inverse_rgb_ratio`

to rgb, we will get `lin_max`

everywhere. But if we modulate `inverse_rgb_ratio`

by some factor which describes how much we want to desaturate, then we get a simple and effective method of highlight desaturation.

The best way I’ve found so far (and I think there’s better approaches), is to modulate the `inverse_rgb_ratio`

by the complement of the compression factor. When we use the norm to do the highlight compression, we do

```
norm = max(r,g,b)
intensity_scale = compress(norm) / norm
scaled_rgb = intensity_scale * rgb
compression_factor = 1 - intensity_scale
```

`intensity_scale`

here is kindof like the derivative of the curve: it represents how much the compress function is altering the norm.

Then we can do

```
lin_max = 4.0
inverse_rgb_ratio = lin_max - c
desaturation_factor = inverse_rgb_ratio * compression_factor
desat_rgb = rgb + desaturation_factor
```

Here’s a video walkthrough of the technical approach.

Considering how stupid and simple this approach is it actually doesn’t look half bad in my aesthetic opinion.

Here’s a video of how the transform looks on some example test images.

And finally here’s the NaiveDisplayTransform node if you want to play around with it. No blinkscript so it should work fine in Nuke Non-Commercial.

**EDIT** - since the above link points to a gist that has been updated, here is the original nuke script as it existed at the time of this post: NaiveDisplayTransform_v01.nk (18.3 KB)

Next step for me is seeing how this same approach might be applied with a more traditional sigmoidal curve for input range compression.

Just wanted to share what I’ve been up to in case it’s useful for anyone here.