moxcms/
yrg.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 3/2025. All rights reserved.
3 * //
4 * // Redistribution and use in source and binary forms, with or without modification,
5 * // are permitted provided that the following conditions are met:
6 * //
7 * // 1.  Redistributions of source code must retain the above copyright notice, this
8 * // list of conditions and the following disclaimer.
9 * //
10 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11 * // this list of conditions and the following disclaimer in the documentation
12 * // and/or other materials provided with the distribution.
13 * //
14 * // 3.  Neither the name of the copyright holder nor the names of its
15 * // contributors may be used to endorse or promote products derived from
16 * // this software without specific prior written permission.
17 * //
18 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29use crate::mlaf::mlaf;
30use crate::{Matrix3f, Vector3f, Xyz};
31use pxfm::{f_atan2f, f_hypotf, f_sincosf};
32
33/// Structure for Yrg colorspace
34///
35/// Kirk Yrg 2021.
36#[repr(C)]
37#[derive(Default, Debug, PartialOrd, PartialEq, Copy, Clone)]
38pub struct Yrg {
39    pub y: f32,
40    pub r: f32,
41    pub g: f32,
42}
43
44/// Structure for cone form of Yrg colorspace
45#[repr(C)]
46#[derive(Default, Debug, PartialOrd, PartialEq, Copy, Clone)]
47pub struct Ych {
48    pub y: f32,
49    pub c: f32,
50    pub h: f32,
51}
52
53const LMS_TO_XYZ: Matrix3f = Matrix3f {
54    v: [
55        [1.8079466, -1.2997167, 0.34785876],
56        [0.61783963, 0.39595452, -0.041046873],
57        [-0.12546961, 0.20478038, 1.7427418],
58    ],
59};
60const XYZ_TO_LMS: Matrix3f = Matrix3f {
61    v: [
62        [0.257085, 0.859943, -0.031061],
63        [-0.394427, 1.175800, 0.106423],
64        [0.064856, -0.076250, 0.559067],
65    ],
66};
67
68impl Yrg {
69    #[inline]
70    pub const fn new(y: f32, r: f32, g: f32) -> Yrg {
71        Yrg { y, r, g }
72    }
73
74    /// Convert [Xyz] D65 to [Yrg]
75    ///
76    /// Yrg defined in D65 white point. Ensure Xyz values is adapted.
77    /// Yrg use CIE XYZ 2006, adapt CIE XYZ 1931 by using [cie_y_1931_to_cie_y_2006] at first.
78    #[inline]
79    pub fn from_xyz(xyz: Xyz) -> Self {
80        let lms = XYZ_TO_LMS.f_mul_vector(Vector3f {
81            v: [xyz.x, xyz.y, xyz.z],
82        });
83        let y = mlaf(0.68990272 * lms.v[0], 0.34832189, lms.v[1]);
84
85        let a = lms.v[0] + lms.v[1] + lms.v[2];
86        let l = if a == 0. { 0. } else { lms.v[0] / a };
87        let m = if a == 0. { 0. } else { lms.v[1] / a };
88        let r = mlaf(mlaf(0.02062, -0.6873, m), 1.0671, l);
89        let g = mlaf(mlaf(-0.05155, -0.0362, l), 1.7182, m);
90        Yrg { y, r, g }
91    }
92
93    #[inline]
94    pub fn to_xyz(&self) -> Xyz {
95        let l = mlaf(0.95 * self.r, 0.38, self.g);
96        let m = mlaf(mlaf(0.03, 0.59, self.g), 0.02, self.r);
97        let den = mlaf(0.68990272 * l, 0.34832189, m);
98        let a = if den == 0. { 0. } else { self.y / den };
99        let l0 = l * a;
100        let m0 = m * a;
101        let s0 = (1f32 - l - m) * a;
102        let v = Vector3f { v: [l0, m0, s0] };
103        let x = LMS_TO_XYZ.f_mul_vector(v);
104        Xyz {
105            x: x.v[0],
106            y: x.v[1],
107            z: x.v[2],
108        }
109    }
110}
111
112impl Ych {
113    #[inline]
114    pub const fn new(y: f32, c: f32, h: f32) -> Self {
115        Ych { y, c, h }
116    }
117
118    #[inline]
119    pub fn from_yrg(yrg: Yrg) -> Self {
120        let y = yrg.y;
121        // Subtract white point. These are the r, g coordinates of
122        // sRGB (D50 adapted) (1, 1, 1) taken through
123        // XYZ D50 -> CAT16 D50->D65 adaptation -> LMS 2006
124        // -> grading RGB conversion.
125        let r = yrg.r - 0.21902143;
126        let g = yrg.g - 0.54371398;
127        let c = f_hypotf(g, r);
128        let h = f_atan2f(g, r);
129        Self { y, c, h }
130    }
131
132    #[inline]
133    pub fn to_yrg(&self) -> Yrg {
134        let y = self.y;
135        let c = self.c;
136        let h = self.h;
137        let sincos = f_sincosf(h);
138        let r = mlaf(0.21902143, c, sincos.1);
139        let g = mlaf(0.54371398, c, sincos.0);
140        Yrg { y, r, g }
141    }
142}
143
144// Pipeline and ICC luminance is CIE Y 1931
145// Kirk Ych/Yrg uses CIE Y 2006
146// 1 CIE Y 1931 = 1.05785528 CIE Y 2006, so we need to adjust that.
147// This also accounts for the CAT16 D50->D65 adaptation that has to be done
148// to go from RGB to CIE LMS 2006.
149// Warning: only applies to achromatic pixels.
150pub const fn cie_y_1931_to_cie_y_2006(x: f32) -> f32 {
151    1.05785528 * (x)
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_yrg() {
160        let xyz = Xyz::new(0.95, 1.0, 1.08);
161        let yrg = Yrg::from_xyz(xyz);
162        let yrg_to_xyz = yrg.to_xyz();
163        assert!((xyz.x - yrg_to_xyz.x) < 1e-5);
164        assert!((xyz.y - yrg_to_xyz.y) < 1e-5);
165        assert!((xyz.z - yrg_to_xyz.z) < 1e-5);
166    }
167
168    #[test]
169    fn test_ych() {
170        let xyz = Yrg::new(0.5, 0.4, 0.3);
171        let yrg = Ych::from_yrg(xyz);
172        let yrg_to_xyz = yrg.to_yrg();
173        assert!((xyz.y - yrg_to_xyz.y) < 1e-5);
174        assert!((xyz.r - yrg_to_xyz.r) < 1e-5);
175        assert!((xyz.g - yrg_to_xyz.g) < 1e-5);
176    }
177}