C o l o r    M a t h

### Introduction

The emphasis is on converting colors from one color space into another. Algorithms are given in pseudocode. Please don't look here for further explanations. We'll only provide the math.
Nuf said!

 Conversion between RGB and HSV RGB and HSL HSV and HSL RGB and HSI RGB and CIE-XYZ and CIE-L*h*s* CIE-Lhs to RGB CIE-XYZ and CIE-Lab and CIE-Luv Equivalent grey Conversion coefficients RGB/CIE-XYZ Chromatic adaptation - Bradford matrix Primaries and whitepoints RGB and CMYK

### Maths: RGB, HSL, CIE-Lhs

RGB to HSV
When converting from RGB (red green blue) to HSV (hue saturation lightness value) an inelegancy arises: the hues have to be divided into sections of 60 degrees. There are some conversion schemes which look more elegant mathematically, but the best linear transform from RGB to HSL divides the hue range into 6 parts.
First you have to make sure that the RGB values are normalized to values between 0 and 1.
The first conversion step is to find the lightness value. An interesting way of doing this is to convert to CMYK (cyan magenta yellow black). After having found the lightness, we set the lightness artificially to 1 so we can find the saturation. The hue can then be found by setting both lightness and saturation to 1.

Input: values between 0-1 for red (R), green (G) and blue (B).
Output: values for hue (H, 0-360°), saturation (S, 0-1), lightness value (V, 0-1).

```
C = 1-R; M = 1-G; Y = 1-B; K = min(C,M,Y)
--Normalization
C = min(1,max(0,C-K)); M = min(1,max(0,M-K)); Y = min(1,max(0,Y-K)); K = min(1,max(0,K))
V = 1-K  --lightness value
if V=0 then return "0,0,0"  --pure black
--Find CMY/RGB at K=0/V=1 in order to get S
C = min(1,C/(1-K)); M = min(1,M/(1-K)); Y = min(1,Y/(1-K))
R = 1-C; G = 1-M; B = 1-Y
lowest = min(R,G,B)
highest = max(R,G,B)
S = 1-lowest  --saturation
if S=0 then
H=0 --C,M,Y were 0, but K > 0, so a grey; let's set H=0
else
--Set V=1, S=1 in order to find the hue
--We do this by putting 1 into the lowest value, 0 into the highest value
--and an S-corrected value into the middle value.
if R = lowest then
RR = 0
if G = highest then GG = 1; BB = B/S+1-1/S else BB = 1; GG = G/S+1-1/S
else
if G = lowest then
GG = 0
if R = highest then RR = 1; BB = B/S+1-1/S else BB = 1; RR = R/S+1-1/S
else
BB = 0
if R = highest then RR = 1; GG = G/S+1-1/S else GG = 1; RR = R/S+1-1/S
end if
end if
--Now we're ready for the hue
--Hue will be a fraction [0-1]
if RR=1 then
if BB=0 then --H: 0-60°
H = GG/6
else --H: 300-360°
H = 1-BB/6
end if
else
if RR=0 then
if GG=1 then --H: 120-180°
H = 1/3+BB/6
else --H: 180-240°
H = 2/3-GG/6
end if
else
if BB=0 then --H: 60-120°
H = 1/3-RR/6
else --H: 240-300°
H = 2/3+RR/6
end if
end if
end if

end if
H = H*360 -- or another scale factor, if any
return H,S,V

```
By the way, in an earlier version of this text, this function was called RGB to HSL and the inverse (see the next function) was called HSL to RGB. We developed this function, based on the HSL color model from the Apple color wheel in earlier days. In the mean time we have noted that the results of this conversion are equivalent to the function RGB_To_HSV and HSV_To_RGB, mentioned in Foley, Van Dam, et alii (Computer graphics, principles and practice in C, 2nd edition), which is cited more often.

HSV to RGB
If you have hue values expressed as an angle, convert them first to a value between 0-1 by dividing by 360.

Input: values between 0-1 for hue (H), saturation (S) and lightness value (V).
Output: values between 0-1 for red (R), green (G) and blue (B).

