## Introduction

In the previous post about my monitor, I used five colors: red, green, blue, white, and a pale purple. For each of them, I began by specifying RGB values… I used Digital color meter to find their XYZ values… and I demonstrated that the relationship was nonlinear when applied to the purple color.

The relationship was…

XYZ = M (RGB)^1.801

where M, however, is linear (a matrix).

I found the nonlinear part of the relationship from the following graph out of my ColorSync utility…

and, although I found the linear part of the relationship (M) by construction, I showed you that I also found all of its entries in the ColorSync utility (and called that version of it TX since it is close to, but not exactly equal to, M):

Briefly, then, every time I specify a set of RGB values, I can get three or four sets of XYZ, and they are very, very close to each other:

• measured values from the DigitalColor Meter
• ColorSync values from the ColorSync calculator
• computed values from my nonlinear transformation using M
• computed values from my nonlinear transformation using TX.

Oh, I have not actually demontrated that the ColorSync calculator matches the other three! Well, let’s do that.

## ColorSync calculator

So, let’s look at the calculator.

We can ask it to convert between RGB and XYZ, and we discover that it pretty well matches what we’ve done already — with two caveats.

1. We had better specify that the device is “Color LCD”, i.e. my monitor.
2. We had better NOT specify “Absolute”, whatever that is.

With those two constraints, we find that the calculator performs the nonlinear transformation between RGB and XYZ. Here, for my own reference as much as anything else, are the calculations for red…

… for which our measured values (DigitalColor Meter) were

0.35733, 0.20605, 0.02618,

and round to

0.3573, 0.206, 0.0262 .

ColorSync gets

.3574, .2061, .0262,

so red and green disagree a little. Not bad. On the other hand, it may be worth noting that the two values do not agree exactly — that is, the DigitalColor meter and ColorSync do not completely agree with each other.

Here’s green…

… for which our measured values (DigitalColor Meter) were

0.45224, 0.69995, 0.12091

and round to

0.4522, 0.7, 0.1209 .

ColorSync gets

.4522, .7, .1209 .

Perfect.

Here’s blue…

… for which our measured values (DigitalColor Meter) were

0.15463, 0.09399, 0.6778

and which round to

0.1546, 0.094, 0.6778

ColorSync gets

.1546, .0940, .6778 .

Perfect again.

Here’s white…

… for which our measured values (DigitalColor Meter) were

0.9642, 1.00003, 0.82489 ,

and which round to

0.9642, 1., 0.8249 .

ColorSync gets

.9642, 1.0000, .8249 .

Perfect again.

and finally, here’s purple…

… for which our measured values (DigitalColor Meter) were

0.28891, 0.2493, 0.37567

which round to

0.2889, 0.2493, 0.3757 .

ColorSync gets

.2886, .2488, .3753 .

Those are all off a bit.

Now let’s reverse it. This time the input is those measured purple XYZ…

ColorSync got .5647, .3890, .6864 … which compares nicely but not exactly to our origial input RGB:

0.565, 0.388, 0.686

Okay by me. But once again I want to emphasize that none of these numbers came from my explicit nonlinear relationship; instead, it’s the two software programs that do not exactly agree.

I’ll check one more in the reverse direction; here’s my measured XYZ for red…

0.35733, 0.20605, 0.02618.

We see that ColorSync reads 1,0,0 . How sweet it is!

So, the ColorSync calculator agrees substantively with the DigitColor Meter.

Now, just what are those choices? There are 4 of them: Absolute, Perceptual, Saturation, and Relative. They are called rendering intents.

You can find additional information about them at Appleat Wiki… (The ICC itself — who defined them! — doesn’t even list them in its own glossaries. Maybe you can find them there, where I failed. On the other hand, it might be more important to look for their technical definitions rather than English-language descriptions. And the ICC should be the place to look.)

From reading the general descriptions, there are probably three key questions:

1. does a rendering intent preserve any colors?
2. what does it do to out-of-gamut colors?
3. what does it do the the white point?

The fact is, however, that without explicit algorithms in my hands, I’m not at all sure what the different rendering intents really do. As usual, the English language just doesn’t suffice.

In particular, I do not understand the difference between absolute and relative in the treatment of the white points. (Maybe that’s what I would find on the ICC site.)

My rough readings of the general descriptions is:

• perceptual maps one entire color space to another; it changes all the colors;
• saturation preserves relative saturation (but what about out-of-gamut?);
• relative clips out-of-gamut colors, preserves in-gamut colors (including the white point);
• absolute changes the white point (but what else?).

Experimental evidence — I’ve only shown one datum, the purple color, but I’ve seen a lot more — tells me that for an in-gamut color, all three of perceptual, saturation, and relative, give the same answer. That seems at variance with the general descriptions, since relative is supposed to preserve in-gamut colors, while saturation and perceptual are not. I could chatter about why this might be the case — but it would be better to have the actual algorithms.

But I don’t want to go do that now.

