13
$\begingroup$

I was recently making a function that needed to convert between several different colorspaces, and I decided to do it manually. @J.M. used many built-in functions to accomplish the same goal. When I was trying to delve in and see if they produced the same results, I found that visually they did, but numerically there were differences, and so I tried to track them down.

In my code, I needed a function to convert from RGB to XYZ colorspaces, and I used the recipe described on this Wikipedia page, which is the same as is used by EasyRGB, and appears in this paper.

The recipe goes as follows: first, the RGB values are converted to "linear RGB" via

$$C_\mathrm{linear}= \begin{cases}\frac{C_\mathrm{srgb}}{12.92}, & C_\mathrm{srgb}\le0.04045\\ \left(\frac{C_\mathrm{srgb}+a}{1+a}\right)^{2.4}, & C_\mathrm{srgb}>0.04045 \end{cases}$$

and then the XYZ values are obtained from a linear transformation,

$$\begin{bmatrix} X\\Y\\Z\end{bmatrix}= \begin{bmatrix} 0.4124&0.3576&0.1805\\ 0.2126&0.7152&0.0722\\ 0.0193&0.1192&0.9505 \end{bmatrix} \begin{bmatrix} R_\mathrm{linear}\\ G_\mathrm{linear}\\ B_\mathrm{linear}\end{bmatrix}$$

So I define this as a function rgb2xyz,

