1#[repr(C)]
11#[derive(Debug, Copy, Clone, Default, PartialOrd)]
12pub struct Luv {
13 pub l: f32,
15 pub u: f32,
23 pub v: f32,
31}
32
33#[repr(C)]
35#[derive(Debug, Copy, Clone, Default, PartialOrd)]
36pub struct LCh {
37 pub l: f32,
41 pub c: f32,
48 pub h: f32,
54}
55
56use crate::mlaf::mlaf;
57use crate::{Chromaticity, Lab, Xyz};
58use num_traits::Pow;
59use pxfm::{f_atan2f, f_cbrtf, f_hypotf, f_powf, f_sincosf};
60use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
61
62pub(crate) const LUV_WHITE_U_PRIME: f32 = 4.0f32 * Chromaticity::D50.to_xyz().y
63 / (Chromaticity::D50.to_xyz().x
64 + 15.0 * Chromaticity::D50.to_xyz().y
65 + 3.0 * Chromaticity::D50.to_xyz().z);
66pub(crate) const LUV_WHITE_V_PRIME: f32 = 9.0f32 * Chromaticity::D50.to_xyz().y
67 / (Chromaticity::D50.to_xyz().x
68 + 15.0 * Chromaticity::D50.to_xyz().y
69 + 3.0 * Chromaticity::D50.to_xyz().z);
70
71pub(crate) const LUV_CUTOFF_FORWARD_Y: f32 = (6f32 / 29f32) * (6f32 / 29f32) * (6f32 / 29f32);
72pub(crate) const LUV_MULTIPLIER_FORWARD_Y: f32 = (29f32 / 3f32) * (29f32 / 3f32) * (29f32 / 3f32);
73pub(crate) const LUV_MULTIPLIER_INVERSE_Y: f32 = (3f32 / 29f32) * (3f32 / 29f32) * (3f32 / 29f32);
74impl Luv {
75 #[inline]
77 #[allow(clippy::manual_clamp)]
78 pub fn from_xyz(xyz: Xyz) -> Self {
79 let [x, y, z] = [xyz.x, xyz.y, xyz.z];
80 let den = mlaf(mlaf(x, 15.0, y), 3.0, z);
81
82 let l = (if y < LUV_CUTOFF_FORWARD_Y {
83 LUV_MULTIPLIER_FORWARD_Y * y
84 } else {
85 116. * f_cbrtf(y) - 16.
86 })
87 .min(100.)
88 .max(0.);
89 let (u, v);
90 if den != 0f32 {
91 let u_prime = 4. * x / den;
92 let v_prime = 9. * y / den;
93 u = 13. * l * (u_prime - LUV_WHITE_U_PRIME);
94 v = 13. * l * (v_prime - LUV_WHITE_V_PRIME);
95 } else {
96 u = 0.;
97 v = 0.;
98 }
99
100 Luv { l, u, v }
101 }
102
103 #[inline]
105 pub fn to_xyz(&self) -> Xyz {
106 if self.l <= 0. {
107 return Xyz::new(0., 0., 0.);
108 }
109 let l13 = 1. / (13. * self.l);
110 let u = mlaf(LUV_WHITE_U_PRIME, self.u, l13);
111 let v = mlaf(LUV_WHITE_V_PRIME, self.v, l13);
112 let y = if self.l > 8. {
113 let jx = (self.l + 16.) / 116.;
114 jx * jx * jx
115 } else {
116 self.l * LUV_MULTIPLIER_INVERSE_Y
117 };
118 let (x, z);
119 if v != 0. {
120 let den = 1. / (4. * v);
121 x = y * 9. * u * den;
122 z = y * mlaf(mlaf(12.0, -3.0, u), -20., v) * den;
123 } else {
124 x = 0.;
125 z = 0.;
126 }
127
128 Xyz::new(x, y, z)
129 }
130
131 #[inline]
132 pub const fn new(l: f32, u: f32, v: f32) -> Luv {
133 Luv { l, u, v }
134 }
135}
136
137impl LCh {
138 #[inline]
139 pub const fn new(l: f32, c: f32, h: f32) -> Self {
140 LCh { l, c, h }
141 }
142
143 #[inline]
145 pub fn from_luv(luv: Luv) -> Self {
146 LCh {
147 l: luv.l,
148 c: f_hypotf(luv.u, luv.v),
149 h: f_atan2f(luv.v, luv.u),
150 }
151 }
152
153 #[inline]
155 pub fn from_lab(lab: Lab) -> Self {
156 LCh {
157 l: lab.l,
158 c: f_hypotf(lab.a, lab.b),
159 h: f_atan2f(lab.b, lab.a),
160 }
161 }
162
163 #[inline]
165 pub fn from_xyz(xyz: Xyz) -> Self {
166 Self::from_luv(Luv::from_xyz(xyz))
167 }
168
169 #[inline]
171 pub fn from_xyz_lab(xyz: Xyz) -> Self {
172 Self::from_lab(Lab::from_xyz(xyz))
173 }
174
175 #[inline]
177 pub fn to_xyz(&self) -> Xyz {
178 self.to_luv().to_xyz()
179 }
180
181 #[inline]
183 pub fn to_xyz_lab(&self) -> Xyz {
184 self.to_lab().to_xyz()
185 }
186
187 #[inline]
188 pub fn to_luv(&self) -> Luv {
189 let sincos = f_sincosf(self.h);
190 Luv {
191 l: self.l,
192 u: self.c * sincos.1,
193 v: self.c * sincos.0,
194 }
195 }
196
197 #[inline]
198 pub fn to_lab(&self) -> Lab {
199 let sincos = f_sincosf(self.h);
200 Lab {
201 l: self.l,
202 a: self.c * sincos.1,
203 b: self.c * sincos.0,
204 }
205 }
206}
207
208impl PartialEq<Luv> for Luv {
209 #[inline]
211 fn eq(&self, other: &Self) -> bool {
212 if self.l != other.l {
213 false
214 } else if self.l == 0.0 {
215 true
216 } else {
217 self.u == other.u && self.v == other.v
218 }
219 }
220}
221
222impl PartialEq<LCh> for LCh {
223 #[inline]
226 fn eq(&self, other: &Self) -> bool {
227 if self.l != other.l {
228 false
229 } else if self.l == 0.0 {
230 true
231 } else if self.c != other.c {
232 false
233 } else if self.c == 0.0 {
234 true
235 } else {
236 use std::f32::consts::TAU;
237 self.h.rem_euclid(TAU) == other.h.rem_euclid(TAU)
238 }
239 }
240}
241
242impl Luv {
243 #[inline]
244 pub fn euclidean_distance(&self, other: Luv) -> f32 {
245 let dl = self.l - other.l;
246 let du = self.u - other.u;
247 let dv = self.v - other.v;
248 (dl * dl + du * du + dv * dv).sqrt()
249 }
250}
251
252impl LCh {
253 #[inline]
254 pub fn euclidean_distance(&self, other: LCh) -> f32 {
255 let dl = self.l - other.l;
256 let dc = self.c - other.c;
257 let dh = self.h - other.h;
258 (dl * dl + dc * dc + dh * dh).sqrt()
259 }
260}
261
262impl Luv {
263 #[inline]
264 pub const fn taxicab_distance(&self, other: Self) -> f32 {
265 let dl = self.l - other.l;
266 let du = self.u - other.u;
267 let dv = self.v - other.v;
268 dl.abs() + du.abs() + dv.abs()
269 }
270}
271
272impl LCh {
273 #[inline]
274 pub const fn taxicab_distance(&self, other: Self) -> f32 {
275 let dl = self.l - other.l;
276 let dc = self.c - other.c;
277 let dh = self.h - other.h;
278 dl.abs() + dc.abs() + dh.abs()
279 }
280}
281
282impl Add<Luv> for Luv {
283 type Output = Luv;
284
285 #[inline]
286 fn add(self, rhs: Luv) -> Luv {
287 Luv::new(self.l + rhs.l, self.u + rhs.u, self.v + rhs.v)
288 }
289}
290
291impl Add<LCh> for LCh {
292 type Output = LCh;
293
294 #[inline]
295 fn add(self, rhs: LCh) -> LCh {
296 LCh::new(self.l + rhs.l, self.c + rhs.c, self.h + rhs.h)
297 }
298}
299
300impl Sub<Luv> for Luv {
301 type Output = Luv;
302
303 #[inline]
304 fn sub(self, rhs: Luv) -> Luv {
305 Luv::new(self.l - rhs.l, self.u - rhs.u, self.v - rhs.v)
306 }
307}
308
309impl Sub<LCh> for LCh {
310 type Output = LCh;
311
312 #[inline]
313 fn sub(self, rhs: LCh) -> LCh {
314 LCh::new(self.l - rhs.l, self.c - rhs.c, self.h - rhs.h)
315 }
316}
317
318impl Mul<Luv> for Luv {
319 type Output = Luv;
320
321 #[inline]
322 fn mul(self, rhs: Luv) -> Luv {
323 Luv::new(self.l * rhs.l, self.u * rhs.u, self.v * rhs.v)
324 }
325}
326
327impl Mul<LCh> for LCh {
328 type Output = LCh;
329
330 #[inline]
331 fn mul(self, rhs: LCh) -> LCh {
332 LCh::new(self.l * rhs.l, self.c * rhs.c, self.h * rhs.h)
333 }
334}
335
336impl Div<Luv> for Luv {
337 type Output = Luv;
338
339 #[inline]
340 fn div(self, rhs: Luv) -> Luv {
341 Luv::new(self.l / rhs.l, self.u / rhs.u, self.v / rhs.v)
342 }
343}
344
345impl Div<LCh> for LCh {
346 type Output = LCh;
347
348 #[inline]
349 fn div(self, rhs: LCh) -> LCh {
350 LCh::new(self.l / rhs.l, self.c / rhs.c, self.h / rhs.h)
351 }
352}
353
354impl Add<f32> for Luv {
355 type Output = Luv;
356
357 #[inline]
358 fn add(self, rhs: f32) -> Self::Output {
359 Luv::new(self.l + rhs, self.u + rhs, self.v + rhs)
360 }
361}
362
363impl Add<f32> for LCh {
364 type Output = LCh;
365
366 #[inline]
367 fn add(self, rhs: f32) -> Self::Output {
368 LCh::new(self.l + rhs, self.c + rhs, self.h + rhs)
369 }
370}
371
372impl Sub<f32> for Luv {
373 type Output = Luv;
374
375 #[inline]
376 fn sub(self, rhs: f32) -> Self::Output {
377 Luv::new(self.l - rhs, self.u - rhs, self.v - rhs)
378 }
379}
380
381impl Sub<f32> for LCh {
382 type Output = LCh;
383
384 #[inline]
385 fn sub(self, rhs: f32) -> Self::Output {
386 LCh::new(self.l - rhs, self.c - rhs, self.h - rhs)
387 }
388}
389
390impl Mul<f32> for Luv {
391 type Output = Luv;
392
393 #[inline]
394 fn mul(self, rhs: f32) -> Self::Output {
395 Luv::new(self.l * rhs, self.u * rhs, self.v * rhs)
396 }
397}
398
399impl Mul<f32> for LCh {
400 type Output = LCh;
401
402 #[inline]
403 fn mul(self, rhs: f32) -> Self::Output {
404 LCh::new(self.l * rhs, self.c * rhs, self.h * rhs)
405 }
406}
407
408impl Div<f32> for Luv {
409 type Output = Luv;
410
411 #[inline]
412 fn div(self, rhs: f32) -> Self::Output {
413 Luv::new(self.l / rhs, self.u / rhs, self.v / rhs)
414 }
415}
416
417impl Div<f32> for LCh {
418 type Output = LCh;
419
420 #[inline]
421 fn div(self, rhs: f32) -> Self::Output {
422 LCh::new(self.l / rhs, self.c / rhs, self.h / rhs)
423 }
424}
425
426impl AddAssign<Luv> for Luv {
427 #[inline]
428 fn add_assign(&mut self, rhs: Luv) {
429 self.l += rhs.l;
430 self.u += rhs.u;
431 self.v += rhs.v;
432 }
433}
434
435impl AddAssign<LCh> for LCh {
436 #[inline]
437 fn add_assign(&mut self, rhs: LCh) {
438 self.l += rhs.l;
439 self.c += rhs.c;
440 self.h += rhs.h;
441 }
442}
443
444impl SubAssign<Luv> for Luv {
445 #[inline]
446 fn sub_assign(&mut self, rhs: Luv) {
447 self.l -= rhs.l;
448 self.u -= rhs.u;
449 self.v -= rhs.v;
450 }
451}
452
453impl SubAssign<LCh> for LCh {
454 #[inline]
455 fn sub_assign(&mut self, rhs: LCh) {
456 self.l -= rhs.l;
457 self.c -= rhs.c;
458 self.h -= rhs.h;
459 }
460}
461
462impl MulAssign<Luv> for Luv {
463 #[inline]
464 fn mul_assign(&mut self, rhs: Luv) {
465 self.l *= rhs.l;
466 self.u *= rhs.u;
467 self.v *= rhs.v;
468 }
469}
470
471impl MulAssign<LCh> for LCh {
472 #[inline]
473 fn mul_assign(&mut self, rhs: LCh) {
474 self.l *= rhs.l;
475 self.c *= rhs.c;
476 self.h *= rhs.h;
477 }
478}
479
480impl DivAssign<Luv> for Luv {
481 #[inline]
482 fn div_assign(&mut self, rhs: Luv) {
483 self.l /= rhs.l;
484 self.u /= rhs.u;
485 self.v /= rhs.v;
486 }
487}
488
489impl DivAssign<LCh> for LCh {
490 #[inline]
491 fn div_assign(&mut self, rhs: LCh) {
492 self.l /= rhs.l;
493 self.c /= rhs.c;
494 self.h /= rhs.h;
495 }
496}
497
498impl AddAssign<f32> for Luv {
499 #[inline]
500 fn add_assign(&mut self, rhs: f32) {
501 self.l += rhs;
502 self.u += rhs;
503 self.v += rhs;
504 }
505}
506
507impl AddAssign<f32> for LCh {
508 #[inline]
509 fn add_assign(&mut self, rhs: f32) {
510 self.l += rhs;
511 self.c += rhs;
512 self.h += rhs;
513 }
514}
515
516impl SubAssign<f32> for Luv {
517 #[inline]
518 fn sub_assign(&mut self, rhs: f32) {
519 self.l -= rhs;
520 self.u -= rhs;
521 self.v -= rhs;
522 }
523}
524
525impl SubAssign<f32> for LCh {
526 #[inline]
527 fn sub_assign(&mut self, rhs: f32) {
528 self.l -= rhs;
529 self.c -= rhs;
530 self.h -= rhs;
531 }
532}
533
534impl MulAssign<f32> for Luv {
535 #[inline]
536 fn mul_assign(&mut self, rhs: f32) {
537 self.l *= rhs;
538 self.u *= rhs;
539 self.v *= rhs;
540 }
541}
542
543impl MulAssign<f32> for LCh {
544 #[inline]
545 fn mul_assign(&mut self, rhs: f32) {
546 self.l *= rhs;
547 self.c *= rhs;
548 self.h *= rhs;
549 }
550}
551
552impl DivAssign<f32> for Luv {
553 #[inline]
554 fn div_assign(&mut self, rhs: f32) {
555 self.l /= rhs;
556 self.u /= rhs;
557 self.v /= rhs;
558 }
559}
560
561impl DivAssign<f32> for LCh {
562 #[inline]
563 fn div_assign(&mut self, rhs: f32) {
564 self.l /= rhs;
565 self.c /= rhs;
566 self.h /= rhs;
567 }
568}
569
570impl Neg for LCh {
571 type Output = LCh;
572
573 #[inline]
574 fn neg(self) -> Self::Output {
575 LCh::new(-self.l, -self.c, -self.h)
576 }
577}
578
579impl Neg for Luv {
580 type Output = Luv;
581
582 #[inline]
583 fn neg(self) -> Self::Output {
584 Luv::new(-self.l, -self.u, -self.v)
585 }
586}
587
588impl Pow<f32> for Luv {
589 type Output = Luv;
590
591 #[inline]
592 fn pow(self, rhs: f32) -> Self::Output {
593 Luv::new(
594 f_powf(self.l, rhs),
595 f_powf(self.u, rhs),
596 f_powf(self.v, rhs),
597 )
598 }
599}
600
601impl Pow<f32> for LCh {
602 type Output = LCh;
603
604 #[inline]
605 fn pow(self, rhs: f32) -> Self::Output {
606 LCh::new(
607 f_powf(self.l, rhs),
608 f_powf(self.c, rhs),
609 f_powf(self.h, rhs),
610 )
611 }
612}
613
614impl Pow<Luv> for Luv {
615 type Output = Luv;
616
617 #[inline]
618 fn pow(self, rhs: Luv) -> Self::Output {
619 Luv::new(
620 f_powf(self.l, rhs.l),
621 f_powf(self.u, rhs.u),
622 f_powf(self.v, rhs.v),
623 )
624 }
625}
626
627impl Pow<LCh> for LCh {
628 type Output = LCh;
629
630 #[inline]
631 fn pow(self, rhs: LCh) -> Self::Output {
632 LCh::new(
633 f_powf(self.l, rhs.l),
634 f_powf(self.c, rhs.c),
635 f_powf(self.h, rhs.h),
636 )
637 }
638}
639
640impl Luv {
641 #[inline]
642 pub fn sqrt(&self) -> Luv {
643 Luv::new(self.l.sqrt(), self.u.sqrt(), self.v.sqrt())
644 }
645
646 #[inline]
647 pub fn cbrt(&self) -> Luv {
648 Luv::new(f_cbrtf(self.l), f_cbrtf(self.u), f_cbrtf(self.v))
649 }
650}
651
652impl LCh {
653 #[inline]
654 pub fn sqrt(&self) -> LCh {
655 LCh::new(
656 if self.l < 0. { 0. } else { self.l.sqrt() },
657 if self.c < 0. { 0. } else { self.c.sqrt() },
658 if self.h < 0. { 0. } else { self.h.sqrt() },
659 )
660 }
661
662 #[inline]
663 pub fn cbrt(&self) -> LCh {
664 LCh::new(f_cbrtf(self.l), f_cbrtf(self.c), f_cbrtf(self.h))
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn round_trip_luv() {
674 let xyz = Xyz::new(0.1, 0.2, 0.3);
675 let lab = Luv::from_xyz(xyz);
676 let rolled_back = lab.to_xyz();
677 let dx = (xyz.x - rolled_back.x).abs();
678 let dy = (xyz.y - rolled_back.y).abs();
679 let dz = (xyz.z - rolled_back.z).abs();
680 assert!(dx < 1e-5);
681 assert!(dy < 1e-5);
682 assert!(dz < 1e-5);
683 }
684
685 #[test]
686 fn round_trip_lch() {
687 let xyz = Xyz::new(0.1, 0.2, 0.3);
688 let luv = Luv::from_xyz(xyz);
689 let lab = LCh::from_luv(luv);
690 let rolled_back = lab.to_luv();
691 let dx = (luv.l - rolled_back.l).abs();
692 let dy = (luv.u - rolled_back.u).abs();
693 let dz = (luv.v - rolled_back.v).abs();
694 assert!(dx < 1e-4);
695 assert!(dy < 1e-4);
696 assert!(dz < 1e-4);
697 }
698}