# Simplistic Gamut Mapping Approaches in Nuke

Tags: #<Tag:0x00007fa12c4feac8> #<Tag:0x00007fa12c4fe848>

Hi.

As mentioned during Meeting #6 and demo’ed at Meeting #7 I’m sharing a Nuke script illustrating some examples of simplistic approaches to the gamut mapping problem.
These are not intended as actual proposals, but rather to illustrate known issues and potential ideas for solutions.
So this is mainly meant as a conversation starter and an easy way for participants to look at some actual images.

gamut_mapping_demo_v02.nk

The methods shown try to satisfy the following requirements for the algorithm:

• non-iterative
• exposure invariant
• source gamut agnostic

The Nuke script does not rely on any custom plug-ins or external resources, except the images you want to try it out with.
It is (hopefully) self-explanatory enough to get everyone started, but please reach out with any problems or questions in using it.

Below is a quick explanation of the 6 mapping methods available in the script:

1. Unmapped:
No gamut mapping is being applied.

2. HSV Saturation Soft-Clip:

The image is converted to a cylindrical HSV projection of the target gamut and the saturation component is soft-clipped to 1.0.

Pros: extreme simplicity
Cons: significant over-compression, especially for red-yellow colours

3. Soft-Project x,y:

The image is converted to CIE Yxy and the chromaticity coordinate is projected onto the target gamut boundary using a soft clip function on the distance from the white coordinate

Pros: simple and intuitive?
Cons: The luminance (Y) component is zero or negative for saturated blue colours and results in target gamut RGB values that are too dark or even black.

4. Normalized HSV Saturation Softclip in XYZ:

The image is converted to a cylindrical HSV projection of CIE XYZ. The saturation component is normalized to the boundary of the target gamut and then soft-clipped to 1.0.

Pros: relatively simple, does not suffer from the negative luminance issue of method #3
Cons: significant blue>purple “Abney Effect”, plot shows jagged non-monotonic mapping at target gamut boundary

5. Normalized HSV Saturation Softclip in LMS:

The image is converted to a cylindrical HSV projection of LMS cone fundamentals according to M. Safdar 2017. The saturation component is normalized to the boundary of the target gamut and then soft-clipped to 1.0.

Pros: also does not suffer from the negative luminance issue of method #3, M. Safdar cone fundamentals are supposed to suppress the shift to purple (although it doesn’t seem to have much effect in this example)
Cons: still significant blue>purple “Abney Effect”

6. Normalized HSV Saturation Softclip in LMS + purple suppression:

Like method #5 but also includes a pre-process step that slightly warps blue hue values to suppress the blue>purple “Abney Effect”. Distortion parameters were manually chosen for optimal visual results with available sample images.

Pros: much better mapping result for saturated blue colours
Cons: hue distortion does also affect colours within the “core gamut” and limiting it to the mapping region only results in counterintuitive hue shifts along saturation gradients

I look forward to hearing your feedback, suggestions and other examples.

Best Regards,

Matthias

5 Likes

Great stuff Matthias!

I only shortly dived into the script, it is clean and quite easy to understand!

What is clear is that very soon a favourite topic will be The Compression Function!

One that I use often for same purposes is : `(a + b * tanh((x-a) / b))` Its trigonometric elegance is hard to beat: https://www.desmos.com/calculator/tl2aqicg25. I added yours too (`( -1 / (( x - a ) / ( 1 - a ) +1 ) +1 ) * ( 1 - a ) + a`).

Nuke variant for those willing to try, it is a great utility function:

``````set cut_paste_input [stack 0]
version 12.1 v1
push \$cut_paste_input
Expression {
expr0 "r > a_Floating_Point_Slider ? a_Floating_Point_Slider + b_Floating_Point_Slider * tanh((r - a_Floating_Point_Slider) / b_Floating_Point_Slider) : r"
expr1 "g > a_Floating_Point_Slider ? a_Floating_Point_Slider + b_Floating_Point_Slider * tanh((g - a_Floating_Point_Slider) / b_Floating_Point_Slider) : g"
expr2 "b > a_Floating_Point_Slider ? a_Floating_Point_Slider + b_Floating_Point_Slider * tanh((b - a_Floating_Point_Slider) / b_Floating_Point_Slider) : b"
name Compression_Expression1
selected true
xpos 3060
ypos 1087
a_Floating_Point_Slider 0.76
b_Floating_Point_Slider {{"1 - a_Floating_Point_Slider"}}
}

``````

I noticed that 5 and 6 blow with the Grasshoper Cornell Box image, haven’t tried to figure out why yet:

Cheers,

Thomas

I often use a variation on @matthias.scharfenber’s compression function, which adds two more parameters (c and d) to control the point at which compression begins, and the value it rolls off to (if compressing everything into the 0-1 range is not a requirement).

``````x < d + a ? x :  d + (-1 / ((x - d - a) / (c - a) + 1) + 1) * (c - a) + a
``````

I have to credit @Paul_Dore with showing me this one.

1 Like
2 Likes

Hi,

I was poking at a variant of HSV I did not know about to satisfy my curiosity, having thin hopes that it could do something better than HSV: HCL by Sarifuddin and Missaoui (2005).

Unfortunately no cigar, it is even worse than HSV!

sRGB Colour Wheel

ACES 2065-1 to sRGB Colour Wheel

Tasty Negative Colours

HSV Saturation Compression

No More Negative Colours

HCL Chroma Compression

Unsurprising Disappointment

I was considering porting it to Nuke initially but given those results it will stay as Python in my world. You can find an implementation in this issue.

Cheers,

Thomas

Due to popular demand I have now uploaded a ZIP archive containing debayered ACES/AP0 OpenEXR versions of the submitted test images to Dropbox.