Color: Color-primary transformation XYZ to RGB

this was titled “Glassner’s transformation XYZ to RGB”


There is a marvelous calculation in Glassner (vol 1, p 103; see my bibliography). Oh, I do not mean to imply that he originated this calculation, merely that I have only ever seen it in his book. And for all I know, he might actually be the originator.

edit: As I said in “Yet More Books on Color“, I found this calculation, done much the way Glassner did but not quite, in Giorgianni & Madden. I decided that I should use a title that did not pretty much credit it to Glassner, despite my disclaimer. end edit.

As he puts it: “Our goal is to find a matrix M which will take a three-element vector representing an XYZ color and transform it to an equivalent RGB vector for some particular monitor.”

The only information given to us will be the chromaticity coordinates x,y for each of the three phosphors, and for the white point.

Can’t be done, you say?

You’re right. We’re going to impose the requirement that the image (X,Y,Z) of the RGB white point (1,1,1) have intensity Y = 1.

There’s nothing sacred about that choice, but without somehing like it, we can’t get from xy to XYZ.

There is one other thing I want to point out up front. After he describes the solution, he says: “Now, to convert any XYZ space color vector C_{XYZ}\ into the appropriate RGB color C_{RGB}\ for this monitor, just post-multiply C_{XYZ}\ by the XYZ-to-RGB matrix M….”


OK, Post-multiply. If u and v are the XYZ and RGB vectors respectively — row vectors, mind you! — then he is saying

v = u M.


v’ = M’ u’.

That tells us that M’ (the transpose of M) is the transition matrix from XYZ (new) to RGB (old).

In other words, his algorithm derives an attitude matrix, instead of a transition matrix. No problem.

Oh, one other detail: his algorithm derives the inverse N of M:

N = M^{-1}\ .

(And yes, N is an attitude matrix, too.)

Now, here is the data for an NTSC monitor, with a white point matching that of the D65 spectrum.

His gives us the x,y coordinates for the r,g,b phopshors, and for the white point. We need to explicitly compute the z coordinate as well, as

z = 1 – x – y.

Here is x,y followed by x,y,z for the red phosphor:


r = {0.67,0.33,0.}

(so I called it r.)

Here is x,y followed by x,y,z for the green phosphor:


g = {0.21,0.71,0.08}

Here is x,y followed by x,y,z for the blue phosphor:


b = {0.14,0.08,0.78}

Finally, here is x,y followed by x,y,z for the white point:


w = {0.313,0.329,0.358}

That’s it, that’s all the input. And I named the vectors r, g, b, w.

His recipe

Let the matrix K contain the x,y,z coordinates of the three phosphors. To be specific, let the first row — there it is, K is an attitude matrix — contain the coordinates for the red phosphor, and so on.

K = \left(\begin{array}{ccc} 0.67 & 0.33 & 0. \\ 0.21 & 0.71 & 0.08 \\ 0.14 & 0.08 & 0.78\end{array}\right)

Let the vector W (upper-case) contain the X,Y,Z components of the white point. We have x,y,z


so we get X,Y,Z by dividing by y and multiplying by Y (=1), so just divide by y:

W = {0.951368,1.,1.08815}

(I’ll admit that Z > Y bothers me a little, but this won’t be the last time we see something like that.)

Now, let’s just do what he says. One, define and compute

V = W\ K^{-1}

V = {0.879576,0.852293,1.30764}

Two, make a diagonal matrix G from that vector:

G = \left(\begin{array}{ccc} 0.879576 & 0. & 0. \\ 0. & 0.852293 & 0. \\ 0. & 0. & 1.30764\end{array}\right)

Three, our matrix N is given by N = G K

N = \left(\begin{array}{ccc} 0.589316 & 0.29026 & 0. \\ 0.178982 & 0.605128 & 0.0681835 \\ 0.18307 & 0.104612 & 1.01996\end{array}\right)

Now hold up one second. Pre-multiplying K by the diagonal matrix G is just a way of scaling each row of K — each r,g,b vector for a phosphor! — by one element of that V vector.

His new basis, the rows of N, simply changes the lengths of the original r,g,b vectors.

Finish it off:

M = N^{-1} = \left(\begin{array}{ccc} 1.96696 & -0.954515 & 0.0638084 \\ -0.548333 & 1.93796 & -0.12955 \\ -0.296804 & -0.0274414 & 0.982263\end{array}\right)

He printed it with 3 places, so let’s do that:

M = \left(\begin{array}{ccc} 1.967 & -0.955 & 0.064 \\ -0.548 & 1.938 & -0.13 \\ -0.297 & -0.027 & 0.982\end{array}\right)

That is exactly his answer. So far, so good.

Check it

