imagecolorsrgbsrgb

Which matrix is correct to map XYZ to linear RGB for sRGB?


It seems there are 3 main variations on the mapping matrix, and they diverge from about the 3rd or 4th decimal place. Which would be considered the standard matrix?

  1. Bruce Lindbloom http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    Calculated from (x, y) for red, green, blue, and D65 ref white (X, Y, Z)
  RGB -> XYZ
  +0.4124564 +0.3575761 +0.1804375  
  +0.2126729 +0.7151522 +0.0721750  
  +0.0193339 +0.1191920 +0.9503041  
  XYZ -> RGB (by inverting RGB -> XYZ)
  +3.2404542 -1.5371385 -0.4985314  
  -0.9692660 +1.8760108 +0.0415560  
  +0.0556434 -0.2040259 +1.0572252  
  1. W3C https://www.w3.org/Graphics/Color/srgb
    EasyRGB https://easyrgb.com/en/math.php seems to use W3C, but truncated
  XYZ -> RGB
  +3.2406255 -1.5372080 -0.4986286  
  -0.9689307 +1.8757561 +0.0415175  
  +0.0557101 -0.2040211 +1.0569959  
  1. Wikipedia https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation
    Claims to be the sRGB specification
  XYZ -> RGB
  +3.24096994 -1.53738318 -0.49861076  
  -0.96924364 +1.87596750 +0.04155506  
  +0.05563008 -0.20397696 +1.05697151  

Solution

  • Strictly speaking, none of those is correct because they have been derived from the primaries and whitepoint given by IEC 61966-2-1:1999 and rounded at some arbitrary decimal places. So two real choices here, either you use the matrices as given by the standard, i.e. rounded at 4 decimal places or you compute the normalised primary matrix and its inverse directly at full machine precision, ideally double precision.

    IEC 61966-2-1:1999

    MATRIX_sRGB_TO_XYZ = np.array([
        [0.4124, 0.3576, 0.1805],
        [0.2126, 0.7152, 0.0722],
        [0.0193, 0.1192, 0.9505],
    ])
    """
    *sRGB* colourspace to *CIE XYZ* tristimulus values matrix.
    
    MATRIX_sRGB_TO_XYZ : array_like, (3, 3)
    """
    
    MATRIX_XYZ_TO_sRGB = np.array([
        [3.2406, -1.5372, -0.4986],
        [-0.9689, 1.8758, 0.0415],
        [0.0557, -0.2040, 1.0570],
    ])
    """
    *CIE XYZ* tristimulus values to *sRGB* colourspace matrix.
    
    MATRIX_XYZ_TO_sRGB : array_like, (3, 3)
    """
    

    ITU-R BT.709 which IEC 61966-2-1:1999 uses the primaries and whitepoint from, does not specify the matrices, so for example, computing them at double-precision:

    >>> import colour
    >>> import numpy as np
    >>> np.set_printoptions(formatter={'float': '{:0.15f}'.format}, suppress=True)
    >>> colour.models.RGB_COLOURSPACE_BT709.matrix_RGB_to_XYZ
    array([[0.412390799265959, 0.357584339383878, 0.180480788401834],
           [0.212639005871510, 0.715168678767756, 0.072192315360734],
           [0.019330818715592, 0.119194779794626, 0.950532152249661]])
    >>> colour.models.RGB_COLOURSPACE_BT709.matrix_XYZ_to_RGB
    array([[3.240969941904523, -1.537383177570094, -0.498610760293003],
           [-0.969243636280880, 1.875967501507721, 0.041555057407176],
           [0.055630079696994, -0.203976958888977, 1.056971514242879]])
    

    Technically, the rounding differences should be absorbed by any quantization effect of using 8-bit integer representation, but they have consequences when you working with floating-point values and the IEC 61966-2-1:1999 matrices do not round-trip properly for example.

    The problem lies in the fact that providing both primaries/whitepoint and conversion matrices from/to RGB to/from CIE XYZ creates ambiguity. Which one do you choose? People will tend to pick the matrices because they are already computed, which is easy to verify by running a basic Google Search.

    For interchange with other software, you would probably want to pick the published matrices, however, for internal colour conversion work, the derived matrices are preferred because you will suffer less from the rounding if you perform a lot of back and forth conversions. Practically speaking though, you will find that it does not matter too much.