Suffice it to say: I can use anything but “absolute” to duplicate the pairs of measurements taken by DigitalColor Meter, and the (almost) equivalent calculations using the explicit nonlinear transformation.

We will see more experimental evidence about the absolute rendering intent later in this post.

There is additional information in the ColorSync Utility. In addition to the XYZ values provided by the “colorant tristimulus” data…

… we find another table of data called “phosphor values”:

The immediate question is: Are these the same xy values we would get from the previous XYZ values?

We will see that the answer is no.

Then the next question is: How can we quantify the difference?

While there may be other things we can do, I couldn’t resist modifying the latest question: Don’t we have enough information to compute a primary conversion matrix?

(That’s the calculation — which I’ve done before — that starts with a set of xy for red, green, blue, and white, and generates a transformation from RGB to XYZ.)

Let’s answer the first question: are these the same numbers — do these xyz correspond to the given XYZ?

Red? The new xy (and z) values for red — which I might call the given xyz — are…

0.5921, 0.3466, 0.0613 .

(I computed z as z = 1 – x – y.)

On the other hand, our XYZ values for the pure red disk were 0.35733, 0.20605, 0.02618

0.606096, 0.349498, 0.044406 .

($x=\frac{X}{X+Y+Z}\$, etc.)

I might call those measured xyz. We seem to have two different sets of xyz values for the red phosphor.

Although we now know that the answer to the first question is no, we want (and need) some more data.

Green. The given xyz are…

0.333, 0.5472, 0.1198 ,

… and we confirm that they too are different from the xyz

0.355227, 0.5498, 0.0949729

of our measured XYZ, which were 0.45224, 0.69995, 0.12091 .

Blue? The given xyz are…

0.1576, 0.0885, 0.7539

… which are different from the xyz

0.166911, 0.101455, 0.731634

of our measured XYZ (0.15463, 0.09399, 0.6778).

Finally, the given white point xyz…

0.3127, 0.329, 0.3583

… differs from the xyz

0.3457, 0.358547 ,0.295753

of the measured XYZ, 0.9642, 1.00003, 0.82489 .

Now is a good time to look at those white point numbers. They look familiar. A quick check of Hunt’s “The Reproduction of Color” tells me that the measured xyz is the D50 white point, while the given xyz is the D65 white point. (That is, they are the chromaticity coordinates of the D50 and D65 standard illuminants.)

Fascinating.

## Primary Conversion matrix

Now let’s proceed to use the given xy coordinates to get a transformation matrix. (I’ve just repeated the same link for this.)

We set XYZ of the white point so that Y = 1… just divide xyz by y:

W = (0.950456, 1., 1.08906)

We construct an attitude matrix K whose rows are the given (not measured) xyz coordinates of the red, green, and blue phosphors…

$K = \left(\begin{array}{ccc} 0.5921 & 0.3466 & 0.0613 \\ 0.333 & 0.5472 & 0.1198 \\ 0.1576 & 0.0885 & 0.7539\end{array}\right)$

We compute $V = W\ K^{-1}\$

$V = \{0.571472,\ 1.27209,\ 1.19596\}$

… make a diagonal matrix G of that vector…

$G = \left(\begin{array}{ccc} 0.571472 & 0. & 0. \\ 0. & 1.27209 & 0. \\ 0. & 0. & 1.19596\end{array}\right)$

… and then compute N = G K:

$N = \left(\begin{array}{ccc} 0.338369 & 0.198072 & 0.0350312 \\ 0.423605 & 0.696086 & 0.152396 \\ 0.188483 & 0.105842 & 0.901631\end{array}\right)$

We have found N mapping RGB to XYZ. Recall, however, that N is an attitude matrix. I want a transition matrix T such that

XYZ = T RGB

so T = N’. Call it Tg to distinguish it from TX.

$Tg = N^T = \left(\begin{array}{ccc} 0.338369 & 0.423605 & 0.188483 \\ 0.198072 & 0.696086 & 0.105842 \\ 0.0350312 & 0.152396 & 0.901631\end{array}\right)$

Check it, if only once. We apply Tg to red, i.e. to the vector (1, 0, 0), and we get

XYZ = 0.338369, 0.198072, 0.0350312

from which we get

xyz = 0.5921, 0.3466, 0.0613,

and that is exactly the given xyz for the red phosphor, as it should be.

But why didn’t they just give us this matrix?

They almost did.

Let me grab another item from ColorSync.

My guess is that PCS stands for profile connection space, but I’m not sure it matters.

$C = \left(\begin{array}{ccc} 1.04788 & 0.022919 & -0.050201 \\ 0.029572 & 0.990494 & -0.017059 \\ -0.009232 & 0.015076 & 0.751648\end{array}\right)$

Here’s what does matter: applied to the computed Tg, that matrix (almost exactly) gives M (or TX):

M = C Tg.

Since we were actually given TX and C, we could have gotten Tg from

$Tg = C^{-1}\ M\$.

