1use std::fmt::{self, Debug};
4
5use super::lerp::lerp_double_non_null;
6use super::math::clamp_double;
7
8fn scale_alpha(x: Color, factor: f64) -> Color {
9 x.with_values(
10 Some(clamp_double(x.a * factor, 0.0, 1.0)),
11 None,
12 None,
13 None,
14 None,
15 )
16}
17
18fn wider_color_space(a: ColorSpace, b: ColorSpace) -> ColorSpace {
19 if a == ColorSpace::DisplayP3 || b == ColorSpace::DisplayP3 {
20 ColorSpace::DisplayP3
21 } else {
22 a
23 }
24}
25
26#[derive(Clone, Copy, PartialEq)]
74pub struct Color {
75 pub a: f64,
77 pub r: f64,
79 pub g: f64,
81 pub b: f64,
83 pub color_space: ColorSpace,
85}
86
87impl Color {
88 pub const fn new(value: u32) -> Color {
110 Self::from_argbc(
111 (value >> 24) as i32,
112 (value >> 16) as i32,
113 (value >> 8) as i32,
114 value as i32,
115 ColorSpace::Srgb,
116 )
117 }
118
119 pub const fn from(
139 alpha: f64,
140 red: f64,
141 green: f64,
142 blue: f64,
143 color_space: ColorSpace,
144 ) -> Color {
145 Color {
146 a: alpha,
147 r: red,
148 g: green,
149 b: blue,
150 color_space,
151 }
152 }
153
154 pub const fn from_argb(a: i32, r: i32, g: i32, b: i32) -> Color {
172 Self::from_argbc(a, r, g, b, ColorSpace::Srgb)
173 }
174
175 const fn from_argbc(
176 alpha: i32,
177 red: i32,
178 green: i32,
179 blue: i32,
180 color_space: ColorSpace,
181 ) -> Color {
182 Self::from_rgboc(
183 red,
184 green,
185 blue,
186 ((alpha & 0xff) as f64) / 255.0,
187 color_space,
188 )
189 }
190
191 pub const fn from_rgbo(r: i32, g: i32, b: i32, opacity: f64) -> Color {
210 Self::from_rgboc(r, g, b, opacity, ColorSpace::Srgb)
211 }
212
213 const fn from_rgboc(r: i32, g: i32, b: i32, opacity: f64, color_space: ColorSpace) -> Color {
214 Color {
215 a: opacity,
216 r: ((r & 0xff) as f64) / 255.0,
217 g: ((g & 0xff) as f64) / 255.0,
218 b: ((b & 0xff) as f64) / 255.0,
219 color_space,
220 }
221 }
222
223 fn float_to_int8(x: f64) -> i32 {
224 (x * 255.0).round().clamp(0.0, 255.0) as i32
225 }
226
227 #[deprecated(
232 note = "Use component accessors like .r or .g, or to_argb32 for an explicit conversion"
233 )]
234 pub fn value(&self) -> u32 {
235 self.to_argb32()
236 }
237
238 pub fn to_argb32(&self) -> u32 {
267 #[allow(clippy::identity_op)]
268 {
269 ((Self::float_to_int8(self.a) as u32) << 24)
270 | ((Self::float_to_int8(self.r) as u32) << 16)
271 | ((Self::float_to_int8(self.g) as u32) << 8)
272 | ((Self::float_to_int8(self.b) as u32) << 0)
273 }
274 }
275
276 #[deprecated(note = "Use (*.a * 255.0).round().clamp(0, 255)")]
281 #[allow(deprecated)]
282 pub fn alpha(&self) -> i32 {
283 ((0xff000000 & self.value()) >> 24) as i32
284 }
285
286 #[deprecated(note = "Use .a.")]
291 #[allow(deprecated)]
292 pub fn opacity(&self) -> f64 {
293 (self.alpha() as f64) / (0xFF as f64)
294 }
295
296 #[deprecated(note = "Use (*.r * 255.0).round().clamp(0, 255)")]
298 #[allow(deprecated)]
299 pub fn red(&self) -> i32 {
300 ((0x00ff0000 & self.value()) >> 16) as i32
301 }
302
303 #[deprecated(note = "Use (*.g * 255.0).round().clamp(0, 255)")]
305 #[allow(deprecated)]
306 pub fn green(&self) -> i32 {
307 ((0x0000ff00 & self.value()) >> 8) as i32
308 }
309
310 #[deprecated(note = "Use (*.b * 255.0).round().clamp(0, 255)")]
312 #[allow(deprecated)]
313 pub fn blue(&self) -> i32 {
314 #[allow(clippy::identity_op)]
315 {
316 ((0x000000ff & self.value()) >> 0) as i32
317 }
318 }
319
320 pub fn with_values(
338 &self,
339 alpha: Option<f64>,
340 red: Option<f64>,
341 green: Option<f64>,
342 blue: Option<f64>,
343 color_space: Option<ColorSpace>,
344 ) -> Color {
345 let mut updated_components = None;
346 if alpha.is_some() || red.is_some() || green.is_some() || blue.is_some() {
347 updated_components = Some(Color::from(
348 alpha.unwrap_or(self.a),
349 red.unwrap_or(self.r),
350 green.unwrap_or(self.g),
351 blue.unwrap_or(self.b),
352 self.color_space,
353 ));
354 }
355 if let Some(color_space) = color_space
356 && color_space != self.color_space
357 {
358 let transform = get_color_transform(self.color_space, color_space);
359 transform.transform(updated_components.unwrap_or(*self), color_space)
360 } else {
361 updated_components.unwrap_or(*self)
362 }
363 }
364
365 #[allow(deprecated)]
370 pub fn with_alpha(&self, a: i32) -> Color {
371 Color::from_argb(a, self.red(), self.green(), self.blue())
372 }
373
374 #[deprecated(note = "Use .with_values() to avoid precision loss.")]
379 pub fn with_opacity(&self, opacity: f64) -> Color {
380 debug_assert!((0.0..=1.0).contains(&opacity));
381 self.with_alpha((255.0 * opacity).round() as i32)
382 }
383
384 #[allow(deprecated)]
389 pub fn with_red(&self, r: i32) -> Color {
390 Color::from_argb(self.alpha(), r, self.green(), self.blue())
391 }
392
393 #[allow(deprecated)]
398 pub fn with_green(&self, g: i32) -> Color {
399 Color::from_argb(self.alpha(), self.red(), g, self.blue())
400 }
401
402 #[allow(deprecated)]
407 pub fn with_blue(&self, b: i32) -> Color {
408 Color::from_argb(self.alpha(), self.red(), self.green(), b)
409 }
410
411 fn linearize_color_component(component: f64) -> f64 {
413 if component <= 0.03928 {
414 return component / 12.92;
415 }
416 ((component + 0.055) / 1.055).powf(2.4)
417 }
418
419 pub fn compute_luminance(&self) -> f64 {
426 debug_assert!(self.color_space != ColorSpace::ExtendedSrgb);
427 let r = Self::linearize_color_component(self.r);
429 let g = Self::linearize_color_component(self.g);
430 let b = Self::linearize_color_component(self.b);
431 0.2126 * r + 0.7152 * g + 0.0722 * b
432 }
433
434 pub fn lerp(x: Option<Color>, y: Option<Color>, t: f64) -> Option<Color> {
462 debug_assert!(x.is_none_or(|c| c.color_space != ColorSpace::ExtendedSrgb));
463 debug_assert!(y.is_none_or(|c| c.color_space != ColorSpace::ExtendedSrgb));
464 match (x, y) {
465 (None, None) => None,
466 (Some(x), None) => Some(scale_alpha(x, 1.0 - t)),
467 (None, Some(y)) => Some(scale_alpha(y, t)),
468 (Some(x), Some(y)) => {
469 let a;
470 let b;
471 let result_color_space;
472 if x.color_space == y.color_space {
473 a = x;
474 b = y;
475 result_color_space = x.color_space;
476 } else {
477 result_color_space = wider_color_space(x.color_space, y.color_space);
478 a = x.with_values(None, None, None, None, Some(result_color_space));
479 b = y.with_values(None, None, None, None, Some(result_color_space));
480 }
481 Some(Color::from(
482 clamp_double(lerp_double_non_null(a.a, b.a, t), 0.0, 1.0),
483 clamp_double(lerp_double_non_null(a.r, b.r, t), 0.0, 1.0),
484 clamp_double(lerp_double_non_null(a.g, b.g, t), 0.0, 1.0),
485 clamp_double(lerp_double_non_null(a.b, b.b, t), 0.0, 1.0),
486 result_color_space,
487 ))
488 }
489 }
490 }
491
492 pub fn alpha_blend(foreground: Color, background: Color) -> Color {
501 debug_assert!(foreground.color_space == background.color_space);
502 debug_assert!(foreground.color_space != ColorSpace::ExtendedSrgb);
503 let alpha = foreground.a;
504 if alpha == 0.0 {
505 return background;
507 }
508 let inv_alpha = 1.0 - alpha;
509 let mut back_alpha = background.a;
510 if back_alpha == 1.0 {
511 Color::from(
513 1.0,
514 alpha * foreground.r + inv_alpha * background.r,
515 alpha * foreground.g + inv_alpha * background.g,
516 alpha * foreground.b + inv_alpha * background.b,
517 foreground.color_space,
518 )
519 } else {
520 back_alpha *= inv_alpha;
522 let out_alpha = alpha + back_alpha;
523 debug_assert!(out_alpha != 0.0);
524 Color::from(
525 out_alpha,
526 (foreground.r * alpha + background.r * back_alpha) / out_alpha,
527 (foreground.g * alpha + background.g * back_alpha) / out_alpha,
528 (foreground.b * alpha + background.b * back_alpha) / out_alpha,
529 foreground.color_space,
530 )
531 }
532 }
533
534 pub fn get_alpha_from_opacity(opacity: f64) -> i32 {
538 (clamp_double(opacity, 0.0, 1.0) * 255.0).round() as i32
539 }
540}
541
542impl Debug for Color {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 write!(
545 f,
546 "Color(alpha: {:.4}, red: {:.4}, green: {:.4}, blue: {:.4}, colorSpace: {:?})",
547 self.a, self.r, self.g, self.b, self.color_space
548 )
549 }
550}
551
552#[derive(Clone, Copy, PartialEq, Eq)]
563pub enum ColorSpace {
564 Srgb,
571
572 ExtendedSrgb,
577
578 DisplayP3,
586}
587
588impl Debug for ColorSpace {
589 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590 match self {
591 ColorSpace::Srgb => write!(f, "ColorSpace.sRGB"),
592 ColorSpace::ExtendedSrgb => write!(f, "ColorSpace.extendedSRGB"),
593 ColorSpace::DisplayP3 => write!(f, "ColorSpace.displayP3"),
594 }
595 }
596}
597
598enum ColorTransform {
599 Identity,
600 Clamp,
601 P3ToSrgb,
602 SrgbToP3,
603}
604
605impl ColorTransform {
606 fn transform(self, color: Color, result_color_space: ColorSpace) -> Color {
607 match self {
608 ColorTransform::Identity => color,
609 ColorTransform::Clamp => Color::from(
610 clamp_double(color.a, 0.0, 1.0),
611 clamp_double(color.r, 0.0, 1.0),
612 clamp_double(color.g, 0.0, 1.0),
613 clamp_double(color.b, 0.0, 1.0),
614 result_color_space,
615 ),
616 ColorTransform::P3ToSrgb => {
617 let r_lin = srgb_eotf_extended(color.r);
618 let g_lin = srgb_eotf_extended(color.g);
619 let b_lin = srgb_eotf_extended(color.b);
620
621 let r_out = K_P3_TO_SRGB_LINEAR[0] * r_lin
622 + K_P3_TO_SRGB_LINEAR[1] * g_lin
623 + K_P3_TO_SRGB_LINEAR[2] * b_lin;
624 let g_out = K_P3_TO_SRGB_LINEAR[3] * r_lin
625 + K_P3_TO_SRGB_LINEAR[4] * g_lin
626 + K_P3_TO_SRGB_LINEAR[5] * b_lin;
627 let b_out = K_P3_TO_SRGB_LINEAR[6] * r_lin
628 + K_P3_TO_SRGB_LINEAR[7] * g_lin
629 + K_P3_TO_SRGB_LINEAR[8] * b_lin;
630
631 Color::from(
632 color.a,
633 srgb_oetf_extended(r_out),
634 srgb_oetf_extended(g_out),
635 srgb_oetf_extended(b_out),
636 result_color_space,
637 )
638 }
639 ColorTransform::SrgbToP3 => {
640 let r_lin = srgb_eotf_extended(color.r);
641 let g_lin = srgb_eotf_extended(color.g);
642 let b_lin = srgb_eotf_extended(color.b);
643
644 let r_out = K_SRGB_TO_P3_LINEAR[0] * r_lin
645 + K_SRGB_TO_P3_LINEAR[1] * g_lin
646 + K_SRGB_TO_P3_LINEAR[2] * b_lin;
647 let g_out = K_SRGB_TO_P3_LINEAR[3] * r_lin
648 + K_SRGB_TO_P3_LINEAR[4] * g_lin
649 + K_SRGB_TO_P3_LINEAR[5] * b_lin;
650 let b_out = K_SRGB_TO_P3_LINEAR[6] * r_lin
651 + K_SRGB_TO_P3_LINEAR[7] * g_lin
652 + K_SRGB_TO_P3_LINEAR[8] * b_lin;
653
654 Color::from(
655 color.a,
656 srgb_oetf_extended(r_out),
657 srgb_oetf_extended(g_out),
658 srgb_oetf_extended(b_out),
659 result_color_space,
660 )
661 }
662 }
663 }
664}
665
666const K_SRGB_GAMMA: f64 = 2.4;
669const K_SRGB_LINEAR_THRESHOLD: f64 = 0.04045;
670const K_SRGB_LINEAR_SLOPE: f64 = 12.92;
671const K_SRGB_ENCODED_OFFSET: f64 = 0.055;
672const K_SRGB_ENCODED_DIVISOR: f64 = 1.055;
673const K_SRGB_LINEAR_TO_ENCODED_THRESHOLD: f64 = 0.0031308;
674
675fn srgb_eotf(v: f64) -> f64 {
677 if v <= K_SRGB_LINEAR_THRESHOLD {
678 v / K_SRGB_LINEAR_SLOPE
679 } else {
680 ((v + K_SRGB_ENCODED_OFFSET) / K_SRGB_ENCODED_DIVISOR).powf(K_SRGB_GAMMA)
681 }
682}
683
684fn srgb_oetf(v: f64) -> f64 {
686 if v <= K_SRGB_LINEAR_TO_ENCODED_THRESHOLD {
687 v * K_SRGB_LINEAR_SLOPE
688 } else {
689 K_SRGB_ENCODED_DIVISOR * v.powf(1.0 / K_SRGB_GAMMA) - K_SRGB_ENCODED_OFFSET
690 }
691}
692
693fn srgb_eotf_extended(v: f64) -> f64 {
695 if v < 0.0 {
696 -srgb_eotf(-v)
697 } else {
698 srgb_eotf(v)
699 }
700}
701
702fn srgb_oetf_extended(v: f64) -> f64 {
703 if v < 0.0 {
704 -srgb_oetf(-v)
705 } else {
706 srgb_oetf(v)
707 }
708}
709
710const K_P3_TO_SRGB_LINEAR: [f64; 9] = [
713 1.2249401, -0.2249402, 0.0, -0.0420569, 1.0420571, 0.0, -0.0196376, -0.0786507, 1.0982884,
714];
715
716const K_SRGB_TO_P3_LINEAR: [f64; 9] = [
718 0.8224622, 0.1775380, 0.0, 0.0331942, 0.9668058, 0.0, 0.0170806, 0.0723974, 0.9105220,
719];
720
721fn get_color_transform(source: ColorSpace, destination: ColorSpace) -> ColorTransform {
722 match source {
723 ColorSpace::Srgb => match destination {
724 ColorSpace::Srgb => ColorTransform::Identity,
725 ColorSpace::ExtendedSrgb => ColorTransform::Identity,
726 ColorSpace::DisplayP3 => ColorTransform::SrgbToP3,
727 },
728 ColorSpace::ExtendedSrgb => match destination {
729 ColorSpace::Srgb => ColorTransform::Clamp,
730 ColorSpace::ExtendedSrgb => ColorTransform::Identity,
731 ColorSpace::DisplayP3 => ColorTransform::Clamp,
732 },
733 ColorSpace::DisplayP3 => match destination {
734 ColorSpace::Srgb => ColorTransform::Clamp,
735 ColorSpace::ExtendedSrgb => ColorTransform::P3ToSrgb,
736 ColorSpace::DisplayP3 => ColorTransform::Identity,
737 },
738 }
739}
740
741#[cfg(test)]
742mod tests {
743 use super::*;
744
745 fn color_matches(actual: Color, expected: Color, threshold: f64) -> bool {
746 actual.color_space == expected.color_space
747 && (actual.a - expected.a).abs() <= threshold
748 && (actual.r - expected.r).abs() <= threshold
749 && (actual.g - expected.g).abs() <= threshold
750 && (actual.b - expected.b).abs() <= threshold
751 }
752
753 #[test]
755 fn packed_constructor_from_argb_and_from_agree() {
756 let packed = Color::new(0xFF42A5F5);
757 let from_argb = Color::from_argb(0xFF, 0x42, 0xA5, 0xF5);
758 let from = Color::from(
759 1.0,
760 0x42 as f64 / 255.0,
761 0xA5 as f64 / 255.0,
762 0xF5 as f64 / 255.0,
763 ColorSpace::Srgb,
764 );
765 let from_rgbo = Color::from_rgbo(0x42, 0xA5, 0xF5, 1.0);
766
767 assert_eq!(packed, from_argb);
768 assert_eq!(packed, from);
769 assert_eq!(packed, from_rgbo);
770 assert_eq!(packed.to_argb32(), 0xFF42A5F5);
771 assert_eq!(packed.a, 1.0);
772 assert_eq!(packed.color_space, ColorSpace::Srgb);
773 }
774
775 #[test]
776 #[allow(deprecated)]
777 fn color_accessors_should_work() {
778 let foo = Color::new(0x12345678);
779 assert_eq!(foo.alpha(), 0x12);
780 assert_eq!(foo.red(), 0x34);
781 assert_eq!(foo.green(), 0x56);
782 assert_eq!(foo.blue(), 0x78);
783 }
784
785 #[test]
786 fn two_colors_are_equal_when_components_and_space_match() {
787 assert_eq!(Color::new(0x12345678), Color::new(0x12345678));
788 assert_ne!(Color::new(0x12345678), Color::new(0x87654321));
789 let srgb = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::Srgb);
790 let p3 = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::DisplayP3);
791 assert_ne!(srgb, p3);
792 }
793
794 #[test]
795 fn lerp_walks_the_line_and_clamps_extrapolation() {
796 assert_eq!(
797 Color::lerp(
798 Some(Color::new(0x00000000)),
799 Some(Color::new(0xFFFFFFFF)),
800 0.0
801 ),
802 Some(Color::new(0x00000000))
803 );
804 assert!(color_matches(
805 Color::lerp(
806 Some(Color::new(0x00000000)),
807 Some(Color::new(0xFFFFFFFF)),
808 0.5
809 )
810 .unwrap(),
811 Color::new(0x7F7F7F7F),
812 1.0 / 255.0
813 ));
814 assert_eq!(
815 Color::lerp(
816 Some(Color::new(0x00000000)),
817 Some(Color::new(0xFFFFFFFF)),
818 1.0
819 ),
820 Some(Color::new(0xFFFFFFFF))
821 );
822 assert_eq!(
823 Color::lerp(
824 Some(Color::new(0x00000000)),
825 Some(Color::new(0xFFFFFFFF)),
826 -0.1
827 ),
828 Some(Color::new(0x00000000))
829 );
830 assert_eq!(
831 Color::lerp(
832 Some(Color::new(0x00000000)),
833 Some(Color::new(0xFFFFFFFF)),
834 1.1
835 ),
836 Some(Color::new(0xFFFFFFFF))
837 );
838
839 assert_eq!(
841 Color::lerp(
842 Some(Color::new(0xFFFFFFFF)),
843 Some(Color::new(0xFFFFFFFF)),
844 0.04
845 ),
846 Some(Color::new(0xFFFFFFFF))
847 );
848
849 assert_eq!(Color::lerp(None, None, 0.5), None);
850 assert_eq!(
851 Color::lerp(Some(Color::new(0xFFFFFFFF)), None, 0.0),
852 Some(Color::new(0xFFFFFFFF))
853 );
854 }
855
856 #[test]
857 fn lerp_same_color_spaces() {
858 assert_eq!(
859 Color::lerp(
860 Some(Color::from(1.0, 0.0, 0.0, 0.0, ColorSpace::DisplayP3)),
861 Some(Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::DisplayP3)),
862 0.2,
863 ),
864 Some(Color::from(1.0, 0.2, 0.0, 0.0, ColorSpace::DisplayP3))
865 );
866 }
867
868 #[test]
869 fn lerp_mixed_color_spaces_produces_display_p3() {
870 let result = Color::lerp(
871 Some(Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::DisplayP3)),
872 Some(Color::from(1.0, 0.0, 0.0, 1.0, ColorSpace::Srgb)),
873 0.0,
874 )
875 .unwrap();
876 assert_eq!(result.color_space, ColorSpace::DisplayP3);
877 }
878
879 #[test]
880 fn lerp_mixed_color_spaces_srgb_x_and_display_p3_y_at_t_0() {
881 let result = Color::lerp(
882 Some(Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::Srgb)),
883 Some(Color::from(1.0, 0.0, 1.0, 0.0, ColorSpace::DisplayP3)),
884 0.0,
885 )
886 .unwrap();
887 assert_eq!(result.color_space, ColorSpace::DisplayP3);
888 let expected_p3 = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::Srgb).with_values(
889 None,
890 None,
891 None,
892 None,
893 Some(ColorSpace::DisplayP3),
894 );
895 assert!(color_matches(result, expected_p3, 1.0 / 255.0));
896 }
897
898 #[test]
899 fn lerp_mixed_color_spaces_at_midpoint() {
900 let srgb_red = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::Srgb);
901 let p3_green = Color::from(1.0, 0.0, 1.0, 0.0, ColorSpace::DisplayP3);
902 let result = Color::lerp(Some(srgb_red), Some(p3_green), 0.5).unwrap();
903 assert_eq!(result.color_space, ColorSpace::DisplayP3);
904 let srgb_red_as_p3 =
905 srgb_red.with_values(None, None, None, None, Some(ColorSpace::DisplayP3));
906 let expected = Color::from(
907 (srgb_red_as_p3.a + p3_green.a) / 2.0,
908 (srgb_red_as_p3.r + p3_green.r) / 2.0,
909 (srgb_red_as_p3.g + p3_green.g) / 2.0,
910 (srgb_red_as_p3.b + p3_green.b) / 2.0,
911 ColorSpace::DisplayP3,
912 );
913 assert!(color_matches(result, expected, 1e-4));
914 }
915
916 #[test]
917 fn alpha_blend_matches_src_over_dst() {
918 assert_eq!(
919 Color::alpha_blend(Color::new(0x00000000), Color::new(0x00000000)),
920 Color::new(0x00000000)
921 );
922 assert_eq!(
923 Color::alpha_blend(Color::new(0x00000000), Color::new(0xFFFFFFFF)),
924 Color::new(0xFFFFFFFF)
925 );
926 assert_eq!(
927 Color::alpha_blend(Color::new(0xFFFFFFFF), Color::new(0x00000000)),
928 Color::new(0xFFFFFFFF)
929 );
930 assert_eq!(
931 Color::alpha_blend(Color::new(0xFFFFFFFF), Color::new(0xFFFFFFFF)),
932 Color::new(0xFFFFFFFF)
933 );
934 assert_eq!(
935 Color::alpha_blend(Color::new(0x80FFFFFF), Color::new(0xFF000000)),
936 Color::new(0xFF808080)
937 );
938 assert!(color_matches(
939 Color::alpha_blend(Color::new(0x80808080), Color::new(0xFFFFFFFF)),
940 Color::new(0xFFBFBFBF),
941 1.0 / 255.0
942 ));
943 assert!(color_matches(
944 Color::alpha_blend(Color::new(0x80808080), Color::new(0xFF000000)),
945 Color::new(0xFF404040),
946 1.0 / 255.0
947 ));
948 assert_eq!(
949 Color::alpha_blend(
950 Color::from(0.5, 1.0, 1.0, 1.0, ColorSpace::DisplayP3),
951 Color::from(1.0, 0.0, 0.0, 0.0, ColorSpace::DisplayP3),
952 ),
953 Color::from(1.0, 0.5, 0.5, 0.5, ColorSpace::DisplayP3)
954 );
955 }
956
957 #[test]
958 fn compute_gray_luminance() {
959 let light_gray = Color::new(0xFF333333);
961 assert_eq!(light_gray.compute_luminance(), 0.033104766570885055);
964 }
965
966 #[test]
967 fn compute_color_luminance() {
968 let bright_red = Color::new(0xFFFF3B30);
969 assert_eq!(bright_red.compute_luminance(), 0.24601329637099723);
973 }
974
975 #[test]
976 #[allow(deprecated)]
977 fn from_and_accessors() {
978 let color = Color::from(0.1, 0.2, 0.3, 0.4, ColorSpace::Srgb);
979 assert_eq!(color.a, 0.1);
980 assert_eq!(color.r, 0.2);
981 assert_eq!(color.g, 0.3);
982 assert_eq!(color.b, 0.4);
983 assert_eq!(color.color_space, ColorSpace::Srgb);
984
985 assert_eq!(color.alpha(), 26);
986 assert_eq!(color.red(), 51);
987 assert_eq!(color.green(), 77);
988 assert_eq!(color.blue(), 102);
989
990 assert_eq!(color.value(), 0x1a334d66);
991 assert_eq!(color.to_argb32(), 0x1a334d66);
992 }
993
994 #[test]
995 #[allow(deprecated)]
996 fn from_argb_and_accessors() {
997 let color = Color::from_argb(10, 20, 35, 47);
998 assert_eq!(color.alpha(), 10);
999 assert_eq!(color.red(), 20);
1000 assert_eq!(color.green(), 35);
1001 assert_eq!(color.blue(), 47);
1002 }
1003
1004 #[test]
1005 #[allow(deprecated)]
1006 fn constructor_and_accessors() {
1007 let color = Color::new(0xffeeddcc);
1008 assert_eq!(color.alpha(), 0xff);
1009 assert_eq!(color.red(), 0xee);
1010 assert_eq!(color.green(), 0xdd);
1011 assert_eq!(color.blue(), 0xcc);
1012 }
1013
1014 #[test]
1015 fn p3_to_extended_srgb() {
1016 let p3 = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::DisplayP3);
1017 let srgb = p3.with_values(None, None, None, None, Some(ColorSpace::ExtendedSrgb));
1018 assert_eq!(srgb.a, 1.0);
1019 assert!((srgb.r - 1.0931).abs() < 1e-4);
1020 assert!((srgb.g - -0.22684034705162098).abs() < 1e-4);
1021 assert!((srgb.b - -0.15007957816123998).abs() < 1e-4);
1022 assert_eq!(srgb.color_space, ColorSpace::ExtendedSrgb);
1023 }
1024
1025 #[test]
1026 fn p3_to_srgb() {
1027 let p3 = Color::from(1.0, 1.0, 0.0, 0.0, ColorSpace::DisplayP3);
1028 let srgb = p3.with_values(None, None, None, None, Some(ColorSpace::Srgb));
1029 assert_eq!(srgb.a, 1.0);
1030 assert!((srgb.r - 1.0).abs() < 1e-4);
1031 assert!((srgb.g - 0.0).abs() < 1e-4);
1032 assert!((srgb.b - 0.0).abs() < 1e-4);
1033 assert_eq!(srgb.color_space, ColorSpace::Srgb);
1034 }
1035
1036 #[test]
1037 fn extended_srgb_to_p3() {
1038 let srgb = Color::from(1.0, 1.0931, -0.2268, -0.1501, ColorSpace::ExtendedSrgb);
1039 let p3 = srgb.with_values(None, None, None, None, Some(ColorSpace::DisplayP3));
1040 assert_eq!(p3.a, 1.0);
1041 assert!((p3.r - 1.0).abs() < 1e-4);
1042 assert!((p3.g - 0.0).abs() < 1e-4);
1043 assert!((p3.b - 0.0).abs() < 1e-4);
1044 assert_eq!(p3.color_space, ColorSpace::DisplayP3);
1045 }
1046
1047 #[test]
1048 fn extended_srgb_to_p3_clamped() {
1049 let srgb = Color::from(1.0, 2.0, 0.0, 0.0, ColorSpace::ExtendedSrgb);
1050 let p3 = srgb.with_values(None, None, None, None, Some(ColorSpace::DisplayP3));
1051 assert_eq!(srgb.a, 1.0);
1052 assert!(p3.r <= 1.0);
1053 assert!(p3.g <= 1.0);
1054 assert!(p3.b <= 1.0);
1055 assert!(p3.r >= 0.0);
1056 assert!(p3.g >= 0.0);
1057 assert!(p3.b >= 0.0);
1058 }
1059
1060 #[test]
1061 fn debug_matches_darts_to_string() {
1062 let color = Color::new(0xFF42A5F5);
1063 assert_eq!(
1064 format!("{color:?}"),
1065 "Color(alpha: 1.0000, red: 0.2588, green: 0.6471, blue: 0.9608, colorSpace: ColorSpace.sRGB)"
1066 );
1067 }
1068}