1use crate::color::ColorComponents;
16use crate::values::normalize;
17
18type Transform = euclid::default::Transform3D<f32>;
19type Vector = euclid::default::Vector3D<f32>;
20
21#[inline]
23pub fn normalize_hue(hue: f32) -> f32 {
24 hue - 360. * (hue / 360.).floor()
25}
26
27#[inline]
30fn rgb_to_hue_min_max(red: f32, green: f32, blue: f32) -> (f32, f32, f32) {
31 let max = red.max(green).max(blue);
32 let min = red.min(green).min(blue);
33
34 let delta = max - min;
35
36 let hue = if delta != 0.0 {
37 60.0 * if max == red {
38 (green - blue) / delta + if green < blue { 6.0 } else { 0.0 }
39 } else if max == green {
40 (blue - red) / delta + 2.0
41 } else {
42 (red - green) / delta + 4.0
43 }
44 } else {
45 f32::NAN
46 };
47
48 (hue, min, max)
49}
50
51#[inline]
54pub fn hsl_to_rgb(from: &ColorComponents) -> ColorComponents {
55 fn hue_to_rgb(t1: f32, t2: f32, hue: f32) -> f32 {
56 let hue = normalize_hue(hue);
57
58 if hue * 6.0 < 360.0 {
59 t1 + (t2 - t1) * hue / 60.0
60 } else if hue * 2.0 < 360.0 {
61 t2
62 } else if hue * 3.0 < 720.0 {
63 t1 + (t2 - t1) * (240.0 - hue) / 60.0
64 } else {
65 t1
66 }
67 }
68
69 let ColorComponents(hue, saturation, lightness) = from.map(normalize);
71 let saturation = saturation / 100.0;
72 let lightness = lightness / 100.0;
73
74 let t2 = if lightness <= 0.5 {
75 lightness * (saturation + 1.0)
76 } else {
77 lightness + saturation - lightness * saturation
78 };
79 let t1 = lightness * 2.0 - t2;
80
81 ColorComponents(
82 hue_to_rgb(t1, t2, hue + 120.0),
83 hue_to_rgb(t1, t2, hue),
84 hue_to_rgb(t1, t2, hue - 120.0),
85 )
86}
87
88pub fn rgb_to_hsl(from: &ColorComponents) -> ColorComponents {
91 let ColorComponents(red, green, blue) = *from;
92
93 let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);
94
95 let lightness = (min + max) / 2.0;
96 let delta = max - min;
97
98 let saturation = if delta != 0.0 {
99 if lightness == 0.0 || lightness == 1.0 {
100 0.0
101 } else {
102 (max - lightness) / lightness.min(1.0 - lightness)
103 }
104 } else {
105 0.0
106 };
107
108 ColorComponents(hue, saturation * 100.0, lightness * 100.0)
109}
110
111#[inline]
114pub fn hwb_to_rgb(from: &ColorComponents) -> ColorComponents {
115 let ColorComponents(hue, whiteness, blackness) = from.map(normalize);
117
118 let whiteness = whiteness / 100.0;
119 let blackness = blackness / 100.0;
120
121 if whiteness + blackness >= 1.0 {
122 let gray = whiteness / (whiteness + blackness);
123 return ColorComponents(gray, gray, gray);
124 }
125
126 let x = 1.0 - whiteness - blackness;
127 hsl_to_rgb(&ColorComponents(hue, 100.0, 50.0)).map(|v| v * x + whiteness)
128}
129
130#[inline]
133pub fn rgb_to_hwb(from: &ColorComponents) -> ColorComponents {
134 let ColorComponents(red, green, blue) = *from;
135
136 let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);
137
138 let whiteness = min;
139 let blackness = 1.0 - max;
140
141 ColorComponents(hue, whiteness * 100.0, blackness * 100.0)
142}
143
144#[inline]
146pub fn epsilon_for_range(min: f32, max: f32) -> f32 {
147 (max - min) / 1.0e5
148}
149
150#[inline]
154pub fn orthogonal_to_polar(from: &ColorComponents, e: f32) -> ColorComponents {
155 let ColorComponents(lightness, a, b) = *from;
156
157 let chroma = (a * a + b * b).sqrt();
158
159 let hue = if a.abs() < e && b.abs() < e {
160 f32::NAN
165 } else if chroma.abs() < e {
166 f32::NAN
168 } else {
169 normalize_hue(b.atan2(a).to_degrees())
170 };
171
172 ColorComponents(lightness, chroma, hue)
173}
174
175#[inline]
179pub fn polar_to_orthogonal(from: &ColorComponents) -> ColorComponents {
180 let ColorComponents(lightness, chroma, hue) = *from;
181
182 if hue.is_nan() {
184 return ColorComponents(lightness, 0.0, 0.0);
185 }
186
187 let hue = hue.to_radians();
188 let a = chroma * hue.cos();
189 let b = chroma * hue.sin();
190
191 ColorComponents(lightness, a, b)
192}
193
194#[inline]
195fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents {
196 let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2));
197 ColorComponents(result.x, result.y, result.z)
198}
199
200fn xyz_d65_to_xyz_d50(from: &ColorComponents) -> ColorComponents {
201 #[rustfmt::skip]
202 #[allow(clippy::excessive_precision)]
203 const MAT: Transform = Transform::new(
204 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.0,
205 0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.0,
206 -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.0,
207 0.0, 0.0, 0.0, 1.0,
208 );
209
210 transform(from, &MAT)
211}
212
213fn xyz_d50_to_xyz_d65(from: &ColorComponents) -> ColorComponents {
214 #[rustfmt::skip]
215 #[allow(clippy::excessive_precision)]
216 const MAT: Transform = Transform::new(
217 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.0,
218 -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.0,
219 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.0,
220 0.0, 0.0, 0.0, 1.0,
221 );
222
223 transform(from, &MAT)
224}
225
226pub enum WhitePoint {
228 D50,
230 D65,
232}
233
234impl WhitePoint {
235 #[allow(clippy::excessive_precision)]
236 const fn values(&self) -> ColorComponents {
237 match self {
239 WhitePoint::D50 => ColorComponents(0.9642956764295677, 1.0, 0.8251046025104602),
241 WhitePoint::D65 => ColorComponents(0.9504559270516716, 1.0, 1.0890577507598784),
243 }
244 }
245}
246
247fn convert_white_point(from: WhitePoint, to: WhitePoint, components: &mut ColorComponents) {
248 match (from, to) {
249 (WhitePoint::D50, WhitePoint::D65) => *components = xyz_d50_to_xyz_d65(components),
250 (WhitePoint::D65, WhitePoint::D50) => *components = xyz_d65_to_xyz_d50(components),
251 _ => {},
252 }
253}
254
255pub trait ColorSpaceConversion {
267 const WHITE_POINT: WhitePoint;
269
270 fn to_linear_light(from: &ColorComponents) -> ColorComponents;
273
274 fn to_xyz(from: &ColorComponents) -> ColorComponents;
277
278 fn from_xyz(from: &ColorComponents) -> ColorComponents;
281
282 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents;
285}
286
287pub fn to_xyz<From: ColorSpaceConversion>(from: &ColorComponents) -> (ColorComponents, WhitePoint) {
290 let result = From::to_linear_light(from);
293
294 (From::to_xyz(&result), From::WHITE_POINT)
296}
297
298pub fn from_xyz<To: ColorSpaceConversion>(
301 from: &ColorComponents,
302 white_point: WhitePoint,
303) -> ColorComponents {
304 let mut xyz = *from;
305
306 convert_white_point(white_point, To::WHITE_POINT, &mut xyz);
308
309 let result = To::from_xyz(&xyz);
311
312 To::to_gamma_encoded(&result)
315}
316
317pub struct Srgb;
320
321impl Srgb {
322 #[rustfmt::skip]
323 #[allow(clippy::excessive_precision)]
324 const TO_XYZ: Transform = Transform::new(
325 0.4123907992659595, 0.21263900587151036, 0.01933081871559185, 0.0,
326 0.35758433938387796, 0.7151686787677559, 0.11919477979462599, 0.0,
327 0.1804807884018343, 0.07219231536073371, 0.9505321522496606, 0.0,
328 0.0, 0.0, 0.0, 1.0,
329 );
330
331 #[rustfmt::skip]
332 #[allow(clippy::excessive_precision)]
333 const FROM_XYZ: Transform = Transform::new(
334 3.2409699419045213, -0.9692436362808798, 0.05563007969699361, 0.0,
335 -1.5373831775700935, 1.8759675015077206, -0.20397695888897657, 0.0,
336 -0.4986107602930033, 0.04155505740717561, 1.0569715142428786, 0.0,
337 0.0, 0.0, 0.0, 1.0,
338 );
339}
340
341impl ColorSpaceConversion for Srgb {
342 const WHITE_POINT: WhitePoint = WhitePoint::D65;
343
344 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
345 (*from).map(|value| {
346 let abs = value.abs();
347
348 if abs < 0.04045 {
349 value / 12.92
350 } else {
351 value.signum() * ((abs + 0.055) / 1.055).powf(2.4)
352 }
353 })
354 }
355
356 fn to_xyz(from: &ColorComponents) -> ColorComponents {
357 transform(from, &Self::TO_XYZ)
358 }
359
360 fn from_xyz(from: &ColorComponents) -> ColorComponents {
361 transform(from, &Self::FROM_XYZ)
362 }
363
364 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
365 (*from).map(|value| {
366 let abs = value.abs();
367
368 if abs > 0.0031308 {
369 value.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055)
370 } else {
371 12.92 * value
372 }
373 })
374 }
375}
376
377pub struct Hsl;
379
380impl ColorSpaceConversion for Hsl {
381 const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;
382
383 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
384 Srgb::to_linear_light(&hsl_to_rgb(from))
385 }
386
387 #[inline]
388 fn to_xyz(from: &ColorComponents) -> ColorComponents {
389 Srgb::to_xyz(from)
390 }
391
392 #[inline]
393 fn from_xyz(from: &ColorComponents) -> ColorComponents {
394 Srgb::from_xyz(from)
395 }
396
397 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
398 rgb_to_hsl(&Srgb::to_gamma_encoded(from))
399 }
400}
401
402pub struct Hwb;
404
405impl ColorSpaceConversion for Hwb {
406 const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;
407
408 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
409 Srgb::to_linear_light(&hwb_to_rgb(from))
410 }
411
412 #[inline]
413 fn to_xyz(from: &ColorComponents) -> ColorComponents {
414 Srgb::to_xyz(from)
415 }
416
417 #[inline]
418 fn from_xyz(from: &ColorComponents) -> ColorComponents {
419 Srgb::from_xyz(from)
420 }
421
422 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
423 rgb_to_hwb(&Srgb::to_gamma_encoded(from))
424 }
425}
426
427pub struct SrgbLinear;
430
431impl ColorSpaceConversion for SrgbLinear {
432 const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;
433
434 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
435 *from
437 }
438
439 fn to_xyz(from: &ColorComponents) -> ColorComponents {
440 Srgb::to_xyz(from)
441 }
442
443 fn from_xyz(from: &ColorComponents) -> ColorComponents {
444 Srgb::from_xyz(from)
445 }
446
447 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
448 *from
450 }
451}
452
453pub struct DisplayP3;
456
457impl DisplayP3 {
458 #[rustfmt::skip]
459 #[allow(clippy::excessive_precision)]
460 const TO_XYZ: Transform = Transform::new(
461 0.48657094864821626, 0.22897456406974884, 0.0, 0.0,
462 0.26566769316909294, 0.6917385218365062, 0.045113381858902575, 0.0,
463 0.1982172852343625, 0.079286914093745, 1.0439443689009757, 0.0,
464 0.0, 0.0, 0.0, 1.0,
465 );
466
467 #[rustfmt::skip]
468 #[allow(clippy::excessive_precision)]
469 const FROM_XYZ: Transform = Transform::new(
470 2.4934969119414245, -0.829488969561575, 0.035845830243784335, 0.0,
471 -0.9313836179191236, 1.7626640603183468, -0.07617238926804171, 0.0,
472 -0.40271078445071684, 0.02362468584194359, 0.9568845240076873, 0.0,
473 0.0, 0.0, 0.0, 1.0,
474 );
475}
476
477impl ColorSpaceConversion for DisplayP3 {
478 const WHITE_POINT: WhitePoint = WhitePoint::D65;
479
480 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
481 Srgb::to_linear_light(from)
482 }
483
484 fn to_xyz(from: &ColorComponents) -> ColorComponents {
485 transform(from, &Self::TO_XYZ)
486 }
487
488 fn from_xyz(from: &ColorComponents) -> ColorComponents {
489 transform(from, &Self::FROM_XYZ)
490 }
491
492 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
493 Srgb::to_gamma_encoded(from)
494 }
495}
496
497pub struct DisplayP3Linear;
499impl ColorSpaceConversion for DisplayP3Linear {
500 const WHITE_POINT: WhitePoint = DisplayP3::WHITE_POINT;
501
502 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
503 *from
504 }
505
506 fn to_xyz(from: &ColorComponents) -> ColorComponents {
507 DisplayP3::to_xyz(from)
508 }
509
510 fn from_xyz(from: &ColorComponents) -> ColorComponents {
511 DisplayP3::from_xyz(from)
512 }
513
514 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
515 *from
516 }
517}
518
519pub struct A98Rgb;
522
523impl A98Rgb {
524 #[rustfmt::skip]
525 #[allow(clippy::excessive_precision)]
526 const TO_XYZ: Transform = Transform::new(
527 0.5766690429101308, 0.29734497525053616, 0.027031361386412378, 0.0,
528 0.18555823790654627, 0.627363566255466, 0.07068885253582714, 0.0,
529 0.18822864623499472, 0.07529145849399789, 0.9913375368376389, 0.0,
530 0.0, 0.0, 0.0, 1.0,
531 );
532
533 #[rustfmt::skip]
534 #[allow(clippy::excessive_precision)]
535 const FROM_XYZ: Transform = Transform::new(
536 2.041587903810746, -0.9692436362808798, 0.013444280632031024, 0.0,
537 -0.5650069742788596, 1.8759675015077206, -0.11836239223101824, 0.0,
538 -0.3447313507783295, 0.04155505740717561, 1.0151749943912054, 0.0,
539 0.0, 0.0, 0.0, 1.0,
540 );
541}
542
543impl ColorSpaceConversion for A98Rgb {
544 const WHITE_POINT: WhitePoint = WhitePoint::D65;
545
546 #[allow(clippy::excessive_precision)]
547 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
548 (*from).map(|v| v.signum() * v.abs().powf(2.19921875))
549 }
550
551 fn to_xyz(from: &ColorComponents) -> ColorComponents {
552 transform(from, &Self::TO_XYZ)
553 }
554
555 fn from_xyz(from: &ColorComponents) -> ColorComponents {
556 transform(from, &Self::FROM_XYZ)
557 }
558
559 #[allow(clippy::excessive_precision)]
560 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
561 (*from).map(|v| v.signum() * v.abs().powf(0.4547069271758437))
562 }
563}
564
565pub struct ProphotoRgb;
568
569impl ProphotoRgb {
570 #[rustfmt::skip]
571 #[allow(clippy::excessive_precision)]
572 const TO_XYZ: Transform = Transform::new(
573 0.7977604896723027, 0.2880711282292934, 0.0, 0.0,
574 0.13518583717574031, 0.7118432178101014, 0.0, 0.0,
575 0.0313493495815248, 0.00008565396060525902, 0.8251046025104601, 0.0,
576 0.0, 0.0, 0.0, 1.0,
577 );
578
579 #[rustfmt::skip]
580 #[allow(clippy::excessive_precision)]
581 const FROM_XYZ: Transform = Transform::new(
582 1.3457989731028281, -0.5446224939028347, 0.0, 0.0,
583 -0.25558010007997534, 1.5082327413132781, 0.0, 0.0,
584 -0.05110628506753401, 0.02053603239147973, 1.2119675456389454, 0.0,
585 0.0, 0.0, 0.0, 1.0,
586 );
587}
588
589impl ColorSpaceConversion for ProphotoRgb {
590 const WHITE_POINT: WhitePoint = WhitePoint::D50;
591
592 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
593 (*from).map(|value| {
594 const ET2: f32 = 16.0 / 512.0;
595
596 let abs = value.abs();
597
598 if abs <= ET2 {
599 value / 16.0
600 } else {
601 value.signum() * abs.powf(1.8)
602 }
603 })
604 }
605
606 fn to_xyz(from: &ColorComponents) -> ColorComponents {
607 transform(from, &Self::TO_XYZ)
608 }
609
610 fn from_xyz(from: &ColorComponents) -> ColorComponents {
611 transform(from, &Self::FROM_XYZ)
612 }
613
614 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
615 const ET: f32 = 1.0 / 512.0;
616
617 (*from).map(|v| {
618 let abs = v.abs();
619 if abs >= ET {
620 v.signum() * abs.powf(1.0 / 1.8)
621 } else {
622 16.0 * v
623 }
624 })
625 }
626}
627
628pub struct Rec2020;
631
632impl Rec2020 {
633 #[allow(clippy::excessive_precision)]
634 const ALPHA: f32 = 1.09929682680944;
635 #[allow(clippy::excessive_precision)]
636 const BETA: f32 = 0.018053968510807;
637
638 #[rustfmt::skip]
639 #[allow(clippy::excessive_precision)]
640 const TO_XYZ: Transform = Transform::new(
641 0.6369580483012913, 0.26270021201126703, 0.0, 0.0,
642 0.14461690358620838, 0.677998071518871, 0.028072693049087508, 0.0,
643 0.16888097516417205, 0.059301716469861945, 1.0609850577107909, 0.0,
644 0.0, 0.0, 0.0, 1.0,
645 );
646
647 #[rustfmt::skip]
648 #[allow(clippy::excessive_precision)]
649 const FROM_XYZ: Transform = Transform::new(
650 1.7166511879712676, -0.666684351832489, 0.017639857445310915, 0.0,
651 -0.3556707837763924, 1.616481236634939, -0.042770613257808655, 0.0,
652 -0.2533662813736598, 0.01576854581391113, 0.942103121235474, 0.0,
653 0.0, 0.0, 0.0, 1.0,
654 );
655}
656
657impl ColorSpaceConversion for Rec2020 {
658 const WHITE_POINT: WhitePoint = WhitePoint::D65;
659
660 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
661 (*from).map(|value| {
662 let abs = value.abs();
663
664 if abs < Self::BETA * 4.5 {
665 value / 4.5
666 } else {
667 value.signum() * ((abs + Self::ALPHA - 1.0) / Self::ALPHA).powf(1.0 / 0.45)
668 }
669 })
670 }
671
672 fn to_xyz(from: &ColorComponents) -> ColorComponents {
673 transform(from, &Self::TO_XYZ)
674 }
675
676 fn from_xyz(from: &ColorComponents) -> ColorComponents {
677 transform(from, &Self::FROM_XYZ)
678 }
679
680 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
681 (*from).map(|v| {
682 let abs = v.abs();
683
684 if abs > Self::BETA {
685 v.signum() * (Self::ALPHA * abs.powf(0.45) - (Self::ALPHA - 1.0))
686 } else {
687 4.5 * v
688 }
689 })
690 }
691}
692
693pub struct XyzD50;
696
697impl ColorSpaceConversion for XyzD50 {
698 const WHITE_POINT: WhitePoint = WhitePoint::D50;
699
700 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
701 *from
702 }
703
704 fn to_xyz(from: &ColorComponents) -> ColorComponents {
705 *from
706 }
707
708 fn from_xyz(from: &ColorComponents) -> ColorComponents {
709 *from
710 }
711
712 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
713 *from
714 }
715}
716
717pub struct XyzD65;
720
721impl ColorSpaceConversion for XyzD65 {
722 const WHITE_POINT: WhitePoint = WhitePoint::D65;
723
724 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
725 *from
726 }
727
728 fn to_xyz(from: &ColorComponents) -> ColorComponents {
729 *from
730 }
731
732 fn from_xyz(from: &ColorComponents) -> ColorComponents {
733 *from
734 }
735
736 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
737 *from
738 }
739}
740
741pub struct Lab;
744
745impl Lab {
746 const KAPPA: f32 = 24389.0 / 27.0;
747 const EPSILON: f32 = 216.0 / 24389.0;
748}
749
750impl ColorSpaceConversion for Lab {
751 const WHITE_POINT: WhitePoint = WhitePoint::D50;
752
753 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
754 *from
756 }
757
758 fn to_xyz(from: &ColorComponents) -> ColorComponents {
763 let ColorComponents(lightness, a, b) = *from;
764
765 let f1 = (lightness + 16.0) / 116.0;
766 let f0 = f1 + a / 500.0;
767 let f2 = f1 - b / 200.0;
768
769 let f0_cubed = f0 * f0 * f0;
770 let x = if f0_cubed > Self::EPSILON {
771 f0_cubed
772 } else {
773 (116.0 * f0 - 16.0) / Self::KAPPA
774 };
775
776 let y = if lightness > Self::KAPPA * Self::EPSILON {
777 let v = (lightness + 16.0) / 116.0;
778 v * v * v
779 } else {
780 lightness / Self::KAPPA
781 };
782
783 let f2_cubed = f2 * f2 * f2;
784 let z = if f2_cubed > Self::EPSILON {
785 f2_cubed
786 } else {
787 (116.0 * f2 - 16.0) / Self::KAPPA
788 };
789
790 ColorComponents(x, y, z) * Self::WHITE_POINT.values()
791 }
792
793 fn from_xyz(from: &ColorComponents) -> ColorComponents {
798 let adapted = *from / Self::WHITE_POINT.values();
799
800 let ColorComponents(f0, f1, f2) = adapted.map(|v| {
802 if v > Self::EPSILON {
803 v.cbrt()
804 } else {
805 (Self::KAPPA * v + 16.0) / 116.0
806 }
807 });
808
809 let lightness = 116.0 * f1 - 16.0;
810 let a = 500.0 * (f0 - f1);
811 let b = 200.0 * (f1 - f2);
812
813 ColorComponents(lightness, a, b)
814 }
815
816 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
817 *from
819 }
820}
821
822pub struct Lch;
825
826impl ColorSpaceConversion for Lch {
827 const WHITE_POINT: WhitePoint = Lab::WHITE_POINT;
828
829 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
830 *from
832 }
833
834 fn to_xyz(from: &ColorComponents) -> ColorComponents {
835 let lab = polar_to_orthogonal(from);
837
838 Lab::to_xyz(&lab)
840 }
841
842 fn from_xyz(from: &ColorComponents) -> ColorComponents {
843 let lab = Lab::from_xyz(from);
845
846 orthogonal_to_polar(&lab, epsilon_for_range(0.0, 100.0))
848 }
849
850 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
851 *from
853 }
854}
855
856pub struct Oklab;
859
860impl Oklab {
861 #[rustfmt::skip]
862 #[allow(clippy::excessive_precision)]
863 const XYZ_TO_LMS: Transform = Transform::new(
864 0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0.0,
865 0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0.0,
866 -0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0.0,
867 0.0, 0.0, 0.0, 1.0,
868 );
869
870 #[rustfmt::skip]
871 #[allow(clippy::excessive_precision)]
872 const LMS_TO_OKLAB: Transform = Transform::new(
873 0.2104542553, 1.9779984951, 0.0259040371, 0.0,
874 0.7936177850, -2.4285922050, 0.7827717662, 0.0,
875 -0.0040720468, 0.4505937099, -0.8086757660, 0.0,
876 0.0, 0.0, 0.0, 1.0,
877 );
878
879 #[rustfmt::skip]
880 #[allow(clippy::excessive_precision)]
881 const LMS_TO_XYZ: Transform = Transform::new(
882 1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0.0,
883 -0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0.0,
884 0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0.0,
885 0.0, 0.0, 0.0, 1.0,
886 );
887
888 #[rustfmt::skip]
889 #[allow(clippy::excessive_precision)]
890 const OKLAB_TO_LMS: Transform = Transform::new(
891 0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0.0,
892 0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0.0,
893 0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0.0,
894 0.0, 0.0, 0.0, 1.0,
895 );
896}
897
898impl ColorSpaceConversion for Oklab {
899 const WHITE_POINT: WhitePoint = WhitePoint::D65;
900
901 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
902 *from
904 }
905
906 fn to_xyz(from: &ColorComponents) -> ColorComponents {
907 let lms = transform(from, &Self::OKLAB_TO_LMS);
908 let lms = lms.map(|v| v * v * v);
909 transform(&lms, &Self::LMS_TO_XYZ)
910 }
911
912 fn from_xyz(from: &ColorComponents) -> ColorComponents {
913 let lms = transform(from, &Self::XYZ_TO_LMS);
914 let lms = lms.map(|v| v.cbrt());
915 transform(&lms, &Self::LMS_TO_OKLAB)
916 }
917
918 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
919 *from
921 }
922}
923
924pub struct Oklch;
927
928impl ColorSpaceConversion for Oklch {
929 const WHITE_POINT: WhitePoint = Oklab::WHITE_POINT;
930
931 fn to_linear_light(from: &ColorComponents) -> ColorComponents {
932 *from
934 }
935
936 fn to_xyz(from: &ColorComponents) -> ColorComponents {
937 let oklab = polar_to_orthogonal(from);
939
940 Oklab::to_xyz(&oklab)
942 }
943
944 fn from_xyz(from: &ColorComponents) -> ColorComponents {
945 let lab = Oklab::from_xyz(from);
947
948 orthogonal_to_polar(&lab, epsilon_for_range(0.0, 1.0))
950 }
951
952 fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
953 *from
955 }
956}