ezk_image/color/
primaries.rs

1use crate::vector::Vector;
2
3/// Color gamut of an image
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ColorPrimaries {
6    /// SMPTE ST 240
7    ST240,
8    /// ITU-R BT.709
9    BT709,
10    /// ITU-R BT.2020
11    BT2020,
12}
13
14impl ColorPrimaries {
15    /// Returns the RGB to CIE 1931 XYZ matrix
16    pub fn rgb_to_xyz_mat(self) -> &'static [[f32; 3]; 3] {
17        use ColorPrimaries::*;
18
19        match self {
20            ST240 => &generated_consts::ST240_RGB_TO_XYZ,
21            BT709 => &generated_consts::BT709_RGB_TO_XYZ,
22            BT2020 => &generated_consts::BT2020_RGB_TO_XYZ,
23        }
24    }
25
26    /// Returns the CIE 1931 XYZ to RGB matrix
27    pub fn xyz_to_rgb_mat(self) -> &'static [[f32; 3]; 3] {
28        use ColorPrimaries::*;
29
30        match self {
31            ST240 => &generated_consts::ST240_XYZ_TO_RGB,
32            BT709 => &generated_consts::BT709_XYZ_TO_RGB,
33            BT2020 => &generated_consts::BT2020_XYZ_TO_RGB,
34        }
35    }
36}
37
38#[inline(always)]
39pub(crate) unsafe fn rgb_to_xyz<V: Vector>(fw: &[[f32; 3]; 3], r: V, g: V, b: V) -> [V; 3] {
40    let x = r
41        .vmulf(fw[0][0])
42        .vadd(g.vmulf(fw[1][0]))
43        .vadd(b.vmulf(fw[2][0]));
44    let y = r
45        .vmulf(fw[0][1])
46        .vadd(g.vmulf(fw[1][1]))
47        .vadd(b.vmulf(fw[2][1]));
48    let z = r
49        .vmulf(fw[0][2])
50        .vadd(g.vmulf(fw[1][2]))
51        .vadd(b.vmulf(fw[2][2]));
52
53    [x, y, z]
54}
55
56#[inline(always)]
57pub(crate) unsafe fn xyz_to_rgb<V: Vector>(bw: &[[f32; 3]; 3], x: V, y: V, z: V) -> [V; 3] {
58    let r = x
59        .vmulf(bw[0][0])
60        .vadd(y.vmulf(bw[1][0]))
61        .vadd(z.vmulf(bw[2][0]));
62    let g = x
63        .vmulf(bw[0][1])
64        .vadd(y.vmulf(bw[1][1]))
65        .vadd(z.vmulf(bw[2][1]));
66    let b = x
67        .vmulf(bw[0][2])
68        .vadd(y.vmulf(bw[1][2]))
69        .vadd(z.vmulf(bw[2][2]));
70
71    [r, g, b]
72}
73
74mod generated_consts {
75    pub(super) const ST240_RGB_TO_XYZ: [[f32; 3]; 3] = [
76        [0.39031416, 0.20383073, 0.025401404],
77        [0.3700937, 0.71034116, 0.11341577],
78        [0.19004808, 0.08582816, 0.95024043],
79    ];
80    pub(super) const ST240_XYZ_TO_RGB: [[f32; 3]; 3] = [
81        [3.506003, -1.0092703, 0.026740361],
82        [-1.7397907, 1.9292052, -0.18375263],
83        [-0.5440582, 0.027603257, 1.0636142],
84    ];
85
86    pub(super) const BT709_RGB_TO_XYZ: [[f32; 3]; 3] = [
87        [0.41239083, 0.21263903, 0.01933082],
88        [0.35758436, 0.7151687, 0.11919474],
89        [0.1804808, 0.07219231, 0.95053214],
90    ];
91    pub(super) const BT709_XYZ_TO_RGB: [[f32; 3]; 3] = [
92        [3.2409694, -0.9692435, 0.055630032],
93        [-1.537383, 1.8759671, -0.20397685],
94        [-0.49861073, 0.04155508, 1.0569714],
95    ];
96
97    pub(super) const BT2020_RGB_TO_XYZ: [[f32; 3]; 3] = [
98        [0.63695806, 0.2627002, 0.0],
99        [0.14461692, 0.6779981, 0.028072689],
100        [0.16888095, 0.05930171, 1.060985],
101    ];
102    pub(super) const BT2020_XYZ_TO_RGB: [[f32; 3]; 3] = [
103        [1.7166512, -0.66668427, 0.017639855],
104        [-0.3556708, 1.6164812, -0.04277061],
105        [-0.25336626, 0.01576853, 0.9421032],
106    ];
107}
108
109#[cfg(test)]
110mod generate_matrices {
111    use super::ColorPrimaries::{self, *};
112    use nalgebra::{Matrix3, Vector3};
113
114    fn xy(x: f32, y: f32) -> Vector3<f32> {
115        Vector3::new(x, y, 1.0 - x - y)
116    }
117
118    fn xyz_rgbw(primaries: ColorPrimaries) -> [Vector3<f32>; 4] {
119        match primaries {
120            ST240 => [
121                xy(0.63, 0.3290),
122                xy(0.31, 0.595),
123                xy(0.155, 0.07),
124                xy(0.3127, 0.3290),
125            ],
126            BT709 => [
127                xy(0.64, 0.33),
128                xy(0.3, 0.6),
129                xy(0.15, 0.06),
130                xy(0.3127, 0.3290),
131            ],
132            BT2020 => [
133                xy(0.708, 0.292),
134                xy(0.170, 0.797),
135                xy(0.131, 0.046),
136                xy(0.3127, 0.3290),
137            ],
138        }
139    }
140
141    fn rgb_to_xyz_mat(primaries: ColorPrimaries) -> Matrix3<f32> {
142        let [r, g, b, mut w] = xyz_rgbw(primaries);
143
144        let y = w.y;
145        w.x *= 1.0 / y;
146        w.y *= 1.0 / y;
147        w.z *= 1.0 / y;
148
149        #[rustfmt::skip]
150        let m = Matrix3::new(
151            r.x, g.x, b.x,
152            r.y, g.y, b.y,
153            r.z, g.z, b.z
154        );
155
156        let s = m.try_inverse().unwrap() * (Vector3::new(w.x, w.y, w.z));
157
158        #[rustfmt::skip]
159        let s = Matrix3::new(
160            s.x, 0.0, 0.0,
161            0.0, s.y, 0.0,
162            0.0, 0.0, s.z
163        );
164
165        m * s
166    }
167
168    #[test]
169    #[ignore]
170    fn run() {
171        let primaries = [ST240, BT709, BT2020];
172
173        for primaries in primaries {
174            let rgb_to_xyz = rgb_to_xyz_mat(primaries);
175            let xyz_to_rgb = rgb_to_xyz.try_inverse().unwrap();
176
177            println!("pub(super) const {primaries:?}_RGB_TO_XYZ: [[f32; 3]; 3] = {rgb_to_xyz:?};");
178            println!("pub(super) const {primaries:?}_XYZ_TO_RGB: [[f32; 3]; 3] = {xyz_to_rgb:?};");
179            println!()
180        }
181    }
182}