```
--H  [0-1] is divided into 6 equal sectors.
--From within each sector the proper conversion function is called.
if H < 1/6 then
return H1(H,S,V)
else
if H < 1/3 then
return H2(H,S,V)
else
if H < 1/2 then
return H3(H,S,V)
else
if H < 2/3 then
return H4(H,S,V)
else
if H < 5/6 then
return H5(H,S,V)
else
return H6(H,S,V)
end if
end if
end if
end if
end if

--Here are the 6 conversion functions.
--First H,1,1 is converted to R,G,B.
--Then a correction for the actual S.
--At last a correction for the actual V.
function H1 H,S,V
R = 1; G = 6*H; B = 0
G = G*S + 1 - S; B = B*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H1
function H2 H,S,V
R = 1-6*(H - 1/6); G = 1; B = 0
R = R*S + 1 - S; B = B*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H2
function H3 H,S,V
R = 0; G = 1; B = 6*(H - 1/3)
R = R*S + 1 - S; B = B*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H3
function H4 H,S,V
R = 0; G = 1-6*(H - 1/2); B = 1
R = R*S + 1 - S; G = G*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H4
function H5 H,S,V
R = 6*(H - 2/3); G = 0; B = 1
R = R*S + 1 - S; G = G*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H5
function H6 H,S,V
R = 1;  G = 0; B = 1-6*(H - 5/6)
G = G*S + 1 - S; B = B*S + 1 - S
R = R*V; G = G*V; B = B*V
return R,G,B
end H6

```

 To the index at the top of the page

RGB to HSL
The following two routines convert between RGB and HSL, where H is the hue, S is the saturation and L is the lightness.

Input: values between 0-1 for red (R), green (G) and blue (B).
Output: values between 0-360 for hue (H), and between 0-1 for saturation (S) and lightness (L).

```
rgbmax = max(r,g,b); rgbmin = min(r,g,b);
L = (rgbmax+rgbmin)/2;
if rgbmax=rgbmin then
S = 0
else
del = rgbmax-rgbmin
if L<=0.5 then
S = del/(rgbmax+rgbmin)
else
S = del/(2-rgbmax-rgbmin)
end if
end if
if rgbmax=rgbmin then
H = 0
else
rc = (rgbmax-r)/del
gc = (rgbmax-g)/del
bc = (rgbmax-b)/del
if r=rgbmax then
H = bc-gc
else
if g=rgbmax then
H = 2+rc-bc
else
H = 4+gc-rc
end if
end if
H = H*60;
if H>=360 then H -= 360;
if H<0 then H += 360;
end if
return H,S,L

```

HSL to RGB
Input: values between 0-360 for hue (H), and between 0-1 for saturation (S) and lightness (L).
Output: values between 0-1 for red (R), green (G) and blue (B).

```
if L<=0.5 then m2 = L+L*S else m2 = L+S-L*S
m1 = 2*L-m2
if S=0 then
r = L; g = L; b = L
else
r = hsl_value(m1,m2,H+120)
g = hsl_value(m1,m2,H)
b = hsl_value(m1,m2,H-120)
end if
return r,g,b

function hsl_value n1,n2,h
-- Auxiliary to HSL to RGB
hue = h
if hue>=360 then hue -= 360
if hue<0 then hue += 360
if hue<60 then
res = n1+(n2-n1)*hue/60
else
if hue<180 then
res = n2
else
if hue<240 then
res = n1+(n2-n1)*(240-hue)/60
else
res = n1
end if
end if
end if
return res
end hsl_value

```

 To the index at the top of the page

HSV to HSL direct
It is possible to perform a direct conversion between the color models HSV and HSL.

Input and output: values between 0-1 for hue (H), saturation (S) and lightness value (V or L).

```
function HSV_HSL h,s,v
hh = h
L = (2-s)*v
ss = s*v
if L<=1 then ss /= L else ss /= 2-L
L /= 2
return hh,ss,L
end HSV_HSL

function HSL_HSV hh,ss,L
h = hh
L *= 2
if L<=1 then ss *= L else ss *= 2-L
v = (L+ss)/2
s = (2*ss)/(L+ss)
return h,s,v
end HSL_HSV

```

 To the index at the top of the page

RGB to HSI
There is yet another color model, based on hue, saturation and a representation of luminance. This is a non-linear model.

