RGB Primary Corrections, Saturation

I’ve recently started to play with art, I think it’s a great piece of software and I really like the primaries correction in the channel mixer.
Thanks @agriggio for the good work!

I’ve read the paper linked in this post but it isn’t an easy lecture and I haven’t looked yet into the art’s code, I wanted however to figure out how this tool could works.

Premise:
If we have a photo with a wide gamut color profile ( eg rec.2020 ) and we assign a wrong and narrow color profile ( eg. srgb ) to it, the image will appear less saturated.

original image with rec.2020 profile (and converted to srgb for displaying purpose)

same image with rec.2020 profile and srgb assigned profile

The easy conclusion is that this happens because the rgb primaries are farther away from the center (actually the white point coordinates)

I’ve used vapoursynth for the follow test, more precisely the fmtc , primaries plugin

Here’s his brief description

Performs a gamut conversion given a set of three primary colors and a reference white to another set of primary colors and a target reference white. Illuminant conversions are done using the Bradford method.
All colors are given in xyY colorspace, with their x and y coordinates.

The usage is very simple, it takes the source r,g,b,white-point chromaticity xy coordinates and the destination r,g,b,white-point xy chromaticity coordinates.
This values could be found in the wikipedia page too.

Of course the math for the color space conversion is more complex.

Now finally the interesting part.

The goal to be achieved could be for example the blue desaturation in the srgb color profile.
CIExy1931_Rec_709

What we need are the blue and white point xy coordinates (b=[0.150,0.060], w=[0.3127,0.3290])
CIExy1931_Rec_709-02

Now we have to find the straight line between these two points and choose a point outside the blue-white point segment
CIExy1931_Rec_709-03

In the end we have pratically a different color space
CIExy1931_Rec_709-04

That’s it, now a pratical example.
I’ve exported this image from darktable with the 2020 linear profile (converted to srgb for displaying purpose)

The source xy coordinates (rec.2020 primaries and d65 white point) are
r 0.70792, 0.29203
g 0.17024, 0.79652
b 0.13137, 0.04588
w(d65) 0.31271,0.32902

The two points that we need for the calculations are blue and white, I will not post here the math but it’s easy to calculate the value of y when x is 0.0001(randomly choosen), in this case the result is -0.15895811624.

The destination xy coordinates are
r 0.70792, 0.29203
g 0.17024, 0.79652
b 0.0001, -0.15895811624
w(d65) 0.31271,0.32902

And this is the result (converted to srgb for displaying purpose)

After this discussion I was able in the end to implement a function for add or remove saturation modifying the rgb primaries, it was simpler than was I thought.

raw4_func.py (3.8 KB)

This python script accepts a linear rec.2020 .tif (floating point) and it converts the file to srgb at end.
It requires opencv, numpy and math.

def rgb_primaries_saturation (img,sat_r,sat_g,sat_b,rx,ry,gx,gy,bx,by,wx,wy):

The function itself takes in input sat_r, sat_g, sat_b for the saturation of the rgb primaries, 1 is the neutral value, values higher than 1 will desaturate the color primaries and values lower than 1 will increase the saturation

rx,ry,gx,gy,bx,by,wx,wy are the xy coordinates of the working space

Basically the function does this

  1. finds new rgb xy coordinates with linear interpolation

#rx=0.708
#ry=0.292
#gx=0.170
#gy=0.797
#bx=0.131
#by=0.046
#wx=0.3127
#wy=0.3290

#new red xy chromaticity coordinates
rxnew=sat_r*(rx-wx)+wx
rynew=wy+((rxnew-wx)*(ry-wy))/(rx-wx)

#new green xy chromaticity coordinates
gxnew=sat_g*(gx-wx)+wx
gynew=wy+((gxnew-wx)*(gy-wy))/(gx-wx)

#new blu xy chromaticity coordinates
bxnew=sat_b*(bx-wx)+wx
bynew=wy+((bxnew-wx)*(by-wy))/(bx-wx)

  1. calculate the rgb to xyz matrix from the old xy coordinates
  2. calculate the xyz to rgb matrix with the new rgb coordinates
  3. build the correction matrix “joining” these two matrices

For example with this parameters (increase red saturation, reduce blue saturation)

rgb_primaries_saturation(…,sat_r=0.5,sat_g=1,sat_b=2.5,…)

We get this correction matrix

[[ 7.45518286 2.96645120 2.54481714]
[-2.95987568 1.04150585 2.54481714]
[-2.95987568 5.70137613 1.29598757]]

Before

After the matrix

1 Like

Hi @age, I see what you’re doing and I think I understand your reasoning. But when you distort the entire gamut like this, you will not only change saturation but also hues (in fact you change the chromaticity). Is that okay for your purpose?

Yes, because the hue could be shifted later with a hue equalizer or in the same channel mixer like implemented in ART and Lightroom.

Actually the hue shift from this method is very similar of what happens with the rgb colorfulness sliders in darktable’s color calibration module.

There are some issues wih that module in Darktable, it has very bad behaviour when there are rgb negative values, it renders blue hues like a solid black , this is a big regression for the new unbounded pipeline philosophy.
The second “issue” is that isn’t possible to adjust the rgb hue.

Rawtherapee instead doesn’t has any tool to fight the out of gamut colors and it just renders a blue blob.

So yes it’s okay for my purpose.