The first thing I’m going to do is get a transition matrix. Oh, which one do I really want? I want to map RGB components to XYZ and see what I get. We recall that he said M maps XYZ to RGB.

I want to go the other way, so I want the transpose of N, i.e. of M^{-1}\ .

N^T = \left(\begin{array}{ccc} 0.589316 & 0.178982 & 0.18307 \\ 0.29026 & 0.605128 & 0.104612 \\ 0. & 0.0681835 & 1.01996\end{array}\right)

So let’s try it out. What are the X,Y,Z components of the white point (1,1,1)? Applying N’ to (1,1,1) gives me


OK, we do have Y = 1; as we should.

More to the point, what are the x,y,z components of the white point? We take (X,Y,Z)…


compute their sum…


and divide (X,Y,Z) by their sum…


and recall the original data, for comparison….

w = {0.313,0.329,0.358}

Good, they agree. The matrix appears to do what we intended, for the white point.

What about the red phosphor? We apply N’ to (1,0,0) to get (X,Y,Z)…


(That is, of course (?!), the first column of N’.)

compute their sum…


and divide (X,Y,Z) by their sum…


and recall the original data, for comparison….

r = {0.67,0.33,0.}

I have confirmed that the green and blue phosphors also give the right answers.


But what we just did, for each phosphor, was a two-step calculation that ended up with x,y,z.

Could we have specified those conditions, in addition to mapping the white point?

Yes indeed.

My alternative

His method is certainly easy enough to follow. And frankly, I will probably use it in preference to the solution which follows. But I learned from doing this.

The key to his method turns out to be that each row of N is proportional to a row of K.

But did it have to be, or was that another imposed condition (like Y=1)? What if we had simply imposed a set of conditions and solved? it turns out that we get the same answer. That is, we discover that the answer must have each row of N proportional to a row of K. (It’s a consequence, not an assumption. Good to know.)

So, we imagine that we have a completely unknown transition matrix T. It has 9 unknowns.

T = \left(\begin{array}{ccc} t(1,1) & t(1,2) & t(1,3) \\ t(2,1) & t(2,2) & t(2,3) \\ t(3,1) & t(3,2) & t(3,3)\end{array}\right)

What conditions do we impose on it?

First, it maps the white point (1,1,1) to W (which we got from xyY with Y=1); that gives us 3 equations:

\begin{array}{l} t(1,1)+t(1,2)+t(1,3)=0.951368 \\ t(2,1)+t(2,2)+t(2,3)=1. \\ t(3,1)+t(3,2)+t(3,3)=1.08815\end{array}

Second, it maps the red phosphor (1,0,0) to some (X,Y,Z) such that we recover the x,y,z coordinates called r:

(X,Y,Z) / (X+Y+Z) = (x,y,z) = r.

Let me write that as

T.(1,0,0) = (X,Y,Z) = (X+Y+Z) (x,y,z) = (X+Y+Z) r

That is, I write (X,Y,Z) for this case (yes, it’s the first column of T)…


and their sum…


and get three more equations…

\begin{array}{l} t(1,1)=0.67 (t(1,1)+t(2,1)+t(3,1)) \\ t(2,1)=0.33 (t(1,1)+t(2,1)+t(3,1)) \\ t(3,1)=0\end{array}

Do it for the green phosphor, too, getting

\begin{array}{l} t(1,2)=0.21 (t(1,2)+t(2,2)+t(3,2)) \\ t(2,2)=0.71 (t(1,2)+t(2,2)+t(3,2)) \\ t(3,2)=0.08 (t(1,2)+t(2,2)+t(3,2))\end{array}

(This time the numbers come from g instead of from r.)

At this point, I have 9 equations. But I already know that if I add the 3 equations for the blue phosphor, I will get a unique solution, even though I then have 12 linear equations in 9 unknowns. I concluded that 3 equations are redundant, but I don’t know which.

And I don’t care.

So do it for the blue phosphor, getting

\begin{array}{l} t(1,3)=0.14 (t(1,3)+t(2,3)+t(3,3)) \\ t(2,3)=0.08 (t(1,3)+t(2,3)+t(3,3)) \\ t(3,3)=0.78 (t(1,3)+t(2,3)+t(3,3))\end{array}

When I ask Mathematica to solve those 12 equations in 9 unknowns, I get exactly one set of answers for the 9 elements of T:

T = \left(\begin{array}{ccc} 0.589316 & 0.178982 & 0.18307 \\ 0.29026 & 0.605128 & 0.104612 \\ 0 & 0.0681835 & 1.01996\end{array}\right)

That, of course (a transition matrix by design), should be the transpose of N (the corresponding attitude matrix). Recall