Input: values between 0-1 or possibly larger than 1 for red (R), green (G) and blue (B).
Output: values between 0-360 for hue (H), and between 0-1 for saturation (S) and intensity (I).

```
rd = 180/pi -- from radians to degrees
th = acos(0.5*((r-g)+(r-b))/sqrt((r-g)*(r-g)+(r-b)*(g-b)))
if b<=g then
h = th*rd
else
h = (2*pi-th)*rd
end if
if h<0 then h += 360
if h>=360 then h -= 360
s = 1-(3/(r+g+b))*min(r,g,b)
i = (r+g+b)/3
-- one could use a better luminance conversion like
-- L = 0.299*r+0.587*g+0.114*b
-- but then the inverse function (below) has to be adjusted
return h,s,i

```

HSI to RGB
The functions HSI_RGB and RGB_HSI are symmetrical with respect to eachother, but sometimes the calculated r,g,b values will get larger than 1.0. If they are clipped to 1.0, then a back conversion to h,s,i will not produce the same values as the h,s,i values that were used to convert to r,g,b.

Input: values between 0-360 for hue (H), and between 0-1 for saturation (S) and intensity (I).
Output: values between 0-1 or larger than 1 for red (R), green (G) and blue (B).

```
dr = pi/180 -- from degrees to radians
if h>=0 & h<120 then
b = i*(1-s)
r = i*(1+s*cos(h*dr)/cos((60-h)*dr))
g = 3*i-(r+b)
else
if h>=120 & h<240 then
hp = h-120
r = i*(1-s)
g = i*(1+s*cos(hp*dr)/cos((60-hp)*dr))
b = 3*i-(r+g)
else
if h>=240 & h<=360 then
hp = h-240
g = i*(1-s)
b = i*(1+s*cos(hp*dr)/cos((60-hp)*dr))
r = 3*i-(g+b)
else
-- h was in the wrong region
r = 0; g = 0; b = 0
end if
end if
end if
return r,g,b

```

 To the index at the top of the page

RGB to CIE-XYZ and CIE-L*h*s*
Before converting from RGB to CIE-XYZ one has to choose the XYZ description of a suitable set of pure RGBs. Also one has to choose a whitepoint which is the XYZ definition of an illumination.
We choose the RGB phosphors for PAL television as defined by ITU/EBU 3213, and the standard CIE D65 whitepoint.
With these choices the XYZ values can be computed as follows:

Input: values between 0-1 for red (R), green (G) and blue (B).
Output: values for CIE-XYZ coordinates >= 0.

```
X = 0.430574*R + 0.341550*G + 0.178325*B
Y = 0.222015*R + 0.706655*G + 0.071330*B
Z = 0.020183*R + 0.129553*G + 0.939180*B
```
The other way around, if one has XYZ and wants to compute the ITU/EBU 3213 - D65 RGBs:

Input: values for CIE-XYZ coordinates.
Output: values for red (R), green (G) and blue (B), scaled to the range 0-1, but might go past these limits for some XYZ values.

```
R =  3.063219*X -1.393326*Y -0.475801*Z
G = -0.969245*X +1.875968*Y +0.041555*Z
B =  0.067872*X -0.228833*Y +1.069251*Z
```
Later we will explain how the coefficients are calculated.

Please don't make the mistake to drop decimals in the conversion process. The coefficients in above conversion matrices vary wildly (between 0.020183 and 0.939180 for the first matrix) which means that you quickly loose accuracy in the back conversion.

After converting RGB to CIE-XYZ one has to convert to CIE-Lhs. This can be done via CIE-Lab or via CIE-Luv, which is followed here.
First we convert the XYZ to Luv values. The CIE-L*u*v* color space has a more even distribution of color differences. We drop the * because this interferes with the multiplication sign.

Input: values for CIE-XYZ coordinates >= 0.
Output: values between 0-100 for CIE lightness (L), and CIE coordinates u, v real.