OK, that’s all nice. They didn’t give us Tg, but the gave us a chromatic adaptation matrix that would let us compute it from the M matrix. We didn’t need to go to all the work of computing Tg.

Or, I could have used Tg and TX to compute my monitor’s chromatic adaptation matrix. Then I would have to wonder where the given xyz came from…. It’s the chicken and the egg, at this point: I don’t know whether C and TX are primary, and Tg is computed from them — or whether Tg and TX are primary, and C is computed from them.

Regardless of whether we construct Tg or compute it from TX and the chromatic adaptation matrix, we get effectively the same thing.

Let’s look at white. We have

Tg.{1, 1, 1} = {0.950456, 1., 1.08906},

and we had better have that: it’s what we set the white point to. But I’ve seen that somewhere (you haven’t yet, but I have)…

that is exactly what ColorSync gives me, when I ask for absolute rendering intent! I have to say this made me happy.

Please understand: I just brought in the ColorSync calculator out of left field. As far as I knew, it had nothing to do with the primary conversion matrix Tg.

But ColorSync absolute does have something to do with Tg. Unfortunately, it doesn’t have much more to do with it than mapping the white point (see below).

Incidentally, the chromatic adaptation matrix I most often hear about is called the von Kries; here it is:

$\left(\begin{array}{ccc} 1.0161 & 0.0553 & -0.0522 \\ 0.006 & 0.9956 & -0.0012 \\ 0 & 0 & 0.7576\end{array}\right)$

NOTE that it is different from the one provided for my monitor:

$C = \left(\begin{array}{ccc} 1.04788 & 0.022919 & -0.050201 \\ 0.029572 & 0.990494 & -0.017059 \\ -0.009232 & 0.015076 & 0.751648\end{array}\right)$

They are similar, but mainly because they both have small off-diagonal terms and a (3,3) term rather less than 1. Both are predominantly diagonal matrices, both significantly reduce “blue”.

## Summary

We have seen that the ColorSync calculator essentially delivers the XYZ coordinates measured by the DigitalColor Meter, or computed by the nonlinear transformation — provided I do not use the “absolute” rendering intent. (Strictly speaking, all I’ve really shown you is that “relative” works, but I have tested the others.)

The linear part of that nonlinear transformation was described by ColorSync (TX), or could have computed (M) from the XYZ coordinates of red (1,0,0), green, and blue.

Implicit in those XYZ coordinates for red, green, and blue are the corresponding xyz coordinates.

But we have found a second set of xyz coordinates, and they are different. We also saw that the two white points are standard: one is D50, the other D65. We don’t know why, but we do know what.

Still, we constructed a primary conversion matrix (Tg), and we discovered that ColorSync provided the information (C, a chromatic adaptation matrix) required to compute Tg from the supplied TX matrix.

The construction of Tg guarantees that Tg applied to white (1,1,1) gives us the media white point — that was one of the inputs to the computation of Tg! It’s no accident.

Finally, we saw that the ColorSync calculator, with rendering intent absolute, says that the XYZ coordinates of (1,1,1) are… the media white point.

Cool.

And there it gets murky. Real murky.

I don’t know about you, but I certainly expected that Tg applied to red, green, or blue would match the ColorSync calculator using absolute rendering intent. After all, Tg applied to white matches the calculator. But this is not to be.

Yes, I expected that the overall transformation is nonlinear, but I thought it would preserve zeros and ones. (If the nonlinear transformation raises each of R and G and B to a power, then it preserves zeros and ones.)

Ha!

For red, I compute…

Tg.{1, 0, 0} = {0.338369, 0.198072, 0.0350312}

… and ColorSync shows .3523, .2061, .0346 :

These differences are larger than we’ve seen before.

For green, I compute

Tg.{0, 1, 0} = 0.423605, 0.696086, 0.152396

and ColorSync shows .4458, .7, .1596 :

Finally, for blue I compute

Tg.{0, 0, 1} = {0.188483, 0.105842, 0.901631}

… while ColorSync shows .1524, .0940, .8949 :

These are all quite a bit different: the ColorSync calculator doesn’t agree with applying Tg to red, green, and blue.

I am shocked.

The nonlinear part of this transformation does not preserve zeros and ones — so it’s something other than raising to a power (or even to three of them). Whatever it is doing, ColorSync maps the white point the same way Tg does, but it does not map red, green, and blue individually the same way Tg does.

Gee, if I want to understand this, I’m going to have to look under the hood of the absolute rendering intent.

We’ll see. I’ve shown you the pieces of ColorSync that I understand, and I’ve shown you a piece I do not understand. I’m not at all sure that I care much about the part I don’t understand — for now, it may be enough to know that ColorSync (with absolute rendering intent) is doing something I can’t reproduce.

Once again, if you were thinking I was omniscient, forget it. There is stuff out there that I don’t understand, and this is one of them.

I suppose I should close by reminding us that I do understand the nonlinearity of my monitor, so long as I stay with non-absolute rendering intents; we got that out of the previous monitor post.