1use std::fmt;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
34#[non_exhaustive]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum ColorSpace {
37 #[default]
39 Bt709,
40 Bt470bg,
42 Smpte170m,
44 Bt2020Ncl,
46 Bt2020Cl,
48 Rgb,
50 Fcc,
52 Smpte240m,
54 Ycgco,
56 Unknown,
58}
59
60impl ColorSpace {
61 #[must_use]
72 pub const fn name(&self) -> &'static str {
73 match self {
74 Self::Bt709 => "bt709",
75 Self::Bt470bg => "bt470bg",
76 Self::Smpte170m => "smpte170m",
77 Self::Bt2020Ncl => "bt2020ncl",
78 Self::Bt2020Cl => "bt2020cl",
79 Self::Rgb => "rgb",
80 Self::Fcc => "fcc",
81 Self::Smpte240m => "smpte240m",
82 Self::Ycgco => "ycgco",
83 Self::Unknown => "unknown",
84 }
85 }
86
87 #[must_use]
98 pub const fn is_hd(&self) -> bool {
99 matches!(self, Self::Bt709)
100 }
101
102 #[must_use]
113 pub const fn is_sd(&self) -> bool {
114 matches!(self, Self::Bt470bg | Self::Smpte170m)
115 }
116
117 #[must_use]
128 pub const fn is_uhd(&self) -> bool {
129 matches!(self, Self::Bt2020Ncl | Self::Bt2020Cl)
130 }
131
132 #[must_use]
143 pub const fn is_unknown(&self) -> bool {
144 matches!(self, Self::Unknown)
145 }
146}
147
148impl fmt::Display for ColorSpace {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "{}", self.name())
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
165#[non_exhaustive]
166#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
167pub enum ColorRange {
168 #[default]
170 Limited,
171 Full,
173 Unknown,
175}
176
177impl ColorRange {
178 #[must_use]
189 pub const fn name(&self) -> &'static str {
190 match self {
191 Self::Limited => "limited",
192 Self::Full => "full",
193 Self::Unknown => "unknown",
194 }
195 }
196
197 #[must_use]
208 pub const fn is_full(&self) -> bool {
209 matches!(self, Self::Full)
210 }
211
212 #[must_use]
223 pub const fn is_limited(&self) -> bool {
224 matches!(self, Self::Limited)
225 }
226
227 #[must_use]
238 pub const fn is_unknown(&self) -> bool {
239 matches!(self, Self::Unknown)
240 }
241
242 #[must_use]
253 pub const fn luma_min_8bit(&self) -> u8 {
254 match self {
255 Self::Limited => 16,
256 Self::Full | Self::Unknown => 0,
257 }
258 }
259
260 #[must_use]
271 pub const fn luma_max_8bit(&self) -> u8 {
272 match self {
273 Self::Limited => 235,
274 Self::Full | Self::Unknown => 255,
275 }
276 }
277}
278
279impl fmt::Display for ColorRange {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 write!(f, "{}", self.name())
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
297#[non_exhaustive]
298#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
299pub enum ColorPrimaries {
300 #[default]
302 Bt709,
303 Bt470bg,
305 Smpte170m,
307 Smpte240m,
309 Film,
311 DciP3,
313 DisplayP3,
315 Bt2020,
317 Unknown,
319}
320
321impl ColorPrimaries {
322 #[must_use]
333 pub const fn name(&self) -> &'static str {
334 match self {
335 Self::Bt709 => "bt709",
336 Self::Bt470bg => "bt470bg",
337 Self::Smpte170m => "smpte170m",
338 Self::Smpte240m => "smpte240m",
339 Self::Film => "film",
340 Self::DciP3 => "dci-p3",
341 Self::DisplayP3 => "display-p3",
342 Self::Bt2020 => "bt2020",
343 Self::Unknown => "unknown",
344 }
345 }
346
347 #[must_use]
359 pub const fn is_wide_gamut(&self) -> bool {
360 matches!(self, Self::Bt2020 | Self::DciP3 | Self::DisplayP3)
361 }
362
363 #[must_use]
374 pub const fn is_unknown(&self) -> bool {
375 matches!(self, Self::Unknown)
376 }
377}
378
379impl fmt::Display for ColorPrimaries {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 write!(f, "{}", self.name())
382 }
383}
384
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
402#[non_exhaustive]
403#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
404pub enum ColorTransfer {
405 #[default]
407 Bt709,
408 Gamma22,
410 Gamma28,
412 Smpte170m,
414 Smpte240m,
416 Linear,
418 Srgb,
420 Bt2020_10,
422 Bt2020_12,
424 Pq,
426 Hlg,
428 Unknown,
430}
431
432impl ColorTransfer {
433 #[must_use]
445 pub const fn name(&self) -> &'static str {
446 match self {
447 Self::Bt709 => "bt709",
448 Self::Gamma22 => "gamma22",
449 Self::Gamma28 => "gamma28",
450 Self::Smpte170m => "smpte170m",
451 Self::Smpte240m => "smpte240m",
452 Self::Linear => "linear",
453 Self::Srgb => "srgb",
454 Self::Bt2020_10 => "bt2020-10",
455 Self::Bt2020_12 => "bt2020-12",
456 Self::Pq => "pq",
457 Self::Hlg => "hlg",
458 Self::Unknown => "unknown",
459 }
460 }
461
462 #[must_use]
474 pub const fn is_hdr(&self) -> bool {
475 matches!(self, Self::Pq | Self::Hlg)
476 }
477
478 #[must_use]
489 pub const fn is_hlg(&self) -> bool {
490 matches!(self, Self::Hlg)
491 }
492
493 #[must_use]
504 pub const fn is_pq(&self) -> bool {
505 matches!(self, Self::Pq)
506 }
507
508 #[must_use]
519 pub const fn is_unknown(&self) -> bool {
520 matches!(self, Self::Unknown)
521 }
522}
523
524impl fmt::Display for ColorTransfer {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 write!(f, "{}", self.name())
527 }
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
535#[non_exhaustive]
536pub enum AlphaMode {
537 #[default]
539 Straight,
540 Premultiplied,
542 Unknown,
544}
545
546impl AlphaMode {
547 #[must_use]
558 pub const fn name(&self) -> &'static str {
559 match self {
560 Self::Straight => "straight",
561 Self::Premultiplied => "premultiplied",
562 Self::Unknown => "unknown",
563 }
564 }
565
566 #[must_use]
577 pub const fn is_straight(&self) -> bool {
578 matches!(self, Self::Straight)
579 }
580
581 #[must_use]
592 pub const fn is_premultiplied(&self) -> bool {
593 matches!(self, Self::Premultiplied)
594 }
595
596 #[must_use]
607 pub const fn is_unknown(&self) -> bool {
608 matches!(self, Self::Unknown)
609 }
610}
611
612impl fmt::Display for AlphaMode {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 write!(f, "{}", self.name())
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 mod color_space_tests {
623 use super::*;
624
625 #[test]
626 fn test_names() {
627 assert_eq!(ColorSpace::Bt709.name(), "bt709");
628 assert_eq!(ColorSpace::Bt470bg.name(), "bt470bg");
629 assert_eq!(ColorSpace::Smpte170m.name(), "smpte170m");
630 assert_eq!(ColorSpace::Bt2020Ncl.name(), "bt2020ncl");
631 assert_eq!(ColorSpace::Bt2020Cl.name(), "bt2020cl");
632 assert_eq!(ColorSpace::Rgb.name(), "rgb");
633 assert_eq!(ColorSpace::Fcc.name(), "fcc");
634 assert_eq!(ColorSpace::Smpte240m.name(), "smpte240m");
635 assert_eq!(ColorSpace::Ycgco.name(), "ycgco");
636 assert_eq!(ColorSpace::Unknown.name(), "unknown");
637 }
638
639 #[test]
640 fn test_display() {
641 assert_eq!(format!("{}", ColorSpace::Bt709), "bt709");
642 assert_eq!(format!("{}", ColorSpace::Bt2020Ncl), "bt2020ncl");
643 }
644
645 #[test]
646 fn test_default() {
647 assert_eq!(ColorSpace::default(), ColorSpace::Bt709);
648 }
649
650 #[test]
651 fn test_is_hd_sd_uhd() {
652 assert!(ColorSpace::Bt709.is_hd());
653 assert!(!ColorSpace::Bt709.is_sd());
654 assert!(!ColorSpace::Bt709.is_uhd());
655
656 assert!(!ColorSpace::Smpte170m.is_hd());
657 assert!(ColorSpace::Smpte170m.is_sd());
658 assert!(ColorSpace::Bt470bg.is_sd());
659 assert!(!ColorSpace::Smpte170m.is_uhd());
660
661 assert!(!ColorSpace::Bt2020Ncl.is_hd());
662 assert!(!ColorSpace::Bt2020Ncl.is_sd());
663 assert!(ColorSpace::Bt2020Ncl.is_uhd());
664 assert!(ColorSpace::Bt2020Cl.is_uhd());
665 }
666
667 #[test]
668 fn test_is_unknown() {
669 assert!(ColorSpace::Unknown.is_unknown());
670 assert!(!ColorSpace::Bt709.is_unknown());
671 }
672
673 #[test]
674 fn test_debug() {
675 assert_eq!(format!("{:?}", ColorSpace::Bt709), "Bt709");
676 assert_eq!(format!("{:?}", ColorSpace::Rgb), "Rgb");
677 }
678
679 #[test]
680 fn test_equality_and_hash() {
681 use std::collections::HashSet;
682
683 assert_eq!(ColorSpace::Bt709, ColorSpace::Bt709);
684 assert_ne!(ColorSpace::Bt709, ColorSpace::Smpte170m);
685
686 let mut set = HashSet::new();
687 set.insert(ColorSpace::Bt709);
688 set.insert(ColorSpace::Smpte170m);
689 assert!(set.contains(&ColorSpace::Bt709));
690 assert!(!set.contains(&ColorSpace::Bt2020Ncl));
691 }
692
693 #[test]
694 fn test_copy() {
695 let space = ColorSpace::Bt709;
696 let copied = space;
697 assert_eq!(space, copied);
698 }
699 }
700
701 mod color_range_tests {
702 use super::*;
703
704 #[test]
705 fn test_names() {
706 assert_eq!(ColorRange::Limited.name(), "limited");
707 assert_eq!(ColorRange::Full.name(), "full");
708 assert_eq!(ColorRange::Unknown.name(), "unknown");
709 }
710
711 #[test]
712 fn test_display() {
713 assert_eq!(format!("{}", ColorRange::Limited), "limited");
714 assert_eq!(format!("{}", ColorRange::Full), "full");
715 }
716
717 #[test]
718 fn test_default() {
719 assert_eq!(ColorRange::default(), ColorRange::Limited);
720 }
721
722 #[test]
723 fn test_is_full_limited() {
724 assert!(ColorRange::Full.is_full());
725 assert!(!ColorRange::Full.is_limited());
726
727 assert!(!ColorRange::Limited.is_full());
728 assert!(ColorRange::Limited.is_limited());
729 }
730
731 #[test]
732 fn test_is_unknown() {
733 assert!(ColorRange::Unknown.is_unknown());
734 assert!(!ColorRange::Limited.is_unknown());
735 }
736
737 #[test]
738 fn test_luma_values() {
739 assert_eq!(ColorRange::Limited.luma_min_8bit(), 16);
740 assert_eq!(ColorRange::Limited.luma_max_8bit(), 235);
741
742 assert_eq!(ColorRange::Full.luma_min_8bit(), 0);
743 assert_eq!(ColorRange::Full.luma_max_8bit(), 255);
744
745 assert_eq!(ColorRange::Unknown.luma_min_8bit(), 0);
746 assert_eq!(ColorRange::Unknown.luma_max_8bit(), 255);
747 }
748
749 #[test]
750 fn test_equality_and_hash() {
751 use std::collections::HashSet;
752
753 assert_eq!(ColorRange::Limited, ColorRange::Limited);
754 assert_ne!(ColorRange::Limited, ColorRange::Full);
755
756 let mut set = HashSet::new();
757 set.insert(ColorRange::Limited);
758 set.insert(ColorRange::Full);
759 assert!(set.contains(&ColorRange::Limited));
760 assert!(!set.contains(&ColorRange::Unknown));
761 }
762 }
763
764 mod color_primaries_tests {
765 use super::*;
766
767 #[test]
768 fn test_names() {
769 assert_eq!(ColorPrimaries::Bt709.name(), "bt709");
770 assert_eq!(ColorPrimaries::Bt470bg.name(), "bt470bg");
771 assert_eq!(ColorPrimaries::Smpte170m.name(), "smpte170m");
772 assert_eq!(ColorPrimaries::Smpte240m.name(), "smpte240m");
773 assert_eq!(ColorPrimaries::Film.name(), "film");
774 assert_eq!(ColorPrimaries::DciP3.name(), "dci-p3");
775 assert_eq!(ColorPrimaries::DisplayP3.name(), "display-p3");
776 assert_eq!(ColorPrimaries::Bt2020.name(), "bt2020");
777 assert_eq!(ColorPrimaries::Unknown.name(), "unknown");
778 }
779
780 #[test]
781 fn test_display() {
782 assert_eq!(format!("{}", ColorPrimaries::Bt709), "bt709");
783 assert_eq!(format!("{}", ColorPrimaries::Bt2020), "bt2020");
784 }
785
786 #[test]
787 fn test_default() {
788 assert_eq!(ColorPrimaries::default(), ColorPrimaries::Bt709);
789 }
790
791 #[test]
792 fn test_is_wide_gamut() {
793 assert!(ColorPrimaries::Bt2020.is_wide_gamut());
794 assert!(ColorPrimaries::DciP3.is_wide_gamut());
795 assert!(ColorPrimaries::DisplayP3.is_wide_gamut());
796 assert!(!ColorPrimaries::Bt709.is_wide_gamut());
797 assert!(!ColorPrimaries::Smpte170m.is_wide_gamut());
798 }
799
800 #[test]
801 fn test_is_unknown() {
802 assert!(ColorPrimaries::Unknown.is_unknown());
803 assert!(!ColorPrimaries::Bt709.is_unknown());
804 }
805
806 #[test]
807 fn test_equality_and_hash() {
808 use std::collections::HashSet;
809
810 assert_eq!(ColorPrimaries::Bt709, ColorPrimaries::Bt709);
811 assert_ne!(ColorPrimaries::Bt709, ColorPrimaries::Bt2020);
812
813 let mut set = HashSet::new();
814 set.insert(ColorPrimaries::Bt709);
815 set.insert(ColorPrimaries::Bt2020);
816 assert!(set.contains(&ColorPrimaries::Bt709));
817 assert!(!set.contains(&ColorPrimaries::Smpte170m));
818 }
819 }
820
821 mod color_transfer_tests {
822 use super::*;
823
824 #[test]
825 fn test_names() {
826 assert_eq!(ColorTransfer::Bt709.name(), "bt709");
827 assert_eq!(ColorTransfer::Gamma22.name(), "gamma22");
828 assert_eq!(ColorTransfer::Gamma28.name(), "gamma28");
829 assert_eq!(ColorTransfer::Smpte170m.name(), "smpte170m");
830 assert_eq!(ColorTransfer::Smpte240m.name(), "smpte240m");
831 assert_eq!(ColorTransfer::Linear.name(), "linear");
832 assert_eq!(ColorTransfer::Srgb.name(), "srgb");
833 assert_eq!(ColorTransfer::Bt2020_10.name(), "bt2020-10");
834 assert_eq!(ColorTransfer::Bt2020_12.name(), "bt2020-12");
835 assert_eq!(ColorTransfer::Pq.name(), "pq");
836 assert_eq!(ColorTransfer::Hlg.name(), "hlg");
837 assert_eq!(ColorTransfer::Unknown.name(), "unknown");
838 }
839
840 #[test]
841 fn test_display() {
842 assert_eq!(format!("{}", ColorTransfer::Hlg), "hlg");
843 assert_eq!(format!("{}", ColorTransfer::Pq), "pq");
844 assert_eq!(format!("{}", ColorTransfer::Bt709), "bt709");
845 }
846
847 #[test]
848 fn test_default() {
849 assert_eq!(ColorTransfer::default(), ColorTransfer::Bt709);
850 }
851
852 #[test]
853 fn hlg_is_hdr_should_return_true() {
854 assert!(ColorTransfer::Hlg.is_hdr());
855 assert!(ColorTransfer::Hlg.is_hlg());
856 assert!(!ColorTransfer::Hlg.is_pq());
857 }
858
859 #[test]
860 fn pq_is_hdr_should_return_true() {
861 assert!(ColorTransfer::Pq.is_hdr());
862 assert!(ColorTransfer::Pq.is_pq());
863 assert!(!ColorTransfer::Pq.is_hlg());
864 }
865
866 #[test]
867 fn sdr_transfers_are_not_hdr() {
868 assert!(!ColorTransfer::Bt709.is_hdr());
869 assert!(!ColorTransfer::Gamma22.is_hdr());
870 assert!(!ColorTransfer::Gamma28.is_hdr());
871 assert!(!ColorTransfer::Smpte170m.is_hdr());
872 assert!(!ColorTransfer::Smpte240m.is_hdr());
873 assert!(!ColorTransfer::Srgb.is_hdr());
874 assert!(!ColorTransfer::Bt2020_10.is_hdr());
875 assert!(!ColorTransfer::Bt2020_12.is_hdr());
876 assert!(!ColorTransfer::Linear.is_hdr());
877 }
878
879 #[test]
880 fn is_unknown_should_only_match_unknown() {
881 assert!(ColorTransfer::Unknown.is_unknown());
882 assert!(!ColorTransfer::Bt709.is_unknown());
883 assert!(!ColorTransfer::Hlg.is_unknown());
884 }
885
886 #[test]
887 fn test_equality_and_hash() {
888 use std::collections::HashSet;
889
890 assert_eq!(ColorTransfer::Hlg, ColorTransfer::Hlg);
891 assert_ne!(ColorTransfer::Hlg, ColorTransfer::Pq);
892
893 let mut set = HashSet::new();
894 set.insert(ColorTransfer::Hlg);
895 set.insert(ColorTransfer::Pq);
896 assert!(set.contains(&ColorTransfer::Hlg));
897 assert!(!set.contains(&ColorTransfer::Bt709));
898 }
899 }
900
901 mod alpha_mode_tests {
902 use super::*;
903
904 #[test]
905 fn test_names() {
906 assert_eq!(AlphaMode::Straight.name(), "straight");
907 assert_eq!(AlphaMode::Premultiplied.name(), "premultiplied");
908 assert_eq!(AlphaMode::Unknown.name(), "unknown");
909 }
910
911 #[test]
912 fn test_display() {
913 assert_eq!(format!("{}", AlphaMode::Straight), "straight");
914 assert_eq!(format!("{}", AlphaMode::Premultiplied), "premultiplied");
915 assert_eq!(format!("{}", AlphaMode::Unknown), "unknown");
916 }
917
918 #[test]
919 fn test_default() {
920 assert_eq!(AlphaMode::default(), AlphaMode::Straight);
921 }
922
923 #[test]
924 fn is_straight_should_only_match_straight() {
925 assert!(AlphaMode::Straight.is_straight());
926 assert!(!AlphaMode::Premultiplied.is_straight());
927 assert!(!AlphaMode::Unknown.is_straight());
928 }
929
930 #[test]
931 fn is_premultiplied_should_only_match_premultiplied() {
932 assert!(AlphaMode::Premultiplied.is_premultiplied());
933 assert!(!AlphaMode::Straight.is_premultiplied());
934 assert!(!AlphaMode::Unknown.is_premultiplied());
935 }
936
937 #[test]
938 fn is_unknown_should_only_match_unknown() {
939 assert!(AlphaMode::Unknown.is_unknown());
940 assert!(!AlphaMode::Straight.is_unknown());
941 assert!(!AlphaMode::Premultiplied.is_unknown());
942 }
943
944 #[test]
945 fn test_equality_and_hash() {
946 use std::collections::HashSet;
947
948 assert_eq!(AlphaMode::Straight, AlphaMode::Straight);
949 assert_ne!(AlphaMode::Premultiplied, AlphaMode::Straight);
950
951 let mut set = HashSet::new();
952 set.insert(AlphaMode::Straight);
953 assert!(set.contains(&AlphaMode::Straight));
954 assert!(!set.contains(&AlphaMode::Premultiplied));
955 }
956 }
957}
958
959#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
965#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
966pub struct Color {
967 pub r: u8,
969 pub g: u8,
971 pub b: u8,
973 pub a: u8,
975}
976
977impl Color {
978 pub const WHITE: Self = Self::rgb(255, 255, 255);
980 pub const BLACK: Self = Self::rgb(0, 0, 0);
982 pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
984
985 #[must_use]
987 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
988 Self { r, g, b, a: 255 }
989 }
990
991 #[must_use]
993 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
994 Self { r, g, b, a }
995 }
996}
997
998#[cfg(test)]
999mod color_value_tests {
1000 use super::Color;
1001
1002 #[test]
1003 fn rgb_should_be_opaque() {
1004 let c = Color::rgb(10, 20, 30);
1005 assert_eq!(c, Color::rgba(10, 20, 30, 255));
1006 assert_eq!(c.a, 255);
1007 }
1008
1009 #[test]
1010 fn consts_should_have_expected_channels() {
1011 assert_eq!(Color::WHITE, Color::rgba(255, 255, 255, 255));
1012 assert_eq!(Color::BLACK, Color::rgba(0, 0, 0, 255));
1013 assert_eq!(Color::TRANSPARENT.a, 0);
1014 }
1015}