```
u' = 4X/(X+15Y+3Z)
v' = 9Y/(X+15Y+3Z)
if Y > 216/24389 then
L = 116*(Y^(1/3)) - 16
else
L = Y*24389/27
end if
--Now we need the values un, vn of the chosen whitepoint
u = 13L(u'-un)
v = 13L(v'-vn)
```
At this moment the XYZ values are converted to Luv values.
The values un, vn of the D65 whitepoint are:
```
un = 0.197833
vn = 0.468330
```
How did we get those numbers?
We know the x,y coordinates of the D65 whitepoint (see the table below):
```
D65x = 0.312713; D65y = 0.329016;
```
First, we convert the x,y coordinates to XYZ:
```
Yn = 1; Xn = D65x*Yn/D65y; Zn = (1-D65x-D65y)*Yn/D65y;
```
And then to u',v':
```
factor2 = Xn+15*Yn+3*Zn;
un = 4*Xn/factor2;
vn = 9*Yn/factor2;
```
Result:
```
un = 0.19783304
vn = 0.46833047
```

Converting from Luv to Lhs is a matter of transforming from cartesian to polar coordinates.

Input: CIE coordinates u, v.
Output: values for CIE lightness (L) 0-100, CIE hue (0-360°) and CIE saturation (>= 0).

```
h = atan2(v,u) modulo 360° --hue
c = sqrt(u*u + v*v)  --chroma (colorfulness)
s = c/L  --saturation; if L=0, take s=0
```
Please don't use the normal arctangent function. With the atan2 function the sign (+/-) is correct. The atan2 function is defined as follows, this in case your math tool has no atan2 function.

Input: scalars v, u (real).

```
function atan2 v,u
if u=0 and v=0 then: not defined, but you could use 0 as answer
if u=0 then
if v > 0 then pi/2 else -pi/2
if u > 0 then atan(v/u)  --the normal atan function
if u < 0 then
if v >= 0 then atan(v/u) + pi else atan(v/u) - pi
```
The production of atan2 is an angle in radians. Probably you want to convert this to an angle in degrees. Do this by multiplying with 180/pi.
The atan2 function in programming languages usually gives a value between -pi and +pi (-180° and +180°). Shift this to a value between 0 and 360° by adding 360 to the calculated value in degrees.
The value of H is one between and including 0 and 360°. The value of L lies between and including 0 and 100. The values of u and v, and so the values of c and s are greater than or equal to 0.

 To the index at the top of the page

CIE-Lhs to RGB
Many explanations can be found in the previous sections, so here we'll be short.
Probably the value of h (hue) has to be converted to an angle in radians as used by the sine and cosine functions. If you have an angle in degrees, multiply with pi/180 to convert to radians.

Input: values for CIE lightness (L, 0-100), CIE hue (H, radians 0-2*pi), and CIE saturation (S, >= 0).
Output: values between 0-1 for red (R), green (G) and blue (B).

```
c = s*L
u = c*cos(h)
v = c*sin(h)
u' = un + u/(13*L)
v' = vn + v/(13*L)
--See above for the values of un, vn (whitepoint)
if L > 8 then
Y = ((L+16)/116)^3
else
Y = L*27/24389
end if
X = 9*Y*u'/(4*v')
Z = Y*(12-3*u'-20*v')/(4*v')
```
Right now we have the XYZ values. Earlier we saw how to convert to RGB:
```
R =  3.063219*X -1.393326*Y -0.475801*Z
G = -0.969245*X +1.875968*Y +0.041555*Z
B =  0.067872*X -0.228833*Y +1.069251*Z
```
Take note that this is only valid for the chosen RGB primary colors (ITU/EBU 3213 PAL TV) and the chosen whitepoint (D65).

 To the index at the top of the page

CIE-XYZ and CIE-Lab
If you don't like the CIE-Luv color space, use CIE-Lab (CIE-La*b*).
Conversion between CIE-XYZ and CIE-Lab goes as folows.

From XYZ to Lab
Input: CIE-XYZ values.
Output: CIE-Lab values. L stands for the psychometric lightness and is usually a value between 0 and 100; a and b are reals that can be negative.
The coordinates of the chosen whitepoint are Xw, Yw, Zw.

