What Makes sRGB a Special Color Space?
It’s been a long time since I updated the blog, but I was making some new color profiles a few days ago and remembered I never did resolve the small disagreement I had with other people in the color management community around primary colorant values I calculated for my compact sRGB ICC profiles.
I wrote a series of posts about the process I used to create my profiles, which included comparison to other sRGB profiles in common use, and I arrived at the conclusion that every other profile in circulation was using the wrong (not by much, but still…) colorant values. There was a lot of hand-waving around the fact I was writing about the sRGB spec without having seen it, and a lot of the content in my post on that topic was inferred from information on Wikipedia and in the draft spec for sRGB. One of the people who disagreed with my interpretation was none other than Graeme Gill, author of ArgyllCMS and acknowledged subject matter expert, who commented on the post.
I was confident enough in my understanding to stick with my calculated values, but they were still based on inferences. Well, in the intervening years, I was finally able to read the specs for both sRGB and scRGB, and it turns out I did, in fact, have the right idea and the right numbers. I want to clarify a few points from the specs for posterity, however, because sRGB is unlike any other ‘normal’ color space.
The key to this difference can be found in the following quote from the sRGB spec (Section 3.1):
The encoding transformations between 1931 CIEXYZ values and 8 bit RGB values provide unambiguous methods to represent optimum image colorimetry when viewed on the reference display in the reference viewing conditions by the reference observer.
There are 3 very important bits of information communicated in that sentence.
- sRGB defines an encoding rather than a color space in the traditional abstract sense.
- The encoding given in the spec is unambiguous, i.e. not open to interpretation.
- It is intended for 8-bit values.
I think those points resolve the discrepancy between the fact that sRGB gives the Rec.709 primaries in its reference display description and the fact that its actual listed matrix values are intentionally imprecise derivations of the values from Rec.709. The distinction between reference display environment -- which is the only place x,y coordinates are given in the spec – and the actual primary colorants defined in the spec’s encoding is quite clear when reading the actual spec, whereas the Wikipedia summary conflates them.
That does leave a question, though: What happens when you need more than 8-bit precision? I showed that calculating the transform matrix with greater precision, by starting from the Rec.709 x,y coordinates, yields numbers that don’t agree with the rounded numbers in the spec, specifically in the XYZ->RGB matrix. I suggested, however, that one could invert the given RGB->XYZ matrix to arbitrary precision as long as the starting values were the rounded ones in the spec.
Well, it turns out that the scRGB spec, which was written to cover images with higher bit depths, does exactly that. It includes the following statement (IEC 61966-2-2:2003, Section 4.1):
The encoding transformations provide unambiguous methods to transform between CIE 1931 XYZ tristimulus values and 16-bit values for each channel of scRGB.
Again, an unambiguous encoding, but this time for 16-bit precision. The forward scRGB->XYZ matrix is exactly the same as given for sRGB->XYZ, with the exact same 4 decimal places of precision.
0.4124 0.3576 0.1805 0.2126 0.7152 0.0722 0.0193 0.1192 0.9505
The inverse matrix, however, is given with 6 decimal places of precision.
3.240625 -1.537208 -0.498629 -0.968931 1.875756 0.041518 0.055710 -0.204021 1.056996
Those numbers match the ones I calculated in my original post, right up to the sixth decimal place, and of course you can calculate that inverse to higher precision if you need to, without breaking compatibility.
This was the only way that I could make the numbers agree with each other when researching this initially, but it was good to finally see it in black and white in the spec. So unlike every normal color space -- where you can calculate both the forward and inverse matrices to arbitrary precision by starting from the x,y values -- with sRGB you can only get the correct values by starting from the unambiguously specified forward matrix, rounded to exactly 4 decimal places.
One thing I got wrong in my guesses about the content of the spec was around the definition of the XYZ whitepoint. Based on the whitepoint given in the description of the spec on Wikipedia, I inferred that it was intentionally rounded in the wrong direction in the spec. In fact, the XYZ whitepoint value isn’t given in the spec at all. We simply calculate the whitepoint by adding the primary colorants together, which gives a total that doesn’t match the correctly rounded value of the D65 whitepoint when calculated from its x,y coordinates. Again, this is a difference between the reference display conditions and the encoding given in the spec.
The result is the same; the whitepoint is intentionally not exactly D65, because it’s based on intentionally rounded colorants. This is not a matter of them being imprecise. Rather, they are precisely defined as being very slightly different from Rec.709. You can't calculate them 'more precisely' by going back to the x,y coordinates because they are already precisely and unambiguously defined in the spec.
So that’s the final answer. You can’t treat sRGB like any other color space, because it’s not just a color space; it’s a specific encoding of a color space. To be truly compatible, you must match the encoding, and that’s what my profiles do.
BTW, the new profiles I created are now in the GitHub repo. I added a couple of video color spaces and new versions of the Adobe color spaces. I had initially done those as ICC V2 profiles only, but due to a software compatibility issue, I needed V4 variants of those. The V4 profiles have 16-bit fractional gamma values as opposed to the 8-bit values supported by V2 profiles. Unfortunately, they’re 90 bytes bigger because of other changes between the standard versions, but they may be of use to someone.