N^T = \left(\begin{array}{ccc} 0.589316 & 0.178982 & 0.18307 \\ 0.29026 & 0.605128 & 0.104612 \\ 0. & 0.0681835 & 1.01996\end{array}\right)


his way

Let’s lay out his explanation (he did provide one).

With W, K, V, and F as defined…

(Recall…. Let the matrix K contain the x,y,z coordinates of the three phosphors. To be specific, let the first row contain the coordinates for the red phosphor, and so on. Let the vector W contain the X,Y,Z components of the white point. Let G be a diagonal matrix constructed from the vector V — whatever it is. Let F = (1,1,1). )

The key is that each row of N is proportional to a row of K, and the diagonal matrix G holds the proportionality constants:

N = G K.

It is also crucial that F = (1,1,1), because it gives us a simple relationship between V and G:

V = F G.

(No, we don’t know V or G yet.)

We have that N maps the white point F to W (but N is an attitude matrix, so post-multiply):

W = F N.


W = F N = F (G K) = (F G) K = V K


W = V K,

and finally

V = W\ K^{-1}\ .

Having gotten there, we reverse ourselves: from W and K we compute V, then G, then N.

Which is what he told us to do.

Things to come

What I love about this is that now we can play. We can do the obvious: change the phosphors. But we can also do something brand-new: change the white point. And once we do that, we can construct a transition matrix between two white points on the same monitor.

I won’t promise to do it in the next post, but I really, really want to change the white point.

5 Responses to “Color: Color-primary transformation XYZ to RGB”

  1. Monstro Says:

    From these calculations, is it possible to calculate a white point (or change in white point) from one RGB matrix to another? I would like to know if it is possible, given the initial source white point and RGB matrix, as well as the destination RGB matrix, to calculate the destination white point. Does this make sense?

  2. rip Says:


    If, by an RGB matrix, you mean a matrix S holding the XYZ coordinates of R, G, and B, then S applied to (1,1,1) gives the XYZ coordinates of the white point; you can’t set the white point independently of the matrix S.

    The key to the calculation of a primary conversion matrix is that we were given xy of each phoshor, rather than XYZ. In this case, we need and use xyY of the white point.

    You might want to look at the following two posts: this one shows what happens if I change only the white point

    while this one shows two matrices (M = TX, and Tg) which may be what you mean by an “RGB matrix”:

    (You may also want to look at the first monitor post:

    Again, both M (or TX) and Tg applied to (1,1,1) give the XYZ coordinates of the two white points. But M was constructed from given XYZ values for each phosphor, without knowing or using the white point. Only afterwards did I confirm that M applied to (1,1,1) agreed with both the ColorSync Utility and with the DigitalColor Meter.

    If all this doesn’t answer you, please ask again.


  3. Monstro Says:

    By RGB matrix, I mean taking an XYZ matrix of basis vectors (Red, Green, Blue) that define a vector space, then transforming them into a matrix of linear RGB values using a transformation matrix for a target colorspace, followed by a gamma compression into nonlinear RGB corresponding to a target color space, such as sRGB or Adobe RGB. It is these nonlinear RGB values that actually are used by the display.

    I looked at your link where you show that the white point is a diagonal matrix that scales the target. I knew this via Bruce Lindbloom’s website…what I didn’t know was how to generate it from between two sets of XYZ values. I have to say, sheer Genius!

  4. Monstro Says:

    I was planning to measure the xyY coordinates and white point at a given set of native RGB values, generate the RGB to XYZ transform and it’s inverse for the native color space, then start changing the RGB values and recalculate the white point without measuring.

    Well…after absorbing what you’ve done, I don’t think calculating a white point from a change in a linear RGB matrix is possible. The problem is that a transformation matrix is needed to go from RGB to XYZ…and that can only be gathered by physically measuring the colors…this kind of defeats the reason I am doing this…which is to give me a reasonable number for the white point when tweaking the RGB values.

    So, if I were to have a magic wand, I would be able to tweak the RGB matrix and magically generate the xyY coordinates…and therefore the transformation matrices needed to go to and from XYZ…not going to happen.

  5. Dan Says:

    i have a question about how you’re inverting your matrix.
    for example, after you calculate N = G * K, you then invert N to get M.
    when i try to follow this and calculate the inverse matrix of N to get M, i do not get the same answer as you are getting.
    as my linear algebra is very rusty, i can easily believe it’s my issue.
    I tried N * M (aka N * N-1), I get the identity matrix with my matrix M. What am I missing?

    Matrix N
    0.589316 0.290260 0.000000
    0.178982 0.605128 0.068183
    0.183070 0.104611 0.101996

    Matrix M
    1.790130 -0.970865 0.649015
    -0.189320 1.971149 -1.317697
    -3.018879 -0.279114 9.990899

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: