1use core::{
2 marker::PhantomData,
3 ops::{Div, Mul},
4};
5
6use crate::{
7 angle::{RealAngle, SignedAngle},
8 bool_mask::{LazySelect, Select},
9 clamp,
10 hues::Cam16Hue,
11 num::{
12 Abs, Arithmetics, Clamp, Exp, FromScalar, One, PartialCmp, Powf, Real, Signum, Sqrt,
13 Trigonometry, Zero,
14 },
15 white_point,
16 xyz::Xyz,
17};
18
19use super::{Cam16, Parameters};
20
21use self::{chromaticity::ChromaticityType, luminance::LuminanceType};
22
23pub(crate) mod chromaticity;
24pub(crate) mod luminance;
25
26pub(crate) fn xyz_to_cam16<T>(
41 xyz: Xyz<white_point::Any, T>,
42 parameters: DependentParameters<T::Scalar>,
43) -> Cam16<T>
44where
45 T: Real
46 + FromScalar
47 + Arithmetics
48 + Powf
49 + Sqrt
50 + Abs
51 + Signum
52 + Trigonometry
53 + RealAngle
54 + Clone,
55 T::Scalar: Clone,
56{
57 let xyz = xyz.with_white_point() * T::from_f64(100.0); let d_rgb = map3(parameters.d_rgb.clone(), T::from_scalar);
59
60 let [r_a, g_a, b_a] = map3(mul3(m16(xyz), d_rgb), |component| {
61 parameters.adapt.run(component)
62 });
63 let a = r_a.clone() + (T::from_f64(-12.0) * &g_a + &b_a) / T::from_f64(11.0); let b = (r_a.clone() + &g_a - T::from_f64(2.0) * &b_a) / T::from_f64(9.0); let h_rad = b.clone().atan2(a.clone()); let h = Cam16Hue::from_radians(h_rad.clone()); let e_t = T::from_f64(0.25) * (T::cos(h_rad + T::from_f64(2.0)) + T::from_f64(3.8));
68 let capital_a = T::from_scalar(parameters.n_bb)
69 * (T::from_f64(2.0) * &r_a + &g_a + T::from_f64(0.05) * &b_a);
70 let j_root = (capital_a / T::from_scalar(parameters.a_w.clone())).powf(
71 T::from_f64(0.5) * T::from_scalar(parameters.c.clone()) * T::from_scalar(parameters.z),
72 );
73
74 let j = calculate_lightness(j_root.clone()); let q = calculate_brightness(
76 j_root.clone(),
77 T::from_scalar(parameters.c.clone()),
78 T::from_scalar(parameters.a_w.clone()),
79 T::from_scalar(parameters.f_l_4.clone()),
80 ); let t = T::from_f64(5e4) / T::from_f64(13.0)
83 * T::from_scalar(parameters.n_c)
84 * T::from_scalar(parameters.n_cb)
85 * e_t
86 * (a.clone() * a + b.clone() * b).sqrt()
87 / (r_a + g_a + T::from_f64(1.05) * b_a + T::from_f64(0.305));
88 let alpha = t.powf(T::from_f64(0.9))
89 * (T::from_f64(1.64) - T::from_f64(0.29).powf(T::from_scalar(parameters.n)))
90 .powf(T::from_f64(0.73));
91
92 let c = calculate_chroma(j_root, alpha.clone()); let m = calculate_colorfulness(T::from_scalar(parameters.f_l_4), c.clone()); let s = calculate_saturation(
95 T::from_scalar(parameters.c),
96 T::from_scalar(parameters.a_w),
97 alpha,
98 ); Cam16 {
101 lightness: j,
102 chroma: c,
103 hue: h,
104 brightness: q,
105 colorfulness: m,
106 saturation: s,
107 }
108}
109
110fn calculate_lightness<T>(j_root: T) -> T
111where
112 T: Real + Arithmetics,
113{
114 T::from_f64(100.0) * &j_root * j_root
115}
116
117fn calculate_brightness<T>(j_root: T, param_c: T, param_a_w: T, param_f_l_4: T) -> T
118where
119 T: Real + Arithmetics,
120{
121 T::from_f64(4.0) / param_c * j_root * (T::from_f64(4.0) + param_a_w) * param_f_l_4
122}
123
124#[inline]
125pub(super) fn calculate_chroma<T>(j_root: T, alpha: T) -> T
126where
127 T: Mul<T, Output = T>,
128{
129 j_root * alpha
130}
131
132#[inline]
133pub(super) fn calculate_colorfulness<T>(param_f_l_4: T, chroma: T) -> T
134where
135 T: Mul<T, Output = T>,
136{
137 param_f_l_4 * chroma
138}
139
140#[inline]
141pub(super) fn calculate_saturation<T>(param_c: T, param_a_w: T, alpha: T) -> T
142where
143 T: Real + Arithmetics + Sqrt,
144{
145 T::from_f64(50.0) * (param_c * alpha / (param_a_w + T::from_f64(4.0))).sqrt()
146}
147
148#[inline]
149pub(crate) fn cam16_to_xyz<T>(
150 cam16: (LuminanceType<T>, ChromaticityType<T>, Cam16Hue<T>),
151 parameters: DependentParameters<T::Scalar>,
152) -> Xyz<white_point::Any, T>
153where
154 T: Real
155 + FromScalar
156 + One
157 + Zero
158 + Sqrt
159 + Powf
160 + Abs
161 + Signum
162 + Arithmetics
163 + Trigonometry
164 + RealAngle
165 + SignedAngle
166 + PartialCmp
167 + Clone,
168 T::Mask: LazySelect<T> + Clone,
169 T::Scalar: Clone,
170{
171 let is_black = match &cam16.0 {
173 LuminanceType::Lightness(lightness) => lightness.eq(&T::zero()),
174 LuminanceType::Brightness(brightness) => brightness.eq(&T::zero()),
175 };
176
177 let xyz = non_black_cam16_to_xyz(cam16, parameters);
178 Xyz {
179 x: is_black.clone().select(T::zero(), xyz.x),
180 y: is_black.clone().select(T::zero(), xyz.y),
181 z: is_black.select(T::zero(), xyz.z),
182 white_point: PhantomData,
183 }
184}
185
186fn non_black_cam16_to_xyz<T>(
188 cam16: (LuminanceType<T>, ChromaticityType<T>, Cam16Hue<T>),
189 parameters: DependentParameters<T::Scalar>,
190) -> Xyz<white_point::Any, T>
191where
192 T: Real
193 + FromScalar
194 + One
195 + Sqrt
196 + Powf
197 + Abs
198 + Signum
199 + Arithmetics
200 + Trigonometry
201 + RealAngle
202 + SignedAngle
203 + Clone,
204 T::Scalar: Clone,
205{
206 let h_rad = cam16.2.into_radians();
207 let (sin_h, cos_h) = h_rad.clone().sin_cos();
208 let j_root = match cam16.0 {
209 LuminanceType::Lightness(j) => lightness_to_j_root(j),
210 LuminanceType::Brightness(q) => brightness_to_j_root(
211 q,
212 T::from_scalar(parameters.c.clone()),
213 T::from_scalar(parameters.a_w.clone()),
214 T::from_scalar(parameters.f_l_4.clone()),
215 ),
216 };
217 let alpha = match cam16.1 {
218 ChromaticityType::Chroma(c) => c / &j_root,
219 ChromaticityType::Colorfulness(m) => {
220 colorfulness_to_chroma(m, T::from_scalar(parameters.f_l_4)) / &j_root
221 }
222 ChromaticityType::Saturation(s) => saturation_to_alpha(
223 s,
224 T::from_scalar(parameters.c.clone()),
225 T::from_scalar(parameters.a_w.clone()),
226 ),
227 };
228 let t = (alpha
229 * (T::from_f64(1.64) - T::from_f64(0.29).powf(T::from_scalar(parameters.n)))
230 .powf(T::from_f64(-0.73)))
231 .powf(T::from_f64(10.0) / T::from_f64(9.0));
232 let e_t = T::from_f64(0.25) * ((h_rad + T::from_f64(2.0)).cos() + T::from_f64(3.8));
233 let capital_a = T::from_scalar(parameters.a_w)
234 * j_root
235 .powf(T::from_f64(2.0) / T::from_scalar(parameters.c) / T::from_scalar(parameters.z));
236 let p_1 = T::from_f64(5e4) / T::from_f64(13.0)
237 * T::from_scalar(parameters.n_c)
238 * T::from_scalar(parameters.n_cb)
239 * e_t;
240 let p_2 = capital_a / T::from_scalar(parameters.n_bb);
241 let r = T::from_f64(23.0) * (T::from_f64(0.305) + &p_2) * &t
242 / (T::from_f64(23.0) * p_1
243 + t * (T::from_f64(11.0) * &cos_h + T::from_f64(108.0) * &sin_h));
244 let a = cos_h * &r;
245 let b = sin_h * r;
246 let denom = T::one() / T::from_f64(1403.0);
247 let rgb_c = [
248 (T::from_f64(460.0) * &p_2 + T::from_f64(451.0) * &a + T::from_f64(288.0) * &b) * &denom,
249 (T::from_f64(460.0) * &p_2 - T::from_f64(891.0) * &a - T::from_f64(261.0) * &b) * &denom,
250 (T::from_f64(460.0) * p_2 - T::from_f64(220.0) * a - T::from_f64(6300.0) * b) * &denom,
251 ];
252
253 let unadapt = parameters.unadapt;
254 let rgb_c = map3(rgb_c, |component| unadapt.run(component));
255 let d_rgb_inv = map3(parameters.d_rgb_inv, T::from_scalar);
256
257 m16_inv(mul3(rgb_c, d_rgb_inv)) / T::from_f64(100.0) }
259
260pub(super) fn prepare_parameters<T>(
261 parameters: Parameters<Xyz<white_point::Any, T>, T>,
262) -> DependentParameters<T>
263where
264 T: Real
265 + FromScalar<Scalar = T>
266 + One
267 + Zero
268 + Clamp
269 + PartialCmp
270 + Arithmetics
271 + Powf
272 + Sqrt
273 + Exp
274 + Abs
275 + Signum
276 + Clone,
277 T::Mask: LazySelect<T>,
278{
279 let xyz_w = parameters.white_point * T::from_f64(100.0); let l_a = parameters.adapting_luminance;
282 let y_b = parameters.background_luminance * T::from_f64(100.0); let y_w = xyz_w.y.clone();
284 let surround = parameters.surround.into_percent() * T::from_f64(0.1);
285 let c = lazy_select! {
286 if surround.gt_eq(&T::one()) => lerp(
287 T::from_f64(0.59),
288 T::from_f64(0.69),
289 surround.clone() - T::one(),
290 ),
291 else => lerp(T::from_f64(0.525), T::from_f64(0.59), surround.clone())
292 };
293 let f = lazy_select! {
294 if c.gt_eq(&T::from_f64(0.59)) => lerp(
295 T::from_f64(0.9),
296 T::one(),
297 (c.clone() - T::from_f64(0.59)) / T::from_f64(0.1)),
298 else => lerp(
299 T::from_f64(0.8),
300 T::from_f64(0.9),
301 (c.clone() - T::from_f64(0.525)) / T::from_f64(0.065)
302 )
303 };
304 let n_c = f.clone();
305 let k = T::one() / (T::from_f64(5.0) * &l_a + T::one());
306 let f_l = {
307 let k4 = k.clone() * &k * &k * k;
309 let k4_inv = T::one() - &k4;
310 let a_third = T::one() / T::from_f64(3.0);
311
312 k4 * &l_a + T::from_f64(0.1) * &k4_inv * k4_inv * (T::from_f64(5.0) * &l_a).powf(a_third)
313 };
314 let f_l_4 = f_l.clone().powf(T::from_f64(0.25));
315 let n = y_b / &y_w;
316 let z = T::from_f64(1.48) + n.clone().sqrt(); let n_bb = T::from_f64(0.725) * n.clone().powf(T::from_f64(-0.2)); let n_cb = n_bb.clone();
319 let d = match parameters.discounting {
321 super::Discounting::Auto => {
322 f * (T::one()
324 - T::one() / T::from_f64(3.6)
325 * Exp::exp((-l_a - T::from_f64(42.0)) / T::from_f64(92.0)))
326 }
327 super::Discounting::Custom(degree) => degree,
328 };
329
330 let d = clamp(d, T::zero(), T::one());
331
332 let rgb_w = m16(xyz_w); let d_rgb = map3(rgb_w.clone(), |c_w| {
334 lerp(T::one(), y_w.clone() / c_w, d.clone())
335 });
336 let d_rgb_inv = map3(d_rgb.clone(), |d_c| T::one() / d_c);
337 let rgb_cw = mul3(rgb_w, d_rgb.clone());
338
339 let adapt = Adapt { f_l: f_l.clone() };
340
341 let exponent = T::one() / T::from_f64(0.42);
342 let unadapt = Unadapt {
343 constant: T::from_f64(100.0) / f_l * T::from_f64(27.13).powf(exponent.clone()),
344 exponent,
345 };
346
347 let [rgb_aw1, rgb_aw2, rgb_aw3] = map3(rgb_cw, |component| adapt.run(component));
348 let a_w = n_bb.clone() * (T::from_f64(2.0) * rgb_aw1 + rgb_aw2 + T::from_f64(0.05) * rgb_aw3);
349
350 DependentParameters {
351 d_rgb,
352 d_rgb_inv,
353 n,
354 n_bb,
355 n_c,
356 n_cb,
357 a_w,
358 c,
359 z,
360 f_l_4,
361 adapt,
362 unadapt,
363 }
364}
365
366#[inline]
367pub(super) fn lightness_to_brightness<T>(
368 lightness: T,
369 param_c: T,
370 param_a_w: T,
371 param_f_l_4: T,
372) -> T
373where
374 T: Real + Arithmetics + Sqrt,
375{
376 let j_root = lightness_to_j_root(lightness);
377 calculate_brightness(j_root, param_c, param_a_w, param_f_l_4)
378}
379
380#[inline]
381pub(super) fn brightness_to_lightness<T>(
382 brightness: T,
383 param_c: T,
384 param_a_w: T,
385 param_f_l_4: T,
386) -> T
387where
388 T: Real + Arithmetics,
389{
390 let j_root = brightness_to_j_root(brightness, param_c, param_a_w, param_f_l_4);
391 calculate_lightness(j_root)
392}
393
394#[inline]
395pub(super) fn chroma_to_colorfulness<T>(chroma: T, param_f_l_4: T) -> T
396where
397 T: Mul<T, Output = T>,
398{
399 param_f_l_4 * chroma
400}
401
402#[inline]
403pub(super) fn chroma_to_saturation<T>(chroma: T, lightness: T, param_c: T, param_a_w: T) -> T
404where
405 T: Real + Arithmetics + Sqrt + Clone,
406{
407 let j_root = lightness_to_j_root(lightness);
408 let alpha = chroma / &j_root;
409
410 calculate_saturation(param_c, param_a_w, alpha)
411}
412
413#[inline]
414pub(super) fn colorfulness_to_chroma<T>(colorfulness: T, param_f_l_4: T) -> T
415where
416 T: Div<T, Output = T>,
417{
418 colorfulness / param_f_l_4
419}
420
421#[inline]
422pub(super) fn saturation_to_chroma<T>(saturation: T, lightness: T, param_c: T, param_a_w: T) -> T
423where
424 T: Real + Arithmetics + Sqrt,
425{
426 let j_root = lightness_to_j_root(lightness);
427 let alpha = saturation_to_alpha(saturation, param_c, param_a_w);
428
429 calculate_chroma(j_root, alpha)
430}
431
432#[inline]
433fn lightness_to_j_root<T>(lightness: T) -> T
434where
435 T: Real + Mul<T, Output = T> + Sqrt,
436{
437 lightness.sqrt() * T::from_f64(0.1)
438}
439
440#[inline]
441fn brightness_to_j_root<T>(brightness: T, param_c: T, param_a_w: T, param_f_l_4: T) -> T
442where
443 T: Real + Arithmetics,
444{
445 T::from_f64(0.25) * param_c * brightness / ((T::from_f64(4.0) + param_a_w) * param_f_l_4)
446}
447
448#[inline]
449fn saturation_to_alpha<T>(saturation: T, param_c: T, param_a_w: T) -> T
450where
451 T: Real + Arithmetics,
452{
453 T::from_f64(0.0004) * &saturation * saturation * (T::from_f64(4.0) + param_a_w) / param_c
454}
455
456#[derive(Clone, Copy)]
457pub(crate) struct DependentParameters<T> {
458 d_rgb: [T; 3],
459 d_rgb_inv: [T; 3],
460 n: T,
461 n_bb: T,
462 n_c: T,
463 n_cb: T,
464 pub(super) a_w: T,
465 pub(super) c: T,
466 z: T,
467 pub(super) f_l_4: T,
468 adapt: Adapt<T>,
469 unadapt: Unadapt<T>,
470}
471
472#[derive(Clone, Copy)]
473struct Adapt<T> {
474 f_l: T,
475}
476
477impl<T> Adapt<T> {
478 fn run<V>(&self, component: V) -> V
479 where
480 V: Real + FromScalar<Scalar = T> + Abs + Signum + Powf + Arithmetics + Clone,
481 T: Clone,
482 {
483 let x = (V::from_scalar(self.f_l.clone()) * component.clone().abs() * V::from_f64(0.01))
484 .powf(V::from_f64(0.42));
485 component.signum() * V::from_f64(400.0) * &x / (x + V::from_f64(27.13))
486 }
487}
488
489#[derive(Clone, Copy)]
490struct Unadapt<T> {
491 constant: T,
492 exponent: T,
493}
494
495impl<T> Unadapt<T> {
496 fn run<V>(&self, component: V) -> V
497 where
498 V: Real + FromScalar<Scalar = T> + Abs + Signum + Powf + Arithmetics + Clone,
499 T: Clone,
500 {
501 let c_abs = component.clone().abs();
502 component.signum()
503 * V::from_scalar(self.constant.clone())
504 * (c_abs.clone() / (V::from_f64(400.0) - c_abs))
505 .powf(V::from_scalar(self.exponent.clone()))
506 }
507}
508
509fn lerp<T>(from: T, to: T, factor: T) -> T
510where
511 T: One + Arithmetics,
512{
513 (T::one() - &factor) * from + factor * to
514}
515
516fn m16<T>(xyz: Xyz<white_point::Any, T>) -> [T; 3]
517where
518 T: Real + Arithmetics,
519{
520 let Xyz { x, y, z, .. } = xyz;
521
522 #[rustfmt::skip]
523 let rgb = [
524 T::from_f64( 0.401288) * &x + T::from_f64(0.650173) * &y - T::from_f64(0.051461) * &z,
525 T::from_f64(-0.250268) * &x + T::from_f64(1.204414) * &y + T::from_f64(0.045854) * &z,
526 T::from_f64(-0.002079) * x + T::from_f64(0.048952) * y + T::from_f64(0.953127) * z,
527 ];
528
529 rgb
530}
531
532fn m16_inv<T>(rgb: [T; 3]) -> Xyz<white_point::Any, T>
533where
534 T: Real + Arithmetics,
535{
536 let [r, g, b] = rgb;
537
538 #[rustfmt::skip]
539 #[allow(clippy::excessive_precision)] let xyz = Xyz {
541 x: T::from_f64( 1.862067855087233e+0) * &r - T::from_f64(1.011254630531685e+0) * &g + T::from_f64(1.491867754444518e-1) * &b,
542 y: T::from_f64( 3.875265432361372e-1) * &r + T::from_f64(6.214474419314753e-1) * &g - T::from_f64(8.973985167612518e-3) * &b,
543 z: T::from_f64(-1.584149884933386e-2) * r - T::from_f64(3.412293802851557e-2) * g + T::from_f64(1.049964436877850e+0) * b,
544 white_point: PhantomData
545 };
546
547 xyz
548}
549
550fn map3<T, U>(array: [T; 3], mut map: impl FnMut(T) -> U) -> [U; 3] {
551 let [a1, a2, a3] = array;
552 [map(a1), map(a2), map(a3)]
553}
554
555fn mul3<T>(lhs: [T; 3], rhs: [T; 3]) -> [T; 3]
556where
557 T: Mul<T, Output = T>,
558{
559 let [l1, l2, l3] = lhs;
560 let [r1, r2, r3] = rhs;
561
562 [l1 * r1, l2 * r2, l3 * r3]
563}