/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.8.1/src/yrg.rs
Line | Count | Source |
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 | | */ |
29 | | use crate::mlaf::mlaf; |
30 | | use crate::{Matrix3f, Vector3f, Xyz}; |
31 | | use 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)] |
38 | | pub 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)] |
47 | | pub struct Ych { |
48 | | pub y: f32, |
49 | | pub c: f32, |
50 | | pub h: f32, |
51 | | } |
52 | | |
53 | | const 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 | | }; |
60 | | const 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 | | |
68 | | impl Yrg { |
69 | | #[inline] |
70 | 0 | pub const fn new(y: f32, r: f32, g: f32) -> Yrg { |
71 | 0 | Yrg { y, r, g } |
72 | 0 | } |
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 | 0 | pub fn from_xyz(xyz: Xyz) -> Self { |
80 | 0 | let lms = XYZ_TO_LMS.f_mul_vector(Vector3f { |
81 | 0 | v: [xyz.x, xyz.y, xyz.z], |
82 | 0 | }); |
83 | 0 | let y = mlaf(0.68990272 * lms.v[0], 0.34832189, lms.v[1]); |
84 | | |
85 | 0 | let a = lms.v[0] + lms.v[1] + lms.v[2]; |
86 | 0 | let l = if a == 0. { 0. } else { lms.v[0] / a }; |
87 | 0 | let m = if a == 0. { 0. } else { lms.v[1] / a }; |
88 | 0 | let r = mlaf(mlaf(0.02062, -0.6873, m), 1.0671, l); |
89 | 0 | let g = mlaf(mlaf(-0.05155, -0.0362, l), 1.7182, m); |
90 | 0 | Yrg { y, r, g } |
91 | 0 | } |
92 | | |
93 | | #[inline] |
94 | 0 | pub fn to_xyz(&self) -> Xyz { |
95 | 0 | let l = mlaf(0.95 * self.r, 0.38, self.g); |
96 | 0 | let m = mlaf(mlaf(0.03, 0.59, self.g), 0.02, self.r); |
97 | 0 | let den = mlaf(0.68990272 * l, 0.34832189, m); |
98 | 0 | let a = if den == 0. { 0. } else { self.y / den }; |
99 | 0 | let l0 = l * a; |
100 | 0 | let m0 = m * a; |
101 | 0 | let s0 = (1f32 - l - m) * a; |
102 | 0 | let v = Vector3f { v: [l0, m0, s0] }; |
103 | 0 | let x = LMS_TO_XYZ.f_mul_vector(v); |
104 | 0 | Xyz { |
105 | 0 | x: x.v[0], |
106 | 0 | y: x.v[1], |
107 | 0 | z: x.v[2], |
108 | 0 | } |
109 | 0 | } |
110 | | } |
111 | | |
112 | | impl Ych { |
113 | | #[inline] |
114 | 0 | pub const fn new(y: f32, c: f32, h: f32) -> Self { |
115 | 0 | Ych { y, c, h } |
116 | 0 | } |
117 | | |
118 | | #[inline] |
119 | 0 | pub fn from_yrg(yrg: Yrg) -> Self { |
120 | 0 | 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 | 0 | let r = yrg.r - 0.21902143; |
126 | 0 | let g = yrg.g - 0.54371398; |
127 | 0 | let c = f_hypotf(g, r); |
128 | 0 | let h = f_atan2f(g, r); |
129 | 0 | Self { y, c, h } |
130 | 0 | } |
131 | | |
132 | | #[inline] |
133 | 0 | pub fn to_yrg(&self) -> Yrg { |
134 | 0 | let y = self.y; |
135 | 0 | let c = self.c; |
136 | 0 | let h = self.h; |
137 | 0 | let sincos = f_sincosf(h); |
138 | 0 | let r = mlaf(0.21902143, c, sincos.1); |
139 | 0 | let g = mlaf(0.54371398, c, sincos.0); |
140 | 0 | Yrg { y, r, g } |
141 | 0 | } |
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. |
150 | 0 | pub const fn cie_y_1931_to_cie_y_2006(x: f32) -> f32 { |
151 | 0 | 1.05785528 * (x) |
152 | 0 | } |
153 | | |
154 | | #[cfg(test)] |
155 | | mod 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 | | } |