```
if Y/Yw >= 216/24389 then
L = 116(Y/Yw)^(1/3) - 16
else
L = (Y/Yw)*24389/27
end if
LF = (L+16)/116
if X/Xw >= 216/24389 then
a = 500((X/Xw)^(1/3) - LF)
else
a = 500((841/108)(X/Xw) - LF + 4/29)
end if
if Z/Zw >= 216/24389 then
b = 200(LF - (Z/Zw)^(1/3))
else
b = 200(LF - 4/29 - (841/108)(Z/Zw))
end if
```
If needed, it is possible to convert the CIE-Lab coordinates to polar coordinates, expressing hue and chroma.
```
hab = atan2(b, a) -- hue angle
cab = sqrt(a*a + b*b) -- chroma
```
The atan2 function is defined above. Note that the CIE-Lhc values obtained here, are not the same as the CIE-Lhs values obtained earlier, through the conversion via CIE-Luv.

From Lab to XYZ
Input: CIE-Lab values. L stands for the psychometric lightness and is usually a value between 0 and 100.
Output: CIE-XYZ values.
The coordinates of the chosen whitepoint are Xw, Yw, Zw.

```
LF = (L+16)/116
if L >= 29b/50 + 8 then
Z/Zw = (LF - b/200)^3
else
Z/Zw = (108/841)(LF - 4/29 - b/200)
end if
if L >= 8 - 29a/125 then
X/Xw = (a/500 + LF)^3
else
X/Xw = (108/841)(a/500 + LF - 4/29)
end if
if L >= 8 then
Y/Yw = LF^3
else
Y/Yw = 27L/24389
end if
```

If you don't know the XYZ coordinates of the whitepoint, you can compute them from xy coordinates:

```
Y = 1;  X = x*Y/y;  Z = (1-x-y)*Y/y
```

 To the index at the top of the page

Equivalent grey
You may have noticed that the downloadable color patches have an equivalent grey number. What is this?
Suppose we turn off all color, so we only see greys. The grey value we see for a certain color is the equivalent grey value. A problem is: how is this value defined? There are many definitions.
A very simple definition is:

```
grey = (R + G + B)/3
```
An even simpler one is:
```
grey = the value of G
```
The most used definition is the ITU-601-1 definition:
```
grey = 0.299*R + 0.587*G + 0.114*B
```
For instance Kodak Photo CD and PostScript/PDF use this definition.
This definition is directly derived from the CIE-XYZ 1931 specification. If we use all decimals from that specification we get:
```
grey = 0.298954*R + 0.586434*G + 0.114612*B
```
This is the equivalent grey definition used in the downloadable color patches.
There are more grey definitions. For instance the ITU-709 definition for American NTSC television systems:
```
grey = 0.213*R + 0.715*G + 0.072*B
```
The definition used for European PAL television systems is an ITU/EBU 3213 standard:
```
grey = 0.222*R + 0.707*G + 0.071*B
```
Input for al grey functions: values between 0-1 for red (R), green (G) and blue (B).
Output: grey value between 0-1.

 To the index at the top of the page

Conversion coefficients
Earlier we saw that under certain circumstances the conversion from RGB to CIE-XYZ can be performed with the following set of equations.

```
X = 0.430574*R + 0.341550*G + 0.178325*B
Y = 0.222015*R + 0.706655*G + 0.071330*B
Z = 0.020183*R + 0.129553*G + 0.939180*B
```
The 'certain circumstances' are determined by the color coordinates of the whitepoint of the illuminant and the color coordinates of the red, green and blue producing substances, the primaries. That's where those numbers come from. But how are they computed?

Let's use matrix algebra. The equation set can be rewritten as a matrix multiplication.

```
|X| = |0.430574 0.341550 0.178325| |R|
|Y| = |0.222015 0.706655 0.071330| |G|
|Z| = |0.020183 0.129553 0.939180| |B|
```
By the way, the brown equations are formed with a <pre> instruction, so your browser has to use a monospace font in order to display the matrices readable, i.e. with aligning vertical lines.

Now we are going to derive this coefficients matrix.
First we start with what we know or should know, the color coordinates of the whitepoint and the primaries. Let's say that the xy-coordinates of the RGB-primaries and the whitepoint are:

