CIE XYZ Color Space

Device-independent color space based on human vision tristimulus values.

Overview

CIE XYZ is the foundation of colorimetry. It's based on how the human eye perceives color through three types of cone cells (L, M, S). XYZ values are device-independent and serve as an intermediate space for most color conversions.


RGB to XYZ Conversion

sRGB to XYZ (D65 illuminant)

Important: Convert sRGB to linear RGB first (see RGB & Gamma)

$$ \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} = \begin{bmatrix} 0.4124 & 0.3576 & 0.1805 \\ 0.2126 & 0.7152 & 0.0722 \\ 0.0193 & 0.1192 & 0.9505 \end{bmatrix} \begin{bmatrix} R_{linear} \\ G_{linear} \\ B_{linear} \end{bmatrix} $$

XYZ to sRGB (D65 illuminant)

$$ \begin{bmatrix} R_{linear} \\ G_{linear} \\ B_{linear} \end{bmatrix} = \begin{bmatrix} 3.2406 & -1.5372 & -0.4986 \\ -0.9689 & 1.8758 & 0.0415 \\ 0.0557 & -0.2040 & 1.0570 \end{bmatrix} \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} $$

Important: Apply gamma correction after conversion to get sRGB values.


Practical Implementation

Python

 1import numpy as np
 2
 3# sRGB D65 transformation matrices
 4RGB_TO_XYZ = np.array([
 5    [0.4124, 0.3576, 0.1805],
 6    [0.2126, 0.7152, 0.0722],
 7    [0.0193, 0.1192, 0.9505]
 8])
 9
10XYZ_TO_RGB = np.array([
11    [ 3.2406, -1.5372, -0.4986],
12    [-0.9689,  1.8758,  0.0415],
13    [ 0.0557, -0.2040,  1.0570]
14])
15
16def rgb_to_xyz(r, g, b):
17    """
18    Convert linear RGB to XYZ
19    Input: r, g, b in [0, 1] (linear, not gamma-corrected)
20    Output: X, Y, Z
21    """
22    rgb = np.array([r, g, b])
23    xyz = RGB_TO_XYZ @ rgb
24    return xyz[0], xyz[1], xyz[2]
25
26def xyz_to_rgb(x, y, z):
27    """
28    Convert XYZ to linear RGB
29    Input: X, Y, Z
30    Output: r, g, b in [0, 1] (linear, needs gamma correction)
31    """
32    xyz = np.array([x, y, z])
33    rgb = XYZ_TO_RGB @ xyz
34    # Clamp to valid range
35    rgb = np.clip(rgb, 0, 1)
36    return rgb[0], rgb[1], rgb[2]

Go

 1package color
 2
 3type XYZ struct {
 4    X, Y, Z float64
 5}
 6
 7type RGB struct {
 8    R, G, B float64
 9}
10
11// RGBToXYZ converts linear RGB to XYZ (sRGB D65)
12func RGBToXYZ(rgb RGB) XYZ {
13    return XYZ{
14        X: 0.4124*rgb.R + 0.3576*rgb.G + 0.1805*rgb.B,
15        Y: 0.2126*rgb.R + 0.7152*rgb.G + 0.0722*rgb.B,
16        Z: 0.0193*rgb.R + 0.1192*rgb.G + 0.9505*rgb.B,
17    }
18}
19
20// XYZToRGB converts XYZ to linear RGB (sRGB D65)
21func XYZToRGB(xyz XYZ) RGB {
22    rgb := RGB{
23        R:  3.2406*xyz.X - 1.5372*xyz.Y - 0.4986*xyz.Z,
24        G: -0.9689*xyz.X + 1.8758*xyz.Y + 0.0415*xyz.Z,
25        B:  0.0557*xyz.X - 0.2040*xyz.Y + 1.0570*xyz.Z,
26    }
27    // Clamp to valid range
28    rgb.R = clamp(rgb.R, 0, 1)
29    rgb.G = clamp(rgb.G, 0, 1)
30    rgb.B = clamp(rgb.B, 0, 1)
31    return rgb
32}
33
34func clamp(v, min, max float64) float64 {
35    if v < min {
36        return min
37    }
38    if v > max {
39        return max
40    }
41    return v
42}

Key Properties

  • Y component: Represents luminance (brightness)
  • Device-independent: Same XYZ values represent the same color on any device
  • Intermediate space: Used as a bridge between different color spaces
  • Not perceptually uniform: Equal distances in XYZ don't correspond to equal perceived color differences

Common Use Cases

  1. Color space conversion: RGB ↔ LAB, RGB ↔ LCH
  2. Color matching: Comparing colors across devices
  3. Illuminant adaptation: Chromatic adaptation transforms
  4. Colorimetry: Measuring and specifying colors objectively

Notes

  • Always use the correct transformation matrix for your RGB color space (sRGB, Adobe RGB, ProPhoto RGB, etc.)
  • D65 is the standard illuminant for sRGB and most modern displays
  • XYZ values are typically normalized so that $Y = 100$ for a perfect white
  • Out-of-gamut colors (negative RGB after conversion) need to be handled appropriately

Further Reading

Related Snippets