"Clip out-of-gamut colors" check box

Hi everyone,

can someone tell me, what the “Clip out-of-gamut colors” box in the Exposure tab is for? Actually, I understand what clipping out of gamut colours means, but which gamut is meant here? Is this the working profile gamut or the output profile gamut or is it the displays profile gamut? I haven’t found any information on RawPedia, so far.

I would assume that activating the “Clip out-of-gamut colors” box means, that colours which are beyond the output profiles gamut become clipped, whereas all colours will be preserved by a kind of perceptual rendering intent, if the box is unchecked.

Is that assumption correct?

Moreover, the German translation of the boxes name confuses me. It’s translated with “Farben auf Farbraum beschränken” which means „ Restrict colours to colour space”. In my opinion this is the opposite of “Clip out-of-gamut colors” because only colours which are out of gamut (or out of colour space) became clipped. Therefore no clipping will occur if the box is checked, which is the opposite of the English naming.

Or, to say it another way:

In English version:
Box is checked means: Relative Colorimetric rendering intent
Box is unchecked means: Perceptual rendering intent

In German version:
Box is checked means: Perceptual rendering intent
Box is unchecked means: Relative Colorimetric rendering intent

So the German translation is unfortunate, in my view.

I would be very happy if someone could bring some light into the darkness :slight_smile:

Thanks a lot
Erop

@Erop Welcome to the forum! Indeed, the Exposure page doesn’t have this. Ping @XavAL.

The box has to do with the working colour space, since exposure correction is within it. The steps before this in the pipeline can generate out-of-gamut or -range values, so I believe this clipping is to remove them.

As for the German, clipping has nothing to do with rendering intent, which has to do with how the gamut is mapped in the exported file. While the translation isn’t one-to-one, restricting means that RT only keeps colours within the colour space and discards the rest, which is another way of saying that everything else is clipped when the box is checked.

1 Like

If you check this box AND the clipping is done, you will see that immediately because the clipped colors get some pink shade, at least that is what I see in that case.

But how does it remove them? Does it somehow compress everything so that those colours are now within gamut but all the other colours are not what they would otherwise be. If not, does it just turn them grey? or somehow convert them (but no other colours) to their nearest (however defined) in gamut colour?

If I may ask Richard, what is your working space under colour management set to?

Clipping means cutting off, so if all pixel values initially lie between 0 and 1, and by some operation a pixel gets a value > 1, it is clipped and set to 1 exactly.

Edit: but this is not actually what happens in RT, see below.

1 Like

I use ProPhoto as my working space and usually as my output space unless I am saving directly for the web in which case I seitch my output space to sRGB.

Thanks. But it is still possible to get combinations of RGB values where each is less than one (the maximum), but which lie outside a particular gamut. Does that mean that those colours are not clipped because their values are less than the maximum?

In any event, which gamut is being referred to, that of the working colour space or that of the output colour space?

You quoted my answer to that. :wink: The clipping happens to the data in the working space. Conversion of the data to the output profile happens at the end of the pipeline.

PS We tend to lump out-of-gamut and out-of-bounds together. To my understanding, the former tends to appear as nonsense numbers; e.g. negative, NaN and inf values.

1 Like

@RichardRegal I’ve looked at the actual code this time, and things are slightly more complicated. Early on in the pipeline, if you have ‘Clip out-of-gamut colors’ checked, this happens:

                if (params->toneCurve.clampOOG) {
                    for (int i = istart, ti = 0; i < tH; i++, ti++) {
                        for (int j = jstart, tj = 0; j < tW; j++, tj++) {
                            // clip out of gamut colors, without distorting colour too bad
                            float r = std::max(rtemp[ti * TS + tj], 0.f);
                            float g = std::max(gtemp[ti * TS + tj], 0.f);
                            float b = std::max(btemp[ti * TS + tj], 0.f);
                            if (OOG(r) || OOG(g) || OOG(b)) {
                                filmlike_clip(&r, &g, &b);
                            }
                            rtemp[ti * TS + tj] = r;
                            gtemp[ti * TS + tj] = g;
                            btemp[ti * TS + tj] = b;
                        }
                    }
                }

It loops over all pixels, determines if either R, G or B has OOG(x) return true, and then apply filmlike_clip(). So, the OOG(x) function is a template that looks like this:

template <typename T>
constexpr bool OOG(const T &val, const T &high=T(MAXVAL))
{
    return (val < T(0)) || (val > high);
}

Basically, it checks if the supplied value is below 0 or higher than 65535 (MAXVAL is hard-coded). This is the clipping I described. It actually doesn’t detect a true out-of-gamut color, but only out-of-range pixel value beyond the limits of storing in a 16 bit image file. (Remember that RawTherapee does all internal maths in 32 bit floating point precision.)

If such a pixel is detected, this function is applied to it:

static inline void
filmlike_clip_rgb_tone(float *r, float *g, float *b, const float L)
{
    float r_ = *r > L ? L : *r;
    float b_ = *b > L ? L : *b;
    float g_ = b_ + ((r_ - b_) * (*g - *b) / (*r - *b));
    *r = r_;
    *g = g_;
    *b = b_;
}