```
xr=0.64;     yr=0.33
xg=0.29;     yg=0.60
xb=0.15;     yb=0.06
xw=0.312713; yw=0.329016
```
The xy-coordinates have to be converted to XYZ coordinates. Since the xy-coordinates contain no explicit luminance information, the Y-coordinates of the whitepoint and the primaries are usually set to 1. The XYZ-coordinates can be computed as follows.
```
Y = 1; X = x*Y/y; Z = (1-x-y)*Y/y
```
Let's call the RGB coefficients matrix `RGB`, constructed as:
```
|Xr Xg Xb|
RGB = |Yr Yg Yb|
|Zr Zg Zb|
```
Now we have to invert this matrix. Usually this is done with the aid of a programming environment including a library of matrix calculations. If you would like to write your own routine, a 3x3 matrix can be inverted in the following manner.
```
|a b c|
M = |d e f|
|g h i|

det(M) = a(ei-hf) - b(di-gf) + c(dh-ge) --determinant
if det(M) is not 0 then the inverted matrix exists

-1       1    | (ei-hf) -(bi-hc)  (bf-ec)|
M    =  ------ |-(di-gf)  (ai-gc) -(af-dc)|
det(M) | (dh-ge) -(ah-gb)  (ae-db)|
```
Right now we have obtained RGB-1, the inverse of the RGB coefficients matrix.
We form the whitepoint matrix:
```
|Xw|
W = |Yw|
|Zw|
```
and then we matrix-multiply the whitepoint matrix by the inverse of the RGB coefficients matrix:
```
A = RGB-1 . W
```
Matrix A is a matrix with 3 rows and 1 column, representing the achromatic correction. In order to obtain the conversion matrix, we have to scalar-multiply the RGB-matrix with the transpose of the correction matrix:
```
RGBtoXYZ = RGB * AT
```
The transpose of the matrix A is a 1-row matrix:
```
AT = |Ar Ag Ab|
```
To be more clearly: if we scalar-multiply this with matrix RGB, we get:
```
|Xr*Ar Xg*Ag Xb*Ab|
RGBtoXYZ = |Yr*Ar Yg*Ag Yb*Ab|
|Zr*Ar Zg*Ag Zb*Ab|
```
So the conversion from RGB to XYZ is in matrix terms:
```
|X|              |R|
|Y| = RGBtoXYZ . |G|
|Z|              |B|
```
The inverse of the matrix RGBtoXYZ is the coefficients matrix, needed to convert from XYZ to RGB.
Interestingly, the second row of matrix RGBtoXYZ contains the Y-coefficients and those are the luminance coefficients one could use to compute the equivalent grey value with!

Let's give a numerical example, so you can check your programming.

```
xr=0.64;     yr=0.33
xg=0.29;     yg=0.60
xb=0.15;     yb=0.06
xw=0.312713; yw=0.329016

|1.93939 0.48333  2.50000|
RGB = |1.00000 1.00000  1.00000|
|0.09091 0.18333 13.16667|

-1   | 0.68008 -0.30934 -0.10563|
RGB   = |-0.68492  1.32566  0.02937|
| 0.00484 -0.01632  0.07627|

|0.95045|
W = |1.00000|
|1.08892|

AT = |0.22201 0.70666 0.07133|

|0.43057 0.34155 0.17833|
RGBtoXYZ = |0.22201 0.70666 0.07133|
|0.02018 0.12955 0.93918|

-1    | 3.06322 -1.39333 -0.47580|
XYZtoRGB = RGBtoXYZ    = |-0.96924  1.87597  0.04156|
| 0.06787 -0.22883  1.06925|
```
We have seen the coefficients matrices RGBtoXYZ and XYZtoRGB before, see the algorithms for converting from RGB to CIE-XYZ.
Compare the second row of RGBtoXYZ with the last example of the equivalent grey equations above, and copied here:

The definition used for European PAL television systems is an ITU/EBU 3213 standard:

```
grey = 0.222*R + 0.707*G + 0.071*B
```

With the algorithms given above, it is possible to convert a set of XYZ-coordinates to another whitepoint or another set of primaries. First convert with the current coefficients matrix XYZtoRGB1 back to RGB, then calculate a new coefficients matrix RGBtoXYZ2 for the conversion from RGB to XYZ with the new whitepoint and-or the new RGB primaries and do the conversion back to X'Y'Z'.
Conversion of RGB coordinates to another set is also possible. Convert with the current matrix RGBtoXYZ1 to XYZ and convert back with a new matrix XYZtoRGB2 to RGB.

 To the index at the top of the page

