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 #[must_use]
1012 pub fn parse_ffmpeg(s: &str) -> Option<Self> {
1013 let s = s.trim();
1014 let (body, alpha) = match s.split_once('@') {
1017 Some((body, alpha)) => (body, Some(alpha)),
1018 None => (s, None),
1019 };
1020
1021 let mut color = parse_hex_color(body).or_else(|| lookup_color_name(body))?;
1022 if let Some(alpha) = alpha {
1023 color.a = parse_alpha(alpha)?;
1024 }
1025 Some(color)
1026 }
1027
1028 #[must_use]
1036 pub fn ffmpeg_color_names() -> impl ExactSizeIterator<Item = (&'static str, Self)> {
1037 FFMPEG_COLOR_NAMES
1038 .iter()
1039 .map(|&(name, [r, g, b])| (name, Self::rgb(r, g, b)))
1040 }
1041}
1042
1043fn parse_hex_color(s: &str) -> Option<Color> {
1045 let hex = s.strip_prefix("0x").or_else(|| s.strip_prefix('#'))?;
1049 if hex.len() != 6 && hex.len() != 8 {
1050 return None;
1051 }
1052 let byte = |i: usize| u8::from_str_radix(hex.get(i..i + 2)?, 16).ok();
1053 Some(Color {
1054 r: byte(0)?,
1055 g: byte(2)?,
1056 b: byte(4)?,
1057 a: if hex.len() == 8 { byte(6)? } else { 255 },
1058 })
1059}
1060
1061#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1063fn parse_alpha(s: &str) -> Option<u8> {
1064 if let Some(hex) = s.strip_prefix("0x") {
1065 if hex.len() != 2 {
1068 return None;
1069 }
1070 return u8::from_str_radix(hex, 16).ok();
1071 }
1072 let f: f32 = s.parse().ok()?;
1073 if !(0.0..=1.0).contains(&f) {
1074 return None;
1075 }
1076 Some((f * 255.0).round() as u8)
1078}
1079
1080fn lookup_color_name(name: &str) -> Option<Color> {
1082 let lower = name.to_ascii_lowercase();
1083 let idx = FFMPEG_COLOR_NAMES
1084 .binary_search_by(|(n, _)| (*n).cmp(lower.as_str()))
1085 .ok()?;
1086 let [r, g, b] = FFMPEG_COLOR_NAMES[idx].1;
1087 Some(Color::rgb(r, g, b))
1088}
1089
1090const FFMPEG_COLOR_NAMES: &[(&str, [u8; 3])] = &[
1101 ("aliceblue", [0xF0, 0xF8, 0xFF]),
1102 ("antiquewhite", [0xFA, 0xEB, 0xD7]),
1103 ("aqua", [0x00, 0xFF, 0xFF]),
1104 ("aquamarine", [0x7F, 0xFF, 0xD4]),
1105 ("azure", [0xF0, 0xFF, 0xFF]),
1106 ("beige", [0xF5, 0xF5, 0xDC]),
1107 ("bisque", [0xFF, 0xE4, 0xC4]),
1108 ("black", [0x00, 0x00, 0x00]),
1109 ("blanchedalmond", [0xFF, 0xEB, 0xCD]),
1110 ("blue", [0x00, 0x00, 0xFF]),
1111 ("blueviolet", [0x8A, 0x2B, 0xE2]),
1112 ("brown", [0xA5, 0x2A, 0x2A]),
1113 ("burlywood", [0xDE, 0xB8, 0x87]),
1114 ("cadetblue", [0x5F, 0x9E, 0xA0]),
1115 ("chartreuse", [0x7F, 0xFF, 0x00]),
1116 ("chocolate", [0xD2, 0x69, 0x1E]),
1117 ("coral", [0xFF, 0x7F, 0x50]),
1118 ("cornflowerblue", [0x64, 0x95, 0xED]),
1119 ("cornsilk", [0xFF, 0xF8, 0xDC]),
1120 ("crimson", [0xDC, 0x14, 0x3C]),
1121 ("cyan", [0x00, 0xFF, 0xFF]),
1122 ("darkblue", [0x00, 0x00, 0x8B]),
1123 ("darkcyan", [0x00, 0x8B, 0x8B]),
1124 ("darkgoldenrod", [0xB8, 0x86, 0x0B]),
1125 ("darkgray", [0xA9, 0xA9, 0xA9]),
1126 ("darkgreen", [0x00, 0x64, 0x00]),
1127 ("darkkhaki", [0xBD, 0xB7, 0x6B]),
1128 ("darkmagenta", [0x8B, 0x00, 0x8B]),
1129 ("darkolivegreen", [0x55, 0x6B, 0x2F]),
1130 ("darkorange", [0xFF, 0x8C, 0x00]),
1131 ("darkorchid", [0x99, 0x32, 0xCC]),
1132 ("darkred", [0x8B, 0x00, 0x00]),
1133 ("darksalmon", [0xE9, 0x96, 0x7A]),
1134 ("darkseagreen", [0x8F, 0xBC, 0x8F]),
1135 ("darkslateblue", [0x48, 0x3D, 0x8B]),
1136 ("darkslategray", [0x2F, 0x4F, 0x4F]),
1137 ("darkturquoise", [0x00, 0xCE, 0xD1]),
1138 ("darkviolet", [0x94, 0x00, 0xD3]),
1139 ("deeppink", [0xFF, 0x14, 0x93]),
1140 ("deepskyblue", [0x00, 0xBF, 0xFF]),
1141 ("dimgray", [0x69, 0x69, 0x69]),
1142 ("dodgerblue", [0x1E, 0x90, 0xFF]),
1143 ("firebrick", [0xB2, 0x22, 0x22]),
1144 ("floralwhite", [0xFF, 0xFA, 0xF0]),
1145 ("forestgreen", [0x22, 0x8B, 0x22]),
1146 ("fuchsia", [0xFF, 0x00, 0xFF]),
1147 ("gainsboro", [0xDC, 0xDC, 0xDC]),
1148 ("ghostwhite", [0xF8, 0xF8, 0xFF]),
1149 ("gold", [0xFF, 0xD7, 0x00]),
1150 ("goldenrod", [0xDA, 0xA5, 0x20]),
1151 ("gray", [0x80, 0x80, 0x80]),
1152 ("green", [0x00, 0x80, 0x00]),
1153 ("greenyellow", [0xAD, 0xFF, 0x2F]),
1154 ("honeydew", [0xF0, 0xFF, 0xF0]),
1155 ("hotpink", [0xFF, 0x69, 0xB4]),
1156 ("indianred", [0xCD, 0x5C, 0x5C]),
1157 ("indigo", [0x4B, 0x00, 0x82]),
1158 ("ivory", [0xFF, 0xFF, 0xF0]),
1159 ("khaki", [0xF0, 0xE6, 0x8C]),
1160 ("lavender", [0xE6, 0xE6, 0xFA]),
1161 ("lavenderblush", [0xFF, 0xF0, 0xF5]),
1162 ("lawngreen", [0x7C, 0xFC, 0x00]),
1163 ("lemonchiffon", [0xFF, 0xFA, 0xCD]),
1164 ("lightblue", [0xAD, 0xD8, 0xE6]),
1165 ("lightcoral", [0xF0, 0x80, 0x80]),
1166 ("lightcyan", [0xE0, 0xFF, 0xFF]),
1167 ("lightgoldenrodyellow", [0xFA, 0xFA, 0xD2]),
1168 ("lightgreen", [0x90, 0xEE, 0x90]),
1169 ("lightgrey", [0xD3, 0xD3, 0xD3]),
1170 ("lightpink", [0xFF, 0xB6, 0xC1]),
1171 ("lightsalmon", [0xFF, 0xA0, 0x7A]),
1172 ("lightseagreen", [0x20, 0xB2, 0xAA]),
1173 ("lightskyblue", [0x87, 0xCE, 0xFA]),
1174 ("lightslategray", [0x77, 0x88, 0x99]),
1175 ("lightsteelblue", [0xB0, 0xC4, 0xDE]),
1176 ("lightyellow", [0xFF, 0xFF, 0xE0]),
1177 ("lime", [0x00, 0xFF, 0x00]),
1178 ("limegreen", [0x32, 0xCD, 0x32]),
1179 ("linen", [0xFA, 0xF0, 0xE6]),
1180 ("magenta", [0xFF, 0x00, 0xFF]),
1181 ("maroon", [0x80, 0x00, 0x00]),
1182 ("mediumaquamarine", [0x66, 0xCD, 0xAA]),
1183 ("mediumblue", [0x00, 0x00, 0xCD]),
1184 ("mediumorchid", [0xBA, 0x55, 0xD3]),
1185 ("mediumpurple", [0x93, 0x70, 0xD8]),
1186 ("mediumseagreen", [0x3C, 0xB3, 0x71]),
1187 ("mediumslateblue", [0x7B, 0x68, 0xEE]),
1188 ("mediumspringgreen", [0x00, 0xFA, 0x9A]),
1189 ("mediumturquoise", [0x48, 0xD1, 0xCC]),
1190 ("mediumvioletred", [0xC7, 0x15, 0x85]),
1191 ("midnightblue", [0x19, 0x19, 0x70]),
1192 ("mintcream", [0xF5, 0xFF, 0xFA]),
1193 ("mistyrose", [0xFF, 0xE4, 0xE1]),
1194 ("moccasin", [0xFF, 0xE4, 0xB5]),
1195 ("navajowhite", [0xFF, 0xDE, 0xAD]),
1196 ("navy", [0x00, 0x00, 0x80]),
1197 ("oldlace", [0xFD, 0xF5, 0xE6]),
1198 ("olive", [0x80, 0x80, 0x00]),
1199 ("olivedrab", [0x6B, 0x8E, 0x23]),
1200 ("orange", [0xFF, 0xA5, 0x00]),
1201 ("orangered", [0xFF, 0x45, 0x00]),
1202 ("orchid", [0xDA, 0x70, 0xD6]),
1203 ("palegoldenrod", [0xEE, 0xE8, 0xAA]),
1204 ("palegreen", [0x98, 0xFB, 0x98]),
1205 ("paleturquoise", [0xAF, 0xEE, 0xEE]),
1206 ("palevioletred", [0xD8, 0x70, 0x93]),
1207 ("papayawhip", [0xFF, 0xEF, 0xD5]),
1208 ("peachpuff", [0xFF, 0xDA, 0xB9]),
1209 ("peru", [0xCD, 0x85, 0x3F]),
1210 ("pink", [0xFF, 0xC0, 0xCB]),
1211 ("plum", [0xDD, 0xA0, 0xDD]),
1212 ("powderblue", [0xB0, 0xE0, 0xE6]),
1213 ("purple", [0x80, 0x00, 0x80]),
1214 ("red", [0xFF, 0x00, 0x00]),
1215 ("rosybrown", [0xBC, 0x8F, 0x8F]),
1216 ("royalblue", [0x41, 0x69, 0xE1]),
1217 ("saddlebrown", [0x8B, 0x45, 0x13]),
1218 ("salmon", [0xFA, 0x80, 0x72]),
1219 ("sandybrown", [0xF4, 0xA4, 0x60]),
1220 ("seagreen", [0x2E, 0x8B, 0x57]),
1221 ("seashell", [0xFF, 0xF5, 0xEE]),
1222 ("sienna", [0xA0, 0x52, 0x2D]),
1223 ("silver", [0xC0, 0xC0, 0xC0]),
1224 ("skyblue", [0x87, 0xCE, 0xEB]),
1225 ("slateblue", [0x6A, 0x5A, 0xCD]),
1226 ("slategray", [0x70, 0x80, 0x90]),
1227 ("snow", [0xFF, 0xFA, 0xFA]),
1228 ("springgreen", [0x00, 0xFF, 0x7F]),
1229 ("steelblue", [0x46, 0x82, 0xB4]),
1230 ("tan", [0xD2, 0xB4, 0x8C]),
1231 ("teal", [0x00, 0x80, 0x80]),
1232 ("thistle", [0xD8, 0xBF, 0xD8]),
1233 ("tomato", [0xFF, 0x63, 0x47]),
1234 ("turquoise", [0x40, 0xE0, 0xD0]),
1235 ("violet", [0xEE, 0x82, 0xEE]),
1236 ("wheat", [0xF5, 0xDE, 0xB3]),
1237 ("white", [0xFF, 0xFF, 0xFF]),
1238 ("whitesmoke", [0xF5, 0xF5, 0xF5]),
1239 ("yellow", [0xFF, 0xFF, 0x00]),
1240 ("yellowgreen", [0x9A, 0xCD, 0x32]),
1241];
1242
1243#[cfg(test)]
1244mod color_value_tests {
1245 use super::Color;
1246
1247 #[test]
1250 fn parse_ffmpeg_should_read_hex_with_and_without_alpha() {
1251 assert_eq!(
1252 Color::parse_ffmpeg("0x123456"),
1253 Some(Color::rgb(18, 52, 86))
1254 );
1255 assert_eq!(Color::parse_ffmpeg("#123456"), Some(Color::rgb(18, 52, 86)));
1256 assert_eq!(
1257 Color::parse_ffmpeg("0x12345680"),
1258 Some(Color::rgba(18, 52, 86, 128))
1259 );
1260 }
1261
1262 #[test]
1263 fn parse_ffmpeg_should_read_a_name_case_insensitively() {
1264 let green = Some(Color::rgb(0, 128, 0));
1267 assert_eq!(Color::parse_ffmpeg("green"), green);
1268 assert_eq!(Color::parse_ffmpeg("Green"), green);
1269 assert_eq!(Color::parse_ffmpeg("GREEN"), green);
1270 assert_eq!(Color::parse_ffmpeg("lime"), Some(Color::rgb(0, 255, 0)));
1271 assert_eq!(
1272 Color::parse_ffmpeg("DarkOrchid"),
1273 Some(Color::rgb(0x99, 0x32, 0xCC))
1274 );
1275 }
1276
1277 #[test]
1278 fn parse_ffmpeg_should_read_an_alpha_suffix_in_both_spellings() {
1279 assert_eq!(
1280 Color::parse_ffmpeg("black@0x80"),
1281 Some(Color::rgba(0, 0, 0, 128))
1282 );
1283 assert_eq!(
1284 Color::parse_ffmpeg("black@1"),
1285 Some(Color::rgba(0, 0, 0, 255))
1286 );
1287 assert_eq!(
1288 Color::parse_ffmpeg("black@0"),
1289 Some(Color::rgba(0, 0, 0, 0))
1290 );
1291 assert_eq!(
1293 Color::parse_ffmpeg("0x11223344@1.0"),
1294 Some(Color::rgba(0x11, 0x22, 0x33, 255))
1295 );
1296 }
1297
1298 #[test]
1299 fn parse_ffmpeg_should_reject_unparseable_forms() {
1300 assert_eq!(Color::parse_ffmpeg("random"), None);
1302 assert_eq!(Color::parse_ffmpeg("no_such_colour"), None);
1303 assert!(Color::parse_ffmpeg("lightgrey").is_some());
1306 assert_eq!(Color::parse_ffmpeg("lightgray"), None);
1307 assert_eq!(Color::parse_ffmpeg("0x1234"), None);
1308 assert_eq!(Color::parse_ffmpeg("0X123456"), None);
1311 assert_eq!(Color::parse_ffmpeg("black@0x8"), None);
1313 assert_eq!(Color::parse_ffmpeg("black@0x080"), None);
1314 assert_eq!(Color::parse_ffmpeg("0xGGGGGG"), None);
1315 assert_eq!(Color::parse_ffmpeg("black@1.5"), None);
1316 assert_eq!(Color::parse_ffmpeg(""), None);
1317 }
1318
1319 #[test]
1320 fn ffmpeg_color_names_should_be_sorted_and_lower_case() {
1321 for pair in super::FFMPEG_COLOR_NAMES.windows(2) {
1324 assert!(
1325 pair[0].0 < pair[1].0,
1326 "table must be sorted and unique: {:?} then {:?}",
1327 pair[0].0,
1328 pair[1].0
1329 );
1330 }
1331 for (name, _) in super::FFMPEG_COLOR_NAMES {
1332 assert_eq!(
1333 *name,
1334 name.to_ascii_lowercase(),
1335 "table names must be lower-case: {name:?}"
1336 );
1337 }
1338 }
1339
1340 #[test]
1341 fn rgb_should_be_opaque() {
1342 let c = Color::rgb(10, 20, 30);
1343 assert_eq!(c, Color::rgba(10, 20, 30, 255));
1344 assert_eq!(c.a, 255);
1345 }
1346
1347 #[test]
1348 fn consts_should_have_expected_channels() {
1349 assert_eq!(Color::WHITE, Color::rgba(255, 255, 255, 255));
1350 assert_eq!(Color::BLACK, Color::rgba(0, 0, 0, 255));
1351 assert_eq!(Color::TRANSPARENT.a, 0);
1352 }
1353}