/*static*/ void
filmlike_clip(float *r, float *g, float *b)
{
    // This is Adobe's hue-stable film-like curve with a diagonal, ie only used for clipping. Can probably be further optimized.
    const float L = 65535.0;
    if (*r >= *g) {
        if (*g > *b) {         // Case 1: r >= g >  b
            filmlike_clip_rgb_tone(r, g, b, L);
        } else if (*b > *r) {  // Case 2: b >  r >= g
            filmlike_clip_rgb_tone(b, r, g, L);
        } else if (*b > *g) {  // Case 3: r >= b >  g
            filmlike_clip_rgb_tone(r, b, g, L);
        } else {               // Case 4: r >= g == b
            *r = *r > L ? L : *r;
            *g = *g > L ? L : *g;
            *b = *g;
        }
    } else {
        if (*r >= *b) {        // Case 5: g >  r >= b
            filmlike_clip_rgb_tone(g, r, b, L);
        } else if (*b > *g) {  // Case 6: b >  g >  r
            filmlike_clip_rgb_tone(b, g, r, L);
        } else {               // Case 7: g >= b >  r
            filmlike_clip_rgb_tone(g, b, r, L);
        }
    }
}

I am sure that I could figure out exactly what this is doing if I took the time, but the take away message is, it doesn’t simply clip. My earlier statement is therefore wrong: values > 1 do not simply become 1, the actual RGB pixel is treated simultaneously and pushed in some way to preserve the RGB ratios.

2 Likes

So, in all sincerity, I think the ‘out-of-gamut’ label, both in German and English, is a misnomer. Unless I am overlooking something, the actual working profile gamut never comes into play here.

It seems @agriggio has done most of the work on this code. Maybe he can comment too?

1 Like

Out of gamut is definitely a misleading name. The whole thing was a sort of ‘hack’ to allow RT to not clip pixel values (to any particular bound), for users who preferred to do the gamut mapping by themselves in an external program. For this to work properly, though, you need to keep your processing to a minimum, otherwise you might introduce bad artifacts (because several tools in the pipeline assume that the values are in a particular range).
But you are right that this doesn’t really take gamut into account, it merely checks ranges. In general, I’d say leave the option on unless you know what you are doing (e.g. you are using RT only to apply raw corrections and demosaicing). Note further that for a proper “unclipped” workflow you need to save as float tiff and use a v4 output profile.
(Fwiw, I’ve removed the option from art – you can still get no clipping if you know how, but this is ot here)

4 Likes

Sorry for the over-generalization in my previous posts. I wanted to keep it simple…

Come to think of it, it might be a good idea to make this checkbox less prominent. In my current installation of RT, it is at the top of the exposure module and tempting to toggle. I am a person who turns it off regularly but it isn’t meant for the majority of users. What do you all think about that?

I think that is best left for a discussion on the GitHub tracker :wink:

How you will know what you are doing if you are not developer and there is no documentation? With time-consuming experimentation, time-consuming hanging in forums, or maybe you expect somehow the interested user to decipher your code?

Every UI exposed control should be properly documented not only for obvious transparency, but to make easier to spot problems/bugs in the software.

I can see myself via experimentation that the clipping occurs somewhere between the Channel Mixer and the Color Toning (Color correction regions) in the pipeline. Both of these tools can work with out of gamut colors.

Sorry boss, you will find my resignation letter on your desk on Monday morning. It’s been such a privilege to work for you – something I clearly do not deserve.

My best wishes and deepest apologies for having let you down.

5 Likes

Same as me then Richard - I always have ‘clip’ check box unchecked.

If you are on Windows or Linux I would suggest you feed RT with your up to date custom monitor profile - I can’t do this directly on my Mac, but I also run RT in Linux via Parallels so I use the monitor profile.

I would suggest using relative colourmetric as the rendering intent using the drop-down menu to the right, where you have perceptual, relative colormetric and absolute colormetric options.

Perceptual rendering squeezes all the colours so they ‘fit in the colourspace bucket’.
Relative faithfully keeps all the existing colours that do ‘fit in the bucket’ and chops off the ones colours that don’t, replacing them with the nearest neighbour colours the do fit.
Absolute tries to scale all the colours to the white point of the colourspace/profile being used - my advice is never use it - anywhere!

The Eagle was shot on a D4 and here’s the D4 colour space being swamped by the much larger ProPhoto colour space - so ‘file colours’ should not clip.

If I now introduce the monitor profile:

you can see where ‘CAMERA capture spectrum’ colours lie outside the monitor ‘colour space’, but never (hardly) outside the working space.

No shot you ever take with your camera will ever contain ALL the colours your camera can capture, so chances are, you’re always looking at correct captured colour - as long as the working space is ProPhoto.
But what you ‘SEE’ on your monitor is a different beast altogether because no monitor can display anything close to ProPhoto. The monitor profile you see here is actually around 109% of Adobe1998RGB, hence some folk still use it as a working space.
Adobe were the first to put paid to that method by using only ProPhoto linear in the background and MelissaRGB in the front GUI.

So, in reality, and using a wide gamut monitor, clipped colours are only in the highlights above 253RGB - and 253 or 254 are recoverable - it’s only 255 that isn’t, and you can take care of that usually with highlight reconstruction. But 255 should not figure in your exposure anyway - it’s only there due to bad metering.

3 Likes

Here are two 32bit float tiff files processed in RT using the same profile except for the Clip out-of-gamut colors. For both I set exposure compensation to +4 stops.
clipped.tif (6.2 MB)
not clipped.tif (6.2 MB)

When you open them in RT, they look equal. But now try to set exposure compensation to -4 stops for both of them :wink:

3 Likes

A good illustration if ever there was one for unticking!

So it begs the question why is it checked by default? And indeed, why it it there at all?

Because not all tools can properly handle the unclipped data properly, just like Alberto already said. So for the safety and sanity of the uninitiated user, it is checked by default.

The whole thing was a sort of ‘hack’ to allow RT to not clip pixel values (to any particular bound), for users who preferred to do the gamut mapping by themselves in an external program. For this to work properly, though, you need to keep your processing to a minimum, otherwise you might introduce bad artifacts (because several tools in the pipeline assume that the values are in a particular range).