Say a color with coordinates Xs,Ys,Zs has been measured with whitepoint Xws,Yws,Zws, but has to be recalculated for the same color representation under whitepoint Xwd,Ywd,Zwd. (Subscript s stands for source and d for destination.) This can be achieved by transforming the coordinates with a scale matrix. The best choice seems to be the Bradford scale matrix. This is the one that is used in, for instance, Adobe Photoshop.
The transformation goes as follows.
```
|Xd|      |Xs|
|Yd| = |M||Ys|
|Zd|      |Zs|
```
|M| is the Bradford matrix, which is calculated as follows:
```
|ρd/ρs   0     0  |
|M| = |MA|-1 |  0   γd/γs   0  | |MA|
|  0     0   βd/βs|
```
|MA| is the Bradford scale matrix and |MA|-1 is its inverse matrix.
The coordinates ρ,γ,β are calculated as follows:
```
|ρs|       |Xws|
|γs| = |MA||Yws|
|βs|       |Zws|

|ρd|       |Xwd|
|γd| = |MA||Ywd|
|βd|       |Zwd|
```
```
| 0.8951  0.2664 -0.1614|
|MA| = |-0.7502  1.7135  0.0367|
| 0.0389 -0.0685  1.0296|
```
And the (calculated) inverse is:
```
| 0.9869929 -0.1470543  0.1599627|
|MA|-1 = | 0.4323053  0.5183603  0.0492912|
|-0.0085287  0.0400428  0.9684867|
```

Example
```
-- Let's take the white point D65 as source and D50 as destination.
-- The coordinates XYZ are:
Xws = 0.95047; Yws = 1; Zws = 1.08883;
Xwd = 0.96422; Ywd = 1; Zwd = 0.82521;
-- The calculated rho, gamma and beta of the source are:
rhoS = 0.941428535
gamS = 1.040417467
betS = 1.089532651
-- The calculated rho, gamma and beta of the destination are:
rhoD = 0.996284428
gamD = 1.020427363
betD = 0.818644374
-- From this the Bradford matrix is calculated as (only 7 decimals are shown):
| 1.0478113  0.0228865 -0.0501269|
| 0.0295424  0.9904845 -0.0170491|
|-0.0092345  0.0150436  0.7521316|

-- Let's take a color, for instance the red primary of the Adobe RGB (1988) colors.
-- These primaries are measured with respect to the D65 white point,
-- and we shall recalculate the red to the D50 white point.
-- Adobe RGB (1988) - D65, R part in xyY coordinates:
Rx = 0.64; Ry = 0.33; RY = 0.297361;
-- Convert to XYZ:
RX = Rx*RY/Ry = 0.5767001
RZ = (1-Rx-Ry)*RY/Ry = 0.0270328
-- Scale RX,RY,RZ with the Bradford matrix:
RXb = 0.6097234
RYb = 0.3111077
RZb = 0.0194801
-- Convert this back to xyY coordinates:
XYZ = RXb+RYb+RZb;
Rxb = RXXd/XYZ = 0.6484273
Ryb = RYYd/XYZ = 0.3308561
RYb = 0.3111077

```

Primaries and whitepoints

A table with the xy-coordinates of the most used primaries and whitepoints.
The gamma-correction is usually defined as 1/0.45 = 2.2222, so use for very critical work this value instead of the 2.2 from the table. Gamma-correction is not dealt with in this tutorial at the moment.

PrimariesWhitepointGammaRGB (x,y) coordinates
PAL, SECAM, EBU/ITUD652.2r: (0.64,0.33) g: (0.29,0.60) b: (0.15,0.06)
NTSC (1953)CIE C2.2r: (0.67,0.33) g: (0.21,0.71) b: (0.14,0.08)
NTSC (modern)D652.2r: (0.630,0.340) g: (0.310,0.595) b: (0.155,0.070)
SMPTE-C, CCIR 601-1D652.2r: (0.630,0.340) g: (0.310,0.595) b: (0.155,0.070)
Apple RGB, TrinitronD651.8r: (0.625,0.34) g: (0.28,0.595) b: (0.155,0.070)
sRGB, HDTV, ITU-R BT.709D652.2r: (0.64,0.33) g: (0.30,0.60) b: (0.15,0.06)
CIE RGBCIE E2.2r: (0.73467,0.26533) g: (0.27376,0.71741) b: (0.16658, 0.0088600)
Adobe RGB (1998)D652.19921875r: (0.64,0.33) g: (0.21,0.71) b: (0.15,0.06)