rgb2xyz[r_, g_, b_] := Module[
   {transm, rl, gl, bl},
   {rl, gl, bl} = If[# > .04045,
       ((# + 0.055)/1.055)^2.4,
       #/12.92] & /@ {r, g, b};
   transm = {{0.4124, 0.3576, 0.1805}, 
             {0.2126, 0.7152, 0.0722}, 
             {0.0193, 0.1192, 0.9505}};
   transm.{rl, gl, bl}
   ];

and then I can apply it to a test color,

testcolor = {43, 15, 155}/255.;
rgb2xyz @@ testcolor
(* {0.0708348, 0.032218, 0.312589} *)

Then I realized there is a ColorConvert function, and test it against my function.

List @@ ColorConvert[RGBColor @@ testcolor, XYZColor]
(* {0.0592725, 0.0286686, 0.234891} *)

which seems to be a reasonably large discrepancy. Visually, I can see a very small difference

XYZColor @@@ {%, %%}

enter image description here

When I go to other websites, like ColorMine or EasyRGB their results agree with mine completely (although they scale their results by a factor of 100).

What is going on here? Is Mathematica using a different RGB colorspace than sRGB?

$\endgroup$
  • 1
    $\begingroup$ Just a comment, on last EWTC one of the developers said sRGB is what MMA is using. $\endgroup$ – Kuba Dec 10 '15 at 13:00
  • $\begingroup$ @Kuba, Thanks - I just found that in the documentation for ColorConvert $\endgroup$ – Jason B. Dec 10 '15 at 13:06
5
$\begingroup$

Just for reference purposes, here is a manual re-implementation of ColorConvert[color, "RGB" -> "XYZ"]:

(* whitepoints *)
d65 = {0.95047, 1., 1.08883}; d50 = {0.96422, 1., 0.82521};

(* cone response domain *)
bradford = {{0.8951, 0.2664, -0.1614},
            {-0.7502, 1.7135, 0.0367},
            {0.0389, -0.0685, 1.0296}};

(* inverse companding function *)
InversesRGBGamma = Function[x, Piecewise[{{x/12.92, x <= 0.04045}},
                                         ((x + 0.055)/1.055)^2.4], Listable];

(* chromaticity coordinates of primaries (RGB) *)
srgb = {{0.64, 0.33}, {0.3, 0.6}, {0.15, 0.06}};

(* chromatic adaptation matrix, D65 to D50 *)
d65tod50 = LinearSolve[bradford, DiagonalMatrix[(bradford.d50)/(bradford.d65)].bradford];

(* RGB -> XYZ matrix *)
r2x = With[{xyz = Transpose[{#1/#2, 1., (1 - #1 - #2)/#2} & @@@ srgb]}, 
           xyz.DiagonalMatrix[LinearSolve[xyz, d65]]];

rgb2xyz[col_RGBColor] := d65tod50.r2x.InversesRGBGamma[List @@ col]

Example:

rgb2xyz /@ ColorData[61, "ColorList"]
   {{0.199667, 0.106547, 0.00721723}, {0.073949, 0.056383, 0.147498},
    {0.823078, 0.923995, 0.234938}, {0.226765, 0.233149, 0.365262},
    {0.099347, 0.136449, 0.027346}, {0.521978, 0.511432, 0.0633633},
    {0.0405846, 0.033663, 0.113543}, {0.3308, 0.169661, 0.0191982},
    {0.671893, 0.718828, 0.33747}}

List @@@ ColorConvert[ColorData[61, "ColorList"], XYZColor]
   {{0.199667, 0.106547, 0.00721724}, {0.0739489, 0.0563829, 0.147498},
    {0.823078, 0.923995, 0.234938}, {0.226765, 0.233149, 0.365262},
    {0.099347, 0.136449, 0.027346}, {0.521978, 0.511432, 0.0633633},
    {0.0405845, 0.033663, 0.113543}, {0.3308, 0.16966, 0.0191983},
    {0.671893, 0.718828, 0.33747}}

There are discrepancies, but they seem slight.


For completeness, here is the manual "XYZ" -> "RGB" conversion:

sRGBGamma = Function[x, With[{z = Abs[x]},
                             Sign[x] Piecewise[{{12.92 z, z <= 0.0031308}},
                                               1.055 z^(1/2.4) - 0.055]],
                     Listable];

d50tod65 = LinearSolve[bradford, DiagonalMatrix[(bradford.d65)/(bradford.d50)].bradford];

xyz2rgb[col_XYZColor] :=
   Clip[sRGBGamma[LinearSolve[r2x, d50tod65.(List @@ col)]], {0., 1.}]
$\endgroup$
  • $\begingroup$ Nice! I had something similar here, yours is easier to follow what is happening though (and I multiplied the answer by 100 in trying to match the recipe in Moreland's paper) $\endgroup$ – Jason B. Jun 15 '16 at 13:20
  • $\begingroup$ I figured a Mathematica summary of the stuff in Lindbloom's website would be useful for those trying to debug their color conversions. I believe we've both been bitten by mistaken whitepoint assumptions, and I'm sure we won't be the only ones. $\endgroup$ – J. M. will be back soon Jun 15 '16 at 13:23
12
$\begingroup$

Hopefully someone else can shed more light on this, someone with more expertise on colorspaces, but I was able to find out a little bit more. I found Bruce Lindbloom's website which lists many different transformation matrices for going from RGB to XYZ. The matrix used in the above-linked wiki page corresponds to converting between sRGB and XYZ, using a reference white of D65. I know not what the reference white value means, but it will apparently be important here.

I tried the hard way to find out what matrix Mathematica uses to convert between RGB and XYZ, just by taking 3 random colors and converting them and then solving for the matrix,

varmat = 
  Array[ToExpression[
     "m" <> IntegerString[#1] <> IntegerString[#2]] &, {3, 3}];
eqn = {x, y, z} == varmat.(If[# > .04045,
        ((# + 0.055)/1.055)^2.4,
        #/12.92] & /@ {r, g, b});
eqns = Table[
   rgb = RandomReal[1, 3];
   xyz = List @@ ColorConvert[RGBColor @@ rgb, XYZColor];
   eqn /. (#1 -> #2 & @@@ Transpose@{{r, g, b}, rgb}) /. (#1 -> #2 & @@@
       Transpose@{{x, y, z}, xyz})
   , {3}];
varmat /. First@Solve[eqns, Flatten@varmat] // MatrixForm

enter image description here

And this matches exactly the transformation listed on Lindbloom's site for sRGB with a reference white of D50. I had not seen anything listed on the help pages for RGBColor or XYZColor about a reference white. But then I found the following on the page for ColorConvert

ColorConvert automatically performs chromatic (white point) adaptation. D50 white point is assumed for "XYZ", "LAB", "LUV", and "LCH" and D65 for "RGB", "CMYK", "HSB", and "Grayscale".

So that solves this problem, I post it here in case anyone in the future is trying to convert colors and notices the discrepancy.

I do wonder why Mathematica uses a different reference white than seems to be the norm for sRGB.

Edit: I was also able to find that Mathematica uses the D50 Reference White when converting to CIELAB from XYZ, which is again different than many other sites that have conversion utilities.

$\endgroup$
  • 1
    $\begingroup$ Ah, you've seen Lindbloom's site already... another thing to take into account would be chromatic adaptation when converting between systems with different white points. $\endgroup$ – J. M. will be back soon Dec 10 '15 at 14:50
  • $\begingroup$ About the white point: it's just a way to fix where "white" ought to be in the chromaticity diagram; you might want to look at ChromaticityPlot[]. $\endgroup$ – J. M. will be back soon Dec 10 '15 at 14:52
  • 1
    $\begingroup$ >I do wonder why Mathematica uses a different reference white than seems to be the norm for sRGB Given that sRGB is defined respect to D65 and CIEXYZ respect to D50 that's exactly the matrix you want to use. $\endgroup$ – Batracos Mar 16 '16 at 16:22
  • $\begingroup$ As far as I know CIELAB inherits the white point from the XYZ it originates from. $\endgroup$ – Batracos Mar 16 '16 at 16:24
  • $\begingroup$ @Batracos - thank you. I approached this without much knowledge of color spaces at all, but I just noticed I was getting a different result in MMA than elsewhere so I poked around until I came to the discrepency. $\endgroup$ – Jason B. Mar 16 '16 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.