1use crate::Xyz;
30use crate::mlaf::mlaf;
31use pxfm::{f_atan2f, f_powf, f_sincosf};
32
33#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
35pub struct DtUchJch {
36 pub j: f32,
37 pub c: f32,
38 pub h: f32,
39}
40
41#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
43pub struct DtUchHsb {
44 pub h: f32,
45 pub s: f32,
46 pub b: f32,
47}
48
49#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
51pub struct DtUchHcb {
52 pub h: f32,
53 pub c: f32,
54 pub b: f32,
55}
56
57const DT_UCS_L_STAR_RANGE: f32 = 2.098883786377;
58
59#[inline]
60fn y_to_dt_ucs_l_star(y: f32) -> f32 {
61 let y_hat = f_powf(y, 0.631651345306265);
62 DT_UCS_L_STAR_RANGE * y_hat / (y_hat + 1.12426773749357)
63}
64
65#[inline]
66fn dt_ucs_l_star_to_y(x: f32) -> f32 {
67 f_powf(
68 1.12426773749357 * x / (DT_UCS_L_STAR_RANGE - x),
69 1.5831518565279648,
70 )
71}
72
73const L_WHITE: f32 = 0.98805060;
74
75#[inline]
76fn dt_ucs_luv_to_ucs_jch(
77 l_star: f32,
78 l_white: f32,
79 u_star_prime: f32,
80 v_star_prime: f32,
81) -> DtUchJch {
82 let m2: f32 = mlaf(u_star_prime * u_star_prime, v_star_prime, v_star_prime); let j = l_star / l_white;
86 let c =
87 15.932993652962535 * f_powf(l_star, 0.6523997524738018) * f_powf(m2, 0.6007557017508491)
88 / l_white;
89 let h = f_atan2f(v_star_prime, u_star_prime);
90 DtUchJch::new(j, c, h)
91}
92
93#[inline]
94fn 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 let mut u_c = mlaf(mlaf(BIAS[0], Y_C[0], y), X_C[0], x);
100 let mut v_c = mlaf(mlaf(BIAS[1], Y_C[1], y), X_C[1], x);
101 let d_c = mlaf(mlaf(BIAS[2], Y_C[2], y), X_C[2], x);
102
103 let div = if d_c >= 0.0 {
104 d_c.max(f32::MIN)
105 } else {
106 d_c.min(-f32::MIN)
107 };
108 u_c /= div;
109 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 let u_star = STAR_C[0] * u_c / (u_c.abs() + STAR_HF_C[0]);
115 let v_star = STAR_C[1] * v_c / (v_c.abs() + STAR_HF_C[1]);
116
117 let u_star_prime = mlaf(-1.124983854323892 * u_star, -0.980483721769325, v_star);
119 let v_star_prime = mlaf(1.86323315098672 * u_star, 1.971853092390862, v_star);
120 (u_star_prime, v_star_prime)
121}
122
123impl DtUchJch {
124 #[inline]
125 pub fn new(j: f32, c: f32, h: f32) -> DtUchJch {
126 DtUchJch { j, c, h }
127 }
128
129 #[inline]
130 pub fn from_xyz(xyz: Xyz) -> DtUchJch {
131 DtUchJch::from_xyy(xyz.to_xyy())
132 }
133
134 #[inline]
135 pub fn to_xyz(&self) -> Xyz {
136 let xyy = self.to_xyy();
137 Xyz::from_xyy(xyy)
138 }
139
140 #[inline]
141 pub fn from_xyy(xyy: [f32; 3]) -> DtUchJch {
142 let l_star = y_to_dt_ucs_l_star(xyy[2]);
143 let (u_star_prime, v_star_prime) = dt_ucs_xy_to_uv(xyy[0], xyy[1]);
146 dt_ucs_luv_to_ucs_jch(l_star, L_WHITE, u_star_prime, v_star_prime)
147 }
148
149 #[inline]
150 pub fn to_xyy(&self) -> [f32; 3] {
151 let l_star = (self.j * L_WHITE).max(0.0).min(2.09885);
153 let m = if l_star != 0. {
154 f_powf(
155 self.c * L_WHITE / (15.932993652962535 * f_powf(l_star, 0.6523997524738018)),
156 0.8322850678616855,
157 )
158 } else {
159 0.
160 };
161
162 let sin_cos_h = f_sincosf(self.h);
163 let u_star_prime = m * sin_cos_h.1;
164 let v_star_prime = m * sin_cos_h.0;
165
166 let u_star = mlaf(
168 -5.037522385190711 * u_star_prime,
169 -2.504856328185843,
170 v_star_prime,
171 );
172 let v_star = mlaf(
173 4.760029407436461 * u_star_prime,
174 2.874012963239247,
175 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 let u_c = -HF[0] * u_star / (u_star.abs() - F[0]);
182 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 let mut x = mlaf(mlaf(BIAS[0], V_C[0], v_c), U_C[0], u_c);
193 let mut y = mlaf(mlaf(BIAS[1], V_C[1], v_c), U_C[1], u_c);
194 let d = mlaf(mlaf(BIAS[2], V_C[2], v_c), U_C[2], u_c);
195
196 let div = if d >= 0.0 {
197 d.max(f32::MIN)
198 } else {
199 d.min(-f32::MIN)
200 };
201 x /= div;
202 y /= div;
203 let yb = dt_ucs_l_star_to_y(l_star);
204 [x, y, yb]
205 }
206}
207
208impl DtUchHsb {
209 #[inline]
210 pub fn new(h: f32, s: f32, b: f32) -> DtUchHsb {
211 DtUchHsb { h, s, b }
212 }
213
214 #[inline]
215 pub fn from_jch(jch: DtUchJch) -> DtUchHsb {
216 let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
217 let s = if b > 0. { jch.c / b } else { 0. };
218 let h = jch.h;
219 DtUchHsb::new(h, s, b)
220 }
221
222 #[inline]
223 pub fn to_jch(&self) -> DtUchJch {
224 let h = self.h;
225 let c = self.s * self.b;
226 let j = self.b / (f_powf(c, 1.33654221029386) + 1.);
227 DtUchJch::new(j, c, h)
228 }
229}
230
231impl DtUchHcb {
232 #[inline]
233 pub fn new(h: f32, c: f32, b: f32) -> DtUchHcb {
234 DtUchHcb { h, c, b }
235 }
236
237 #[inline]
238 pub fn from_jch(jch: DtUchJch) -> DtUchHcb {
239 let b = jch.j * (f_powf(jch.c, 1.33654221029386) + 1.);
240 let c = jch.c;
241 let h = jch.h;
242 DtUchHcb::new(h, c, b)
243 }
244
245 #[inline]
246 pub fn to_jch(&self) -> DtUchJch {
247 let h = self.h;
248 let c = self.c;
249 let j = self.b / (f_powf(self.c, 1.33654221029386) + 1.);
250 DtUchJch::new(j, c, h)
251 }
252}
253
254#[cfg(test)]
255mod 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}