The CIE whitepoints are defined for some standard illuminants.
The D-series are supposed to represent black body radiators with a temperature of 100 times the given number, in Kelvin.

Whitepointxy-coordinates
CIE A (tungsten lamp)(0.44757,0.40745)
CIE B (direct sunlight)(0.34842,0.35161)
CIE C (average daylight with sun)(0.310063,0.316158)
CIE E (standard reference)(1/3,1/3)
D50(0.34567,0.35850)
D55 (photography, cloudy daylight)(0.33242,0.34743)
D65 (standard daylight)(0.312713,0.329016)
D75(0.29902,0.31485)
D93 (old CRT monitors)(0.2848,0.2932)

RGB and CMYK

In principle it is very difficult to convert between RGB (red, green, blue) and CMYK (cyan, magenta, yellow, black). There is simply too much difference between the colour rendering methods in these colour spaces. In the RGB space colours are renderend by mixing the light of three colours of light producing dots, whereas in the CMYK space light from a light source is selectively reflected from ink, printed on a medium like paper. If one measures how a large set of colours are actually rendered by a specific CMYK printing process, a mathematical model can be constructed and conversion matrices can be calculated. With these matrices RGB colours can be converted to CMYK colours, but only for the printing process at hand.
Without looking at the intricacies of the rendering process, it is possible to define the following theoretical conversion algorithms.

RGB to CMYK

The R,G,B, C,M,Y,K values are between 0 and 1.
Simple model.

```
cp = 1-r; mp = 1-g; yp = 1-b;
kp = min(cp,mp,yp); t = 1-kp;
if t=0 then
c = 0; m = 0; y = 0;
else
c = (cp-kp)/t; m = (mp-kp)/t; y = (yp-kp)/t;
end if
k = kp;
return [c,m,y,k]
```
Better model: simplified form of DeviceRGB to DeviceCMYK from Adobe PostScript.
```
cp = 1-r; mp = 1-g; yp = 1-b;
kp = min(cp,mp,yp);
c = min(1,max(0,cp-kp));
m = min(1,max(0,mp-kp));
y = min(1,max(0,yp-kp));
k = min(1,max(0,kp));
return [c,m,y,k]
```
Including undercolor removal (ucr) and black generation (bg) as defined in PostScript, this would be:
```
cp = 1-r; mp = 1-g; yp = 1-b;
kp = min(cp,mp,yp);
c = min(1,max(0,cp-ucr(kp)));
m = min(1,max(0,mp-ucr(kp)));
y = min(1,max(0,yp-urc(kp)));
k = min(1,max(0,bg(kp)));
return [c,m,y,k]
```
The ucr and bg functions are device/process specific.

CMYK to RGB

The R,G,B, C,M,Y,K values are between 0 and 1. Simple model.

```
t = 1-k;
r = t*(1-c); g = t*(1-m); b = t*(1-y);
return [r,g,b]
```
Better model: DeviceCMYK to DeviceRGB from Adobe PostScript.
```
r = 1-min(1,c+k);
g = 1-min(1,m+k);
b = 1-min(1,y+k);
return [r,g,b]
```

This concludes the maths section of the Color Design Tutorial.

 To the index at the top of the page

Although this page has been put together with the utmost care, errors are possible. The author cannot be held liable for any possible damages, caused by using the information on this page or any related page.
The copyright (©) of these pages and any other commercial rights are fully owned by the author: Oscar van Vlijmen.
Please address questions, reactions, suggestions, corrections and other constructive remarks to the email address you can construct from:
ovv (at) hetnet.nl

© Oscar van Vlijmen, April 2001
Date of last modification: 2012-11-18