skip to main content

From sRGB color space to sRGB profile: how to calculate the ICC sRGB profile primaries from the sRGB color space specifications

This page provides a step-by-step worked example of performing a Bradford chromatic adaptation to calculate the D50-adapted ICC sRGB profile red, green, and blue primaries from the unadapted red, green, blue, and white xyz values given in the sRGB color space specifications.

Written February 2014.

Overview: From the sRGB D65 color space to the ICC D50-adapted sRGB profile

The ICC sRGB profile is made as follows:

  1. The unadapted red, green, and blue XYZ values are calculated from the unadapted red, green, and blue xyz values plus the D65 source white point xyz values given in the sRGB specifications.
  2. The unadapted red, green, and blue XYZ values are chromatically adapted 1 from the sRGB D65 source white point to the ICC D50 destination white point using the Bradford transformation matrix.

The rest of this page walks you step by step through the mathematics of calculating the ICC D50-adapted sRGB profile red, green, and blue XYZ primaries from the values given in the sRGB D65 color space specification.2 All of the calculations are in the downloadable sRGB specifications to ICC profile spreadsheet.

Step by step: Adapting the sRGB D65 color space red, green, and blue primaries from the sRGB D65 source white point to the ICC D50 illuminant

The sRGB specifications

The sRGB specifications give the values for the sRGB unadapted red, green, and blue xy values and the sRGB D65 source white point:

  1. Poynton's Color FAQ, Question 17
  2. Basic sRGB Math, from the Wayback archive of the original Hewlett-Packard/Microsoft sRGB website.
  3. sRGB
  4. Default RGB Colour Space - sRGB
  5. A Standard Default Color Space for the Internet - sRGB and A Standard Default Color Space for the Internet: sRGB (essentially the same information hosted by the W3C and the ICC, respectively)

Table 1 gives the sRGB unadapted red, green, blue, and white xyz values:

Table 1: sRGB specifications unadapted red, green, blue, and D65 source white point xyz values xy values
x y z
D65 Source White Point 0.3127 0.3290 0.3583
red (unadapted) 0.6400 0.3300 0.0300
green (unadapted) 0.3000 0.6000 0.1000
blue (unadapted) 0.1500 0.0600 0.7900

If you do some adding and subtracting, you'll find that the z values are always equal to (1-x-y), so the z values don't add any new information. The real question is what are the right sRGB unadapted red, green, blue, and source white point Y values? The Y value for D65 is automatically 1.0 because RGB color space white points by definition are normalized to have a relative luminance of 1.0. For now, let's postpone how to calculate the sRGB unadapted red, green, and blue Y values.

sRGB profile D65 source and D50 destination white points

To make an actual ICC profile from the xyY values given in the sRGB specification, the unadapted xyY values must be chromatically adapted from the D65 sRGB source white point to the ICC D50 profile destination white point, better known as the ICC profile D50 illuminant. The ICC V4 profile specifications give the exact values for the ICC D50 illuminant:

3.1.21
PCS illuminant
illuminant with the spectral radiance distribution of CIE illuminant D50 and nCIEXYZ X = 0,964 2, nCIEXYZ Y = 1,0, nCIEXYZ Z = 0,824 9

6.3.1
Chromatic adaptation
The PCS adopted white chromaticity shall be the chromaticity of the D50 illuminant defined in ISO 3640

7.2.16 PCS illuminant field (Bytes 68 to 79)
The PCS illuminant field shall contain the nCIEXYZ values X = 0,964 2, Y = 1,0 and Z = 0,824 9 encoded as an XYZNumber. . . .
NOTE These values are the nCIE XYZ values of CIE illuminant D

Table 2a gives the sRGB source and destination white points that will be used to calculate the sRGB D65 to ICC D50 Bradford chromatic adaptation matrix:

Table 2a: sRGB D65 source and ICC D50 destination white points
sRGB D65 Source White Point x y Y
0.3127 0.3290 1.0
ICC D50 Destination White Point X Y Z
0.9642 1.0000 0.8249

Notice that the sRGB D65 white point is given as xyY values, whereas the ICC specifications gives the D50 destination white point as XYZ values. Chromatic adaptation calculations are done using XYZ values rather than xyY values. So calculating the sRGB D65 to ICC D50 Bradford chromatic adaptation matrix requires converting the sRGB specifications D65 source white point values from xyY to XYZ. Fortunately, it's very simple to convert from xyY to XYZ:

X = ( x times Y )   ⁄   y

Y = Y

Z = ( ( 1 − x − y ) times Y )   ⁄   y

Table 2b gives the sRGB D65 source and ICC D50 destination white points as XYZ values:

Table 2b: sRGB D65 source and D50 destination white points as XYZ values
X Y Z
sRGB D65 Source White Point 0.950455927 1.000000000 1.089057751
ICC D50 Destination White Point 0.964200000 1.000000000 0.824900000

If you play around with the xyY to XYZ equations and the corresponding XYZ to xyY equations, you'll see that maintaining four decimal places of accuracy for values initially specified as xyY values requires considerably more than four decimal places in the resulting XYZ values; the same is also true when going the other way around.

All calculations on this page are carried out to 9 decimal places. The final results are rounded to 8 decimal places.

The Bradford transformation matrix

Doing a chromatic adaptation from a source to a destination white point requires first choosing a matrix that defines the cone response domain tranformation. Many such cone response domain transformations have been proposed. The ICC V4 specifications suggests (that is, strongly urges but doesn't actually require) using the Bradford model:

6.2 Rendering intents
6.2.1 General
The colorimetric rendering intents operate on measurement-based colorimetric values as adapted to the PCS adopted white chromaticity. This adaptation, when required, shall be indicated in the chromaticAdaptationTag. For the purposes of this ICC specifications, chromatic adaptation should be calculated using the linear Bradford model.

Bradford chromatic adaptation is done using the following transformation matrix:

Table 3: Bradford transformation matrix
 0.895100000  0.266400000 -0.161400000
-0.750200000  1.713500000  0.036700000
 0.038900000 -0.068500000  1.029600000

Just to clarify a possibly point of confusion, the Bradford tranformation matrix isn't a chromatic adaptation matrix. Rather it's one of many mathematical transforms that have been suggested for converting XYZ values to an LMS color space that describes the "response of the three types of cones of the human eye, named after their responsivity (sensitivity) at long, medium and short wavelengths". So the Bradford transformation matrix in Table 3 above is used to calculate Bradford chromatic adaptation matrices.

The sRGB specifications don't mention anything at all about how to adapt the sRGB color space to the D50 destination white point required for ICC profiles. This is not surprising as the sRGB color space describes the ideal CRT monitor in a non-ICC-profile-color-managed context. However, the ICC recommends using Bradford adaptation and that's what LittleCMS, ArgyllCMS, Adobe, and probably all profile makers use when making the sRGB ICC profile (and also all the other standard RGB working spaces).

The Bradford sRGB D65 to ICC D50 chromatic adaptation matrix

The Bradford transformation matrix and its inverse are used to calculate a Bradford chromatic adaptation matrix from a given source white point to a given destination white point. My downloadable sRGB specifications to ICC profile spreadsheet has the appropriate equations set up ready to calculate a Bradford chromatic adaptation matrix. In the spreadsheet, the source and destination white point cells are already pre-filled-in with the sRGB D65 source white point and the ICC D50 destination white point to calculate the sRGB D65 to ICC D50 Bradford chromatic adaptation matrix. Doing the calculations produces the following chromatic adaptation matrix:

Table 4: sRGB D65 to ICC D50 Bradford Chromatic Adaptation Matrix
 1.047886003  0.022918765 -0.050216095
 0.029581782  0.990483518 -0.017078708
-0.009251881  0.015072607  0.751678134

Unadapted red, green, and blue: from xyz to xyY

Chromatic adaptation is done using XYZ values and the sRGB specifications provide xyz values 3. Converting from xyY to XYZ is trivially easy, but does require that you have Y as well as xy. Because RGB color space white points are normalized to 1.0, the sRGB D65 source white point Y value is automatically 1.0. Now I'll show you the math for calculating the sRGB unadapted red, green, and blue Y values, equations for which can be found in the ArgyllCMS source code in the file "icc/mkDispProf.c":

Assume for the moment the unadapted sRGB red, green, and blue Y values are also equal to 1.0, producing the interim red, green, and blue xyY values in Table 5a:

Table 5a: interim sRGB unadapted red, green, blue xyY values, with Y set equal to 1.0
x y Y
red (unadapted) 0.6400 0.3300 1.0 (interim)
green (unadapted) 0.3000 0.6000 1.0 (interim)
blue (unadapted) 0.1500 0.0600 1.0 (interim)

Calculate the interim XYZ values from the assumed xyY values in Table 5a, using the xyY to XYZ equations, producing the values in Table 5b:

Table 5b: interim sRGB unadapted red, green, blue XYZ values, calculated from the interim xyY values in Table 5a
interim unadapted red interim unadapted green interim unadapted blue
X 1.939393939 0.500000000 2.500000000
Y 1.000000000 1.000000000 1.000000000
Z 0.090909091 0.166666667 13.166666667

To calculate the real unadapted red, green, and blue Y values requires three steps. The first step is to treat the XYZ values in Table 5b as a 3x3 matrix and calculate its inverse (openoffice/libreoffic MINVERSE function). The resulting inverse matrix is shown in Table 5c:

Table 5c: the inverse of the 3x3 matrix formed from the values in Table 5b
0.689156627 -0.326907631 -0.106024096
-0.693172691 1.341633199 0.029718876
0.004016064 -0.014725569 0.076305221

The second step is to turn the sRGB D65 source white point XYZ values from Table 2b above into a 1x3 matrix, as shown in Table 5d:

Table 5d. The sRGB D65 source white point XYZ values written as a 1x3 matrix
X 0.950455927
Y 1.000000000
Z 1.089057751

The third step is to multiply the 3x3 matrix in Table 5c by the 1x3 matrix in Table 5d to produce a second 1x3 matrix. The result, shown in Table 5e, is composed of the actual (as opposed to interim) unadapted red, green, and blue Y values:

Table 5e. The actual (not interim) sRGB unadapted red, green, and blue Y values
actual unadapted red Y 0.212639006
actual unadapted green Y 0.715168679
actual unadapted blue Y 0.072192315

Given the red, green, and blue xy values and the sRGB D65 source white point xyY values, the mathematics shown above for calculating the sRGB unadapted red, green, and blue Y values seems like mathemagic to me. I think more in pictures than in numbers and if anyone knows of or can create a convincing visual explanation of why the mathemagic works, please share!

Unadapted red, green, and blue: from xyY to XYZ

If you will recall from Section B2, I mentioned that chromatic adaptation equations use XYZ values. The goal of Sections B3 through B5 has been get to the point where we could calculate the sRGB unadapted XYZ values from the unadapted xyz values. Now that we have the sRGB unadapted red, green, and blue xyY values, it's trivial to calculate the correponding unadapted red, green, and blue XYZ values.

Table 6 shows the result of using the xyY to XYZ equations to calculate the sRGB unadapted XYZ values from the unadapted xyY values:

Table 6: sRGB unadapted red, green, and blue XYZ values calculated from the unadapted xyY values
unadapted xyY to unadapted XYZ
red x 0.640000000 X 0.412390799
y 0.330000000 Y 0.212639006
Y 0.212639006 Z 0.019330819
green x 0.300000000 X 0.357584339
y 0.600000000 Y 0.715168679
Y 0.715168679 Z 0.119194780
blue x 0.150000000 X 0.180480788
y 0.060000000 Y 0.072192315
Y 0.072192315 Z 0.950532152

Calculating the D50-adapted sRGB primaries

We're almost done! To review what we've covered so far:

  1. The sRGB specifications give the sRGB unadapted red, green, and blue xy values and the sRGB D65 source white point.
  2. The ICC specifications give the ICC D50 destination white point.
  3. The ICC specifications also suggest using the Bradford transformation when calculating the source-to-destination chromatic adaptation matrix.
  4. We calculated the sRGB D65 to ICC D50 Bradford adaptation matrix from the sRGB D65 source white point, the ICC D50 destination white point, and the Bradford transformation matrix.
  5. We used mathemagic to calculate the unadapted sRGB red, green, and blue xyY values from the unadapted red, green, blue, and white xyz values given in the sRGB specification.
  6. Finally, we calculated the unadapted sRGB red, green, and blue XYZ values from the unadapted red, green, and blue xyY values.

The only thing left to do is multiply the sRGB D65 to ICC D50 Bradford adaptation matrix (calculated in Section B4) by each of the unadapted red, green, and blue primaries (calculated in Section B6), in turn:

  1. To calculate the ICC D50-adapted sRGB red XYZ values: multiply the D65 to D50 chromatic adaptation matrix by the sRGB unadapted red XYZ primaries from Table 6.
  2. To calculate the ICC D50-adapted sRGB green XYZ values: multiply the D65 to D50 chromatic adaptation matrix by the sRGB unadapted green XYZ primaries from Table 6.
  3. To calculate the ICC D50-adapted sRGB blue XYZ values: multiply the D65 to D50 chromatic adaptation matrix by the sRGB unadapted blue XYZ primaries from Table 6.

Applying the equations to calculate the D50-adapted sRGB primaries from the D65 unadapted sRGB primaries yields the following ICC sRGB profile red, green, and blue primaries:

Table 7: sRGB ICC profile XYZ primaries, adapted from sRGB D65 to ICC D50
X Y Z
Red 0.436041252 0.222484540 0.013920187
Green 0.385112911 0.716905079 0.097067239
Blue 0.143045838 0.060610381 0.713912574

Spreadsheet calculations vs the ArgyllCMS sRGB.icm profile

Comparing our spreadsheet-calculated values to values generated while making an sRGB profile using the ArgyllCMS mkDispProf

The ArgyllCMS version 1.6.3 source code includes the mkDispProf.c source code file, which is set up to make the sRGB ICC profile 4. Graeme Gill, author of ArgyllCMS, very kindly wrote the mkDispProf.c code as a demonstration of how to use ArgyllCMS to make ICC profiles.

The following code box shows the mkDispProf license and copyright information plus excerpts from the code, which I have modified to include additional printf statements. The sRGB red, green, blue, and white unadapted primaries are highlighted, as is the information that will be printed to the terminal screen when the modified mkDispProf is compiled and the executable is used to make the sRGB profile:

/*
 * Create an ICC V2.4 compatible matrix display profile.
 *
 * Author:  Graeme W. Gill
 * Date:    8/2/2008
 * Version: 1.00
 *
 * Copyright 2006 - 2014 Graeme W. Gill
 *
 * This material is licensed with an "MIT" free use license:-
 * see the License.txt file in this directory for licensing details.
 *
 * Based on icc/lutest.c
 */
/* Setup the primaries */
{
/* Compute primaries as XYZ */
 icmXYZNumber wrgb[4] = {  /* Primaries in Yxy from the standard */
 { 1.0, 0.3127, 0.3290 }, /* White */
 { 1.0, 0.6400, 0.3300 }, /* Red */
 { 1.0, 0.3000, 0.6000 }, /* Green */
 { 1.0, 0.1500, 0.0600 }  /* Blue */
}; 

 double mat[3][3];
 int i;

/* Convert Yxy to XYZ */
 printf("\nPrint interim unadapted XYZ primaries calculated by setting Y=1.0\n"); 
 for (i = 0; i < 4; i++) {
 double v[3];

 icmXYZ2Ary(v, wrgb[i]);
 icmYxy2XYZ(v, v);
 icmAry2XYZ(wrgb[i], v);

 printf("{icmAry2XYZ: i=%d, %1.9f, %1.9f, %1.9f } \n",
 i, wrgb[i].X, wrgb[i].Y, wrgb[i].Z);
 }

/* Convert XYZ to normalised 3x3 matrix */
 icmRGBprim2matrix(wrgb[0], wrgb[1], wrgb[2], wrgb[3], mat);

 printf("\nPrint D65 sRGB white point: XYZ\n"); 
 printf("{ %1.9f, %1.9f, %1.9f }     /* White */\n",
 wrgb[0].X, wrgb[0].Y, wrgb[0].Z);

 printf("\nPrint sRGB unadapted primaries: XYZ\n"); 
 printf("{ %1.9f, %1.9f, %1.9f }, /* Red */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Green */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Blue */\n",
 mat[0][0], mat[0][1], mat[0][2],
 mat[1][0], mat[1][1], mat[1][2],
 mat[2][0], mat[2][1], mat[2][2]);

[ . . .
skipping over some code
 . . . ]

/* Red, Green and Blue Colorant Tags: */
 {
 icmXYZArray *wor, *wog, *wob;
 double fromAbs[3][3];
 double d50m[3][3];

/* Convert to D50 adapated */
 icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, wrgb[0], fromAbs);
 icmMulBy3x3(d50m[0], fromAbs, mat[0]);
 icmMulBy3x3(d50m[1], fromAbs, mat[1]);
 icmMulBy3x3(d50m[2], fromAbs, mat[2]);

 printf("\nPrint sRGB D65 to ICC D50 Chromatic Adaptation Matrix\n"); 
 printf("{ %1.9f, %1.9f, %1.9f }, /* Red */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Green */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Blue */\n",
 fromAbs[0][0], fromAbs[0][1], fromAbs[0][2],
 fromAbs[1][0], fromAbs[1][1], fromAbs[1][2],
 fromAbs[2][0], fromAbs[2][1], fromAbs[2][2]);

 printf("\nPrint D50-adapted primaries: XYZ\n"); 
 printf("{ %1.9f, %1.9f, %1.9f }, /* Red */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Green */\n"
        "{ %1.9f, %1.9f, %1.9f }, /* Blue */\n",
 d50m[0][0], d50m[0][1], d50m[0][2],
 d50m[1][0], d50m[1][1], d50m[1][2],
 d50m[2][0], d50m[2][1], d50m[2][2]);

A quick comparison between the values in Table 1 from the sRGB specifications and the ArgyllCMS mkDispProf values given for icmXYZNumber wrgb[4] "Primaries in Yxy from the standard" in the above code box shows the ArgyllCMS source code uses the exact red, green, blue, and white xy values given in the sRGB specifications. And the Y values have all been set equal to 1.0, just as we did in Section B5 above.

Here are the values printed to screen after compiling mkDispProf modified to include the additional printf statements and then running the executable mkDispProf to make the "srgb.icc" profile. Clicking on the links will take you back to where we calculated the same values using the spreadsheet:

$mkDispProf srgb.icc
Print interim unadapted XYZ primaries calculated by setting Y=1.0
{icmAry2XYZ: i=0, 0.950455927, 1.000000000, 1.089057751 }
{icmAry2XYZ: i=1, 1.939393939, 1.000000000, 0.090909091 }
{icmAry2XYZ: i=2, 0.500000000, 1.000000000, 0.166666667 }
{icmAry2XYZ: i=3, 2.500000000, 1.000000000, 13.166666667 }

Print D65 sRGB white point: XYZ
{ 0.950455927, 1.000000000, 1.089057751 }       /* White */

Print sRGB unadapted primaries: XYZ
{ 0.412390799, 0.212639006, 0.019330819 },      /* Red */
{ 0.357584339, 0.715168679, 0.119194780 },      /* Green */
{ 0.180480788, 0.072192315, 0.950532152 },      /* Blue */

Print sRGB D65 to ICC D50 Chromatic Adaptation Matrix
{ 1.047886003, 0.022918765, -0.050216095 },     /* Red */
{ 0.029581782, 0.990483518, -0.017078708 },     /* Green */
{ -0.009251881, 0.015072607, 0.751678134 },     /* Blue */

Print D50-adapted primaries: XYZ
{ 0.436041252, 0.222484540, 0.013920187 },      /* Red */
{ 0.385112911, 0.716905079, 0.097067239 },      /* Green */
{ 0.143045838, 0.060610381, 0.713912574 },      /* Blue */

Comparing the values printed to screen by the mkDispProf executable while making "srgb.icc" and the corresponding values that were calculated by our spreadsheet, all the values exactly match.

Comparing the calculated sRGB D50-adapted XYZ values to values in the "srgb.icc" profile colorant tags

To recap what's been covered so far: We used a spreadsheet to calculate the D50-adapted sRGB red, green, and blue XYZ values. I modified the mkDispProf source code to print to screen calculations made by the mkDispProf executable while making the "srgb.icc" profile. The values printed to screen by the mkDispProf executable exactly match the values we calculated using the spreadsheet.

Now let's compare the calculated adapted red, green, and blue XYZ values to the red, green, and blue XYZ values found in the actual "srgb.icc" profile produced by mkDispProf. As an important aside, the "srgb.icc" profile red, green, and blue XYZ values are the exact same XYZ values found in the ArgyllCMS sRGB.icm profile (from any recent version of ArgyllCMS including the most recent version 1.6.3, but not including the sRGB.icm profile from 1.6.1 and 1.6.2, which has very slightly different primaries).

If you want to examine the primaries in an actual ICC profile, several free and open source utilities are available, including icc_examin, which provides a gui display; the ArgyllCMS iccdump, which is a terminal utility that gives values to 6 decimal places; and iccToXml (part of iccXML), which produces an XML file with (among other information) 8-decimal-place profile red, green, and blue XYZ values.

I used iccToXml to dump the contents of "srgb.icc" to an XML file. Table 8 below gives the red, green, and blue colorant XYZ values in the XML file:

Table 8: Calculated D50-adapted sRGB primaries vs "srgb.icc" XYZ colorant values
Calculated
adapted
XYZ values
"srgb.icc"
profile
XYZ values
Absolute
difference
Red X 0.436041252 0.43603516 0.00000609
Red Y 0.222484540 0.22248840 0.00000385
Red Z 0.013920187 0.01391602 0.00000415
Green X 0.385112911 0.38511658 0.00000371
Green Y 0.716905079 0.71690369 0.00000143
Green Z 0.097067239 0.09706116 0.00000604
Blue X 0.143045838 0.14305115 0.00000528
Blue Y 0.060610381 0.06060791 0.00000245
Blue Z 0.713912574 0.71392822 0.00001564

Examining Table 8, it turns out that the calculated and actual "srgb.icc" adapted primaries only match through the fifth decimal place. The reason for the discrepancy is a funny thing called hexadecimal quantization:

Hexadecimal quantization and ICC profiles

ICC profiles don't store XYZ values as decimal values. Rather an ICC profile's XYZ values are stored as hexadecimal values. So a certain amount of quantization (rounding) happens during the conversion from decimal values (which are given in the sRGB specifications) to hexadecimal values (which are stored in actual ICC profiles) and back to decimal (which is what utilities like iccdump, icc_examin, and iccToXml provide).

The ICC V4 specifications refer to a profile's red, green, and blue adapted XYZ primaries as colorants. A profile's red, green, and blue colorants are stored as XYZNumbers (all one word, see section 4.14 of the ICC V4 specs). As an aside, the profile illuminant, white point tag, and chad ("chromatic adaptation") tag values are also stored as XYZNumbers. According to the ICC specifciations, XYZNumbers are encoded as "s15Fixed16Number" (see Table 8, section 4.14 of the ICC V4 specs):

4.6 s15Fixed16Number
An s15Fixed16Number is a fixed signed 4-byte (32-bit) quantity which has 16 fractional bits as shown in Table 4.

The referred to Table 4 of the ICC V4 specs shows the range of decimal integer numbers that an s15Fixed16Number can accomodate. Athough I understand hexadecimal numbers, my understanding of the ICC's s15Fixed16Number encoding breaks down right here, so if you can provide a nice summary, please share!

I used the following OpenOffice/LibreOffice spreadsheet formula to convert the calculated sRGB D50-adapted red, green, and blue primaries to hexadecimal and back to decimal:

( HEX2DEC ( DEC2HEX ( number-to-be-converted * 65536; 9 ) ) ) / 65536

where 65536 is 2 raised to the 16th power. I guesstimated that 16 is the right power of 2 by trying different powers of 2 until I got to the power of 2 that most closely replicated the colorant values in the ArgyllCMS sRGB.icm profile.

Table 9 shows the "hexadecimal rounded" spreadsheet-calculated D50-adapted sRGB primaries, starting with the D50-adapted XYZ primaries from Table 8 above:

Table 9: Calculated vs Hexadecimal rounded vs "srgb.icc" colorant XYZ values
Calculated
adapted
XYZ values
Hexadecimal
Rounded
"srgb.icc"
profile
XYZ values
Absolute
difference
between
Rounded and
Actual
Red X 0.436041252 0.436035156 0.43603516 0.00000000
Red Y 0.222484540 0.222473145 0.22248840 0.00001526
Red Z 0.013920187 0.013916016 0.01391602 0.00000000
Green X 0.385112911 0.385101318 0.38511658 0.00001526
Green Y 0.716905079 0.716903687 0.71690369 0.00000000
Green Z 0.097067239 0.097061157 0.09706116 0.00000000
Blue X 0.143045838 0.143035889 0.14305115 0.00001526
Blue Y 0.060610381 0.060607910 0.06060791 0.00000000
Blue Z 0.713912574 0.713897705 0.71392822 0.00003051

Examining Table 9, after the spreadsheet-calculated values have been hexadecimal-rounded, five of the actual XYZ values in the mkDispProf "srgb.icc" profile match the calculated-then-hexadecimal-rounded adapted XYZ values to 8 decimal places. The remaining actual XYZ values match the hexadecimal-rounded values through the fourth decimal place, and are actually closer to the (non-rounded) calculated values than to the hexadecimal-rounded values.

I will guess that the remaining discrepencies between the calculated and actual "srgb.icc" red, green, and blue D50-adapted XYZ values are from a combination of two factors:

  1. I doubt that my rough and ready hexadecimal rounding equation fully accounts for the peculiarities of the ICC-specified s15Fixed16Number hexadecimal format account.
  2. The ArgyllCMS profile-making code specifically takes hexadecimal rounding into account (unlike certain other profile making softwares), which as an extremely important aside is the reason why the ArgyllCMS code makes well-behaved, neutral profiles (again, unlike certain other profile making softwares). I didn't take into account the portions of the ArgyllCMS code that perform this important step.

In any event, it's satisfying (well, I think it is) to see our spreadsheet calculations so closely reproduce the D50-adapted "srgb.icc" colorants produced by the ArgyllCMS mkDispProf.c code.

Conclusion

If you actually made it all the way to the bottom of this page, you deserve a great big round of applause, a medal for fortitude, and maybe a cold beer to sooth your aching head! Hopefully you also have a better understanding of the actual mathematics behind chromatic adaptation as it applies to making the ICC sRGB profile from the sRGB color space specification.

Given the specifications for any standard RGB color space, the exact same mathematical calculations are used to make the corresponding D50-adapted ICC profile. So if you are truly a glutton for punishment, make a copy of the downloadable sRGB specifications to ICC profile spreadsheet and replace the values for the sRGB color space with the corresponding values from the Reference Output Medium Metric RGB Color Space (ROMM RGB) White Paper (ProPhoto) or Adobe® RGB (1998) Color Image Encoding. For extra credit, use iccToXml to check the corresponding values in the profile distributed by your favorite vendor of free and open source profiles and see how closely the colorant tag values agree with your calculations. If there is a large discrepency, then the profile maker used the wrong source white point values, or the wrong unadapted primaries, or both 5.

Notes:

  1. For more information about and some pretty cool practical demonstrations of chromatic adaptation in action, see:
  2. As an important aside, there is an easily overlooked but extremely important conceptual distinction between the sRGB D65 color space and the sRGB D50-adapted ICC profile. Let's define "color management" to mean "controlling the appearance of color as displayed on devices":

    The sRGB color space was designed to describe actual video device display behavior (specifically consumer-grade CRT monitors) where color management was done by device calibration or wasn't done at all.

    The D50-adapted ICC sRGB profile is used when color management is done using ICC profiles to describe device behavior. In this latter context, video devices might be calibrated and then profiled, or simply profiled in their uncalibrated ("native") state.

    In color-managed image editing applications, the sRGB profile is appropriately used as an image editing space ("RGB working space"). But whether profiled, calibrated, both, or neither, today's LCD monitors aren't accurately characterized by the sRGB ICC profile.

  3. If you read all the way through to the end of the sRGB specification, you'll see that the sRGB specifications does give XYZ values (to four decimal places), and the Y in XYZ of course is equal to the Y in xyY. But the XYZ values in the sRGB specifications are calculated values, and the question is how to calculate the red, green, and blue Y values from the initially given red, green, and blue xy values and D65 source white point.
  4. Graeme Gill refers to the profile made by mkDispProf as "srgb-like" because it only includes the functional parts of the original sRGB profile, specifically the white point and colorant tags and tone reproduction curves. The original sRGB profile contains several tags with other information, which are ignored during ICC profile transformations and so from a purely functional point of view, irrelevant.
  5. See Floss RGB working space profile evaluations: Which ones you can trust, which ones to avoid, which ones are closest to selected reference profiles. Scroll down to the sRGB table.