/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.6/src/dt_ucs.rs
Line | Count | Source |
1 | | /* |
2 | | * // Copyright (c) Radzivon Bartoshyk 6/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::Xyz; |
30 | | use crate::mlaf::mlaf; |
31 | | use pxfm::{f_atan2f, f_powf, f_sincosf}; |
32 | | |
33 | | /// Darktable UCS JCH ( Darktable Uniform Color Space ) |
34 | | #[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] |
35 | | pub struct DtUchJch { |
36 | | pub j: f32, |
37 | | pub c: f32, |
38 | | pub h: f32, |
39 | | } |
40 | | |
41 | | /// Darktable UCS HSB ( Darktable Uniform Color Space ) |
42 | | #[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] |
43 | | pub struct DtUchHsb { |
44 | | pub h: f32, |
45 | | pub s: f32, |
46 | | pub b: f32, |
47 | | } |
48 | | |
49 | | /// Darktable HCB ( Darktable Uniform Color Space ) |
50 | | #[derive(Copy, Clone, PartialOrd, PartialEq, Debug)] |
51 | | pub struct DtUchHcb { |
52 | | pub h: f32, |
53 | | pub c: f32, |
54 | | pub b: f32, |
55 | | } |
56 | | |
57 | | const DT_UCS_L_STAR_RANGE: f32 = 2.098883786377; |
58 | | |
59 | | #[inline] |
60 | 0 | fn y_to_dt_ucs_l_star(y: f32) -> f32 { |
61 | 0 | let y_hat = f_powf(y, 0.631651345306265); |
62 | 0 | DT_UCS_L_STAR_RANGE * y_hat / (y_hat + 1.12426773749357) |
63 | 0 | } |
64 | | |
65 | | #[inline] |
66 | 0 | fn dt_ucs_l_star_to_y(x: f32) -> f32 { |
67 | 0 | f_powf( |
68 | 0 | 1.12426773749357 * x / (DT_UCS_L_STAR_RANGE - x), |
69 | | 1.5831518565279648, |
70 | | ) |
71 | 0 | } |
72 | | |
73 | | const L_WHITE: f32 = 0.98805060; |
74 | | |
75 | | #[inline] |
76 | 0 | fn dt_ucs_luv_to_ucs_jch( |
77 | 0 | l_star: f32, |
78 | 0 | l_white: f32, |
79 | 0 | u_star_prime: f32, |
80 | 0 | v_star_prime: f32, |
81 | 0 | ) -> DtUchJch { |
82 | 0 | let m2: f32 = mlaf(u_star_prime * u_star_prime, v_star_prime, v_star_prime); // square of colorfulness M |
83 | | |
84 | | // should be JCH[0] = powf(L_star / L_white), cz) but we treat only the case where cz = 1 |
85 | 0 | let j = l_star / l_white; |
86 | 0 | let c = |
87 | 0 | 15.932993652962535 * f_powf(l_star, 0.6523997524738018) * f_powf(m2, 0.6007557017508491) |
88 | 0 | / l_white; |
89 | 0 | let h = f_atan2f(v_star_prime, u_star_prime); |
90 | 0 | DtUchJch::new(j, c, h) |
91 | 0 | } |
92 | | |
93 | | #[inline] |
94 | 0 | fn dt_ucs_xy_to_uv(x: f32, y: f32) -> (f32, f32) { |
95 | | const X_C: [f32; 3] = [-0.783941002840055, 0.745273540913283, 0.318707282433486]; |
96 | | const Y_C: [f32; 3] = [0.277512987809202, -0.205375866083878, 2.16743692732158]; |
97 | | const BIAS: [f32; 3] = [0.153836578598858, -0.165478376301988, 0.291320554395942]; |
98 | | |
99 | 0 | let mut u_c = mlaf(mlaf(BIAS[0], Y_C[0], y), X_C[0], x); |
100 | 0 | let mut v_c = mlaf(mlaf(BIAS[1], Y_C[1], y), X_C[1], x); |
101 | 0 | let d_c = mlaf(mlaf(BIAS[2], Y_C[2], y), X_C[2], x); |
102 | | |
103 | 0 | let div = if d_c >= 0.0 { |
104 | 0 | d_c.max(f32::MIN) |
105 | | } else { |
106 | 0 | d_c.min(-f32::MIN) |
107 | | }; |
108 | 0 | u_c /= div; |
109 | 0 | v_c /= div; |
110 | | |
111 | | const STAR_C: [f32; 2] = [1.39656225667, 1.4513954287]; |
112 | | const STAR_HF_C: [f32; 2] = [1.49217352929, 1.52488637914]; |
113 | | |
114 | 0 | let u_star = STAR_C[0] * u_c / (u_c.abs() + STAR_HF_C[0]); |
115 | 0 | let v_star = STAR_C[1] * v_c / (v_c.abs() + STAR_HF_C[1]); |
116 | | |
117 | | // The following is equivalent to a 2D matrix product |
118 | 0 | let u_star_prime = mlaf(-1.124983854323892 * u_star, -0.980483721769325, v_star); |
119 | 0 | let v_star_prime = mlaf(1.86323315098672 * u_star, 1.971853092390862, v_star); |
120 | 0 | (u_star_prime, v_star_prime) |
121 | 0 | } |
122 | | |
123 | | impl DtUchJch { |
124 | | #[inline] |
125 | 0 | pub fn new(j: f32, c: f32, h: f32) -> DtUchJch { |
126 | 0 | DtUchJch { j, c, h } |
127 | 0 | } |
128 | | |
129 | | #[inline] |
130 | 0 | pub fn from_xyz(xyz: Xyz) -> DtUchJch { |
131 | 0 | DtUchJch::from_xyy(xyz.to_xyy()) |
132 | 0 | } |
133 | | |
134 | | #[inline] |
135 | 0 | pub fn to_xyz(&self) -> Xyz { |
136 | 0 | let xyy = self.to_xyy(); |
137 | 0 | Xyz::from_xyy(xyy) |
138 | 0 | } |
139 | | |
140 | | #[inline] |
141 | 0 | pub fn from_xyy(xyy: [f32; 3]) -> DtUchJch { |
142 | 0 | let l_star = y_to_dt_ucs_l_star(xyy[2]); |
143 | | // let l_white = y_to_dt_ucs_l_star(1.); |
144 | | |
145 | 0 | let (u_star_prime, v_star_prime) = dt_ucs_xy_to_uv(xyy[0], xyy[1]); |
146 | 0 | dt_ucs_luv_to_ucs_jch(l_star, L_WHITE, u_star_prime, v_star_prime) |
147 | 0 | } |
148 | | |
149 | | #[inline] |
150 | 0 | pub fn to_xyy(&self) -> [f32; 3] { |
151 | | // let l_white: f32 = y_to_dt_ucs_l_star(1.0); |
152 | 0 | let l_star = (self.j * L_WHITE).max(0.0).min(2.09885); |
153 | 0 | let m = if l_star != 0. { |
154 | 0 | f_powf( |
155 | 0 | self.c * L_WHITE / (15.932993652962535 * f_powf(l_star, 0.6523997524738018)), |
156 | | 0.8322850678616855, |
157 | | ) |
158 | | } else { |
159 | 0 | 0. |
160 | | }; |
161 | | |
162 | 0 | let sin_cos_h = f_sincosf(self.h); |
163 | 0 | let u_star_prime = m * sin_cos_h.1; |
164 | 0 | let v_star_prime = m * sin_cos_h.0; |
165 | | |
166 | | // The following is equivalent to a 2D matrix product |
167 | 0 | let u_star = mlaf( |
168 | 0 | -5.037522385190711 * u_star_prime, |
169 | | -2.504856328185843, |
170 | 0 | v_star_prime, |
171 | | ); |
172 | 0 | let v_star = mlaf( |
173 | 0 | 4.760029407436461 * u_star_prime, |
174 | | 2.874012963239247, |
175 | 0 | v_star_prime, |
176 | | ); |
177 | | |
178 | | const F: [f32; 2] = [1.39656225667, 1.4513954287]; |
179 | | const HF: [f32; 2] = [1.49217352929, 1.52488637914]; |
180 | | |
181 | 0 | let u_c = -HF[0] * u_star / (u_star.abs() - F[0]); |
182 | 0 | let v_c = -HF[1] * v_star / (v_star.abs() - F[1]); |
183 | | |
184 | | const U_C: [f32; 3] = [0.167171472114775, -0.150959086409163, 0.940254742367256]; |
185 | | const V_C: [f32; 3] = [0.141299802443708, -0.155185060382272, 1.000000000000000]; |
186 | | const BIAS: [f32; 3] = [ |
187 | | -0.00801531300850582, |
188 | | -0.00843312433578007, |
189 | | -0.0256325967652889, |
190 | | ]; |
191 | | |
192 | 0 | let mut x = mlaf(mlaf(BIAS[0], V_C[0], v_c), U_C[0], u_c); |
193 | 0 | let mut y = mlaf(mlaf(BIAS[1], V_C[1], v_c), U_C[1], u_c); |
194 | 0 | let d = mlaf(mlaf(BIAS[2], V_C[2], v_c), U_C[2], u_c); |
195 | | |
196 | 0 | let div = if d >= 0.0 { |
197 | 0 | d.max(f32::MIN) |
198 | | } else { |
199 | 0 | d.min(-f32::MIN) |
200 | | }; |
201 | 0 | x /= div; |
202 | 0 | y /= div; |
203 | 0 | let yb = dt_ucs_l_star_to_y(l_star); |
204 | 0 | [x, y, yb] |
205 | 0 | } |
206 | | } |
207 | | |
208 | | impl DtUchHsb { |
209 | | #[inline] |
210 | 0 | pub fn new(h: f32, s: f32, b: f32) -> DtUchHsb { |
211 | 0 | DtUchHsb { h, s, b } |
212 | 0 | } |
213 | | |
214 | | #[inline] |
215 | 0 | pub fn from_jch(jch: DtUchJch) -> DtUchHsb { |
216 | 0 | let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.); |
217 | 0 | let s = if b > 0. { jch.c / b } else { 0. }; |
218 | 0 | let h = jch.h; |
219 | 0 | DtUchHsb::new(h, s, b) |
220 | 0 | } |
221 | | |
222 | | #[inline] |
223 | 0 | pub fn to_jch(&self) -> DtUchJch { |
224 | 0 | let h = self.h; |
225 | 0 | let c = self.s * self.b; |
226 | 0 | let j = self.b / (f_powf(c, 1.33654221029386) + 1.); |
227 | 0 | DtUchJch::new(j, c, h) |
228 | 0 | } |
229 | | } |
230 | | |
231 | | impl DtUchHcb { |
232 | | #[inline] |
233 | 0 | pub fn new(h: f32, c: f32, b: f32) -> DtUchHcb { |
234 | 0 | DtUchHcb { h, c, b } |
235 | 0 | } |
236 | | |
237 | | #[inline] |
238 | 0 | pub fn from_jch(jch: DtUchJch) -> DtUchHcb { |
239 | 0 | let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.); |
240 | 0 | let c = jch.c; |
241 | 0 | let h = jch.h; |
242 | 0 | DtUchHcb::new(h, c, b) |
243 | 0 | } |
244 | | |
245 | | #[inline] |
246 | 0 | pub fn to_jch(&self) -> DtUchJch { |
247 | 0 | let h = self.h; |
248 | 0 | let c = self.c; |
249 | 0 | let j = self.b / (f_powf(self.c, 1.33654221029386) + 1.); |
250 | 0 | DtUchJch::new(j, c, h) |
251 | 0 | } |
252 | | } |
253 | | |
254 | | #[cfg(test)] |
255 | | mod tests { |
256 | | use super::*; |
257 | | |
258 | | #[test] |
259 | | fn test_darktable_ucs_jch() { |
260 | | let xyy = [0.4, 0.2, 0.5]; |
261 | | let ucs = DtUchJch::from_xyy(xyy); |
262 | | let xyy_rev = ucs.to_xyy(); |
263 | | assert!( |
264 | | (xyy[0] - xyy_rev[0]).abs() < 1e-5, |
265 | | "Expected {}, got {}", |
266 | | xyy[0], |
267 | | xyy_rev[0] |
268 | | ); |
269 | | assert!( |
270 | | (xyy[1] - xyy_rev[1]).abs() < 1e-5, |
271 | | "Expected {}, got {}", |
272 | | xyy[1], |
273 | | xyy_rev[1] |
274 | | ); |
275 | | assert!( |
276 | | (xyy[2] - xyy_rev[2]).abs() < 1e-5, |
277 | | "Expected {}, got {}", |
278 | | xyy[2], |
279 | | xyy_rev[2] |
280 | | ); |
281 | | } |
282 | | |
283 | | #[test] |
284 | | fn test_darktable_hsb() { |
285 | | let jch = DtUchJch::new(0.3, 0.6, 0.4); |
286 | | let hsb = DtUchHsb::from_jch(jch); |
287 | | let r_jch = hsb.to_jch(); |
288 | | |
289 | | assert!( |
290 | | (r_jch.j - jch.j).abs() < 1e-5, |
291 | | "Expected {}, got {}", |
292 | | jch.j, |
293 | | r_jch.j |
294 | | ); |
295 | | assert!( |
296 | | (r_jch.c - jch.c).abs() < 1e-5, |
297 | | "Expected {}, got {}", |
298 | | jch.c, |
299 | | r_jch.c |
300 | | ); |
301 | | assert!( |
302 | | (r_jch.h - jch.h).abs() < 1e-5, |
303 | | "Expected {}, got {}", |
304 | | jch.h, |
305 | | r_jch.h |
306 | | ); |
307 | | } |
308 | | |
309 | | #[test] |
310 | | fn test_darktable_hcb() { |
311 | | let jch = DtUchJch::new(0.3, 0.6, 0.4); |
312 | | let hcb = DtUchHcb::from_jch(jch); |
313 | | let r_jch = hcb.to_jch(); |
314 | | |
315 | | assert!( |
316 | | (r_jch.j - jch.j).abs() < 1e-5, |
317 | | "Expected {}, got {}", |
318 | | jch.j, |
319 | | r_jch.j |
320 | | ); |
321 | | assert!( |
322 | | (r_jch.c - jch.c).abs() < 1e-5, |
323 | | "Expected {}, got {}", |
324 | | jch.c, |
325 | | r_jch.c |
326 | | ); |
327 | | assert!( |
328 | | (r_jch.h - jch.h).abs() < 1e-5, |
329 | | "Expected {}, got {}", |
330 | | jch.h, |
331 | | r_jch.h |
332 | | ); |
333 | | } |
334 | | |
335 | | #[test] |
336 | | fn test_darktable_ucs_jch_from_xyz() { |
337 | | let xyz = Xyz::new(0.4, 0.2, 0.5); |
338 | | let ucs = DtUchJch::from_xyz(xyz); |
339 | | let xyy_rev = ucs.to_xyz(); |
340 | | assert!( |
341 | | (xyz.x - xyz.x).abs() < 1e-5, |
342 | | "Expected {}, got {}", |
343 | | xyz.x, |
344 | | xyy_rev.x |
345 | | ); |
346 | | assert!( |
347 | | (xyz.y - xyz.y).abs() < 1e-5, |
348 | | "Expected {}, got {}", |
349 | | xyz.y, |
350 | | xyy_rev.y |
351 | | ); |
352 | | assert!( |
353 | | (xyz.z - xyz.z).abs() < 1e-5, |
354 | | "Expected {}, got {}", |
355 | | xyz.z, |
356 | | xyy_rev.z |
357 | | ); |
358 | | } |
359 | | } |