[csswg-drafts] [css-color-4] Matrices for sRGB to XYZ and back were (slightly) wrong (#5922)

svgeesus has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-color-4] Matrices for sRGB to XYZ and back were (slightly) wrong ==
TLDR; very small change, no visible effect, but imprecise round-trip was annoying. Non-normative sample code updated. Paywalled standards are bad and lead to errors.

**What was wrong**

The [normative definition of sRGB in CSS Color 4](https://drafts.csswg.org/css-color-4/#valdef-color-srgb) is correct. In the [non-normative sample code](https://drafts.csswg.org/css-color-4/#color-conversion-code), the matrix to go from linear-light sRGB to CIE XYZ, and the inverse matrix to go from CIE XYZ to linear-light sRGB, was wrong starting at the fourth decimal place.

**Why**

How did this happen? The official definition of sRGB 

**IEC 61966-2-1:1999/AMD1:2003** 
Amendment 1 - Multimedia systems and equipment - Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB

is a 16 page PDF which you [buy from the IEC webstore for 70 CHF](https://webstore.iec.ch/publication/6168).  I don't have a copy.

The standard is different from the [original definition of sRGB](https://www.w3.org/Graphics/Color/sRGB) because roundoff errors to 4 decimal places in the original proposal meant that the linear and curved parts of the transfer function were not continuous (although the effect made no difference, at 8 bits per component). The IEC standard fixed that.

So, like [others](https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz) I used the conversion matrices [calculated by Bruce Lindbloom](http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html) in the sRGB sample code.

The impact of this was fairly subtle. If you converted sRGB white, `#FFFFFF` to display-p3 you got `color(display-p3 1.0000557 0.99999258 0.99990441)` which is `color(display-p3 1.000 1.000 1.000)` to four significant figures, but `color(display-p3 1.0001 1.0000 0.9999)` to five significant figures, and this should be exactly `1 1 1` because both sRGB and display-p3 use a D65 white.

This bugged me.

Converting from some other D65-using colorspace, like `color(rec2020 1 1 1)` to display-p3, gave a nice satisfying round-trippable `color(display-p3 1 1 1). So this imprecision was related to sRGB.

Wikipedia [has the conversion matrices, to 8 significant figures](https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB)) which, it claims, come from the earlier version of the standard, **IEC 61966-2-1:1999.** Which is a [51 page PDF and costs 175 CHF](https://webstore.iec.ch/publication/6169). I don't know whether you need both this and the later standard, or just the later one. I don't have a copy of that, either.

**What did I change**

Today I [recalculated](https://drafts.csswg.org/css-color-4/matrixmaker.html) the sRGB ones, based on the sRGB primary chromaticities and white point. When rounded to 8 decimal places, they are _exactly_ identical to the ones that Wikipedia has. This gives me renewed confidence in those figures.

```js
var M = [
  [ 0.41239079926595934, 0.357584339383878,   0.1804807884018343  ],
  [ 0.21263900587151027, 0.715168678767756,   0.07219231536073371 ],
  [ 0.01933081871559182, 0.11919477979462598, 0.9505321522496607  ]
 ];
var Minv = [
  [  3.2409699419045226,  -1.537383177570094,   -0.4986107602930034  ],
  [ -0.9692436362808796,   1.8759675015077202,   0.04155505740717559 ],
  [  0.05563007969699366, -0.20397695888897652,  1.0569715142428786  ]
 ];
```

Here are what Wikipedia claims are the official ones, for the 1999 version of the standard:

```js
var M = [
  [ 0.41239080,  0.35758434,   0.18048079  ],
  [ 0.21263901,  0.71516868,   0.07219232 ],
  [ 0.01933082 , 0.11919478,   0.95053215 ]
 ];
var Minv = [
  [  3.24096994,  -1.53738318,  -0.49861076 ],
  [ -0.96924364 ,  1.87596750,   0.04155506 ],
  [  0.05563008,  -0.20397696   1.05697151  ]
 ];
```
It seems that, while I calculated the conversion matrices for the other predefined RGB spaces, I copied the sRGB ones from Lindbloom. And that was the source of the error. 

And here are the Lindbloom ones:

```js
var M = [
  [0.4124564,  0.3575761,  0.1804375],
  [0.2126729,  0.7151522,  0.0721750],
  [0.0193339,  0.1191920,  0.9503041]
 ];
var Minv = [
  [ 3.2404542, -1.5371385, -0.4985314],
  [-0.9692660,  1.8760108,  0.0415560],
  [ 0.0556434, -0.2040259,  1.0572252]
 ];
```

**What difference does this actually make**

In practical terms, none. The deltaE2000 between `color(display-p3 1.0000557 0.99999258 0.99990441)` and `color(display-p3 1.0000000 1.0000000 1.0000000)` is **0.015**, and a deltaE2000 of less than 1 is difficult to see.

However, given the problems that sRGB already had from excessive roundoff error, it is satisfying that with this change there is no roundoff error to 8 decimal places. This does affect the values in one WPT test, but does not affect the pass/fail since the difference is very small, invisible to the eye, and hard to measure accurately even with a good spectrophotometer.

**matrixmaker.html is blank**

Thanks for noticing. To change what matrix is calculated, edit the source. To see the result, open the console.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/5922 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 3 February 2021 17:36:13 UTC