semmixcolor and perception

Converting between CIE XYZ and RGB

Going from RGB to CIE XYZ is the bridge between device color and device-independent color. It takes two steps: undoing the gamma encoding and multiplying by a 3×3 matrix. Shown here for sRGB, the most widely used RGB space.

Why "RGB" is not enough

The triple (200, 80, 40) does not yet define a color. You also need the display's primaries, its white point and its transfer curve. A specific RGB space defines all three. sRGB, set in 1996 for monitors and the web, has:

xy
red0.640.33
green0.300.60
blue0.150.06
white D650.31270.3290

The matrix below is derived from these chromaticity coordinates. Adobe RGB or Display P3 have different primaries and therefore different matrices.

Step 1: linearization

sRGB values are encoded nonlinearly (see gamma correction). The matrix works on values proportional to light, so the encoding has to be undone first. For each component c in 0..1:

c_lin = c / 12.92 if c ≤ 0.04045 c_lin = ((c + 0.055) / 1.055)^2.4 otherwise

Step 2: the matrix

| X | | 0.4124564 0.3575761 0.1804375 | | R_lin | | Y | = | 0.2126729 0.7151522 0.0721750 | · | G_lin | | Z | | 0.0193339 0.1191920 0.9503041 | | B_lin |

The middle row is the luminance formula: green contributes over 71% of brightness, blue just over 7%. White (1, 1, 1) gives X = 0.9505, Y = 1, Z = 1.0888, exactly the D65 white point.

The way back: XYZ to RGB

| R_lin | | 3.2404542 -1.5371385 -0.4985314 | | X | | G_lin | = | -0.9692660 1.8760108 0.0415560 | · | Y | | B_lin | | 0.0556434 -0.2040259 1.0572252 | | Z | c = 12.92 · c_lin if c_lin ≤ 0.0031308 c = 1.055 · c_lin^(1/2.4) - 0.055 otherwise

Worked example

RGB (200, 80, 40), #c85028.

c: 0.7843 0.3137 0.1569 c_lin: 0.5776 0.0802 0.0212 e.g. R: ((0.7843 + 0.055) / 1.055)^2.4 = 0.7955^2.4 = 0.5776 X = 0.4124564·0.5776 + 0.3575761·0.0802 + 0.1804375·0.0212 = 0.2707 Y = 0.2126729·0.5776 + 0.7151522·0.0802 + 0.0721750·0.0212 = 0.1817 Z = 0.0193339·0.5776 + 0.1191920·0.0802 + 0.9503041·0.0212 = 0.0409

Note the linearization: an encoded R of 0.78 is only 0.58 of the light, and G = 0.31 is a mere 0.08. Skipping this step is the most common mistake in color conversion, for this color it gives L* = 69.7 instead of 49.7.

From this XYZ you can go on to CIELAB and CIELUV. For this color: Lab (49.71, 45.77, 46.31).

Out-of-gamut colors

XYZ covers every visible color; sRGB covers only part of them. The reverse conversion can therefore produce negative values or values above 1. Example: X = 0.20, Y = 0.50, Z = 0.10 (a saturated green):

R_lin = -0.1703 G_lin = 0.7483 B_lin = 0.0148

Negative red means this green cannot be displayed in sRGB. The simplest fix is clipping to 0..1 (here roughly RGB (0, 224, 33)), but that changes hue and lightness. Color management software uses better gamut mapping methods that preserve hue at the cost of saturation.

The matrix depends on the white point. When XYZ has to be relative to D50 (for example in ICC profiles), an extra chromatic adaptation step is needed, usually the Bradford method. Skipping it gives a slight bluish or yellowish shift.