1use core::fmt;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[non_exhaustive]
18#[repr(u8)]
19pub enum ChannelType {
20 U8 = 1,
22 U16 = 2,
24 F32 = 4,
26 F16 = 5,
28}
29
30impl ChannelType {
31 #[inline]
33 #[allow(unreachable_patterns)]
34 pub const fn byte_size(self) -> usize {
35 match self {
36 Self::U8 => 1,
37 Self::U16 | Self::F16 => 2,
38 Self::F32 => 4,
39 _ => 0,
40 }
41 }
42
43 #[inline]
45 pub const fn is_u8(self) -> bool {
46 matches!(self, Self::U8)
47 }
48
49 #[inline]
51 pub const fn is_u16(self) -> bool {
52 matches!(self, Self::U16)
53 }
54
55 #[inline]
57 pub const fn is_f32(self) -> bool {
58 matches!(self, Self::F32)
59 }
60
61 #[inline]
63 pub const fn is_f16(self) -> bool {
64 matches!(self, Self::F16)
65 }
66
67 #[inline]
69 #[allow(unreachable_patterns)]
70 pub const fn is_integer(self) -> bool {
71 matches!(self, Self::U8 | Self::U16)
72 }
73
74 #[inline]
76 #[allow(unreachable_patterns)]
77 pub const fn is_float(self) -> bool {
78 matches!(self, Self::F32 | Self::F16)
79 }
80}
81
82impl fmt::Display for ChannelType {
83 #[allow(unreachable_patterns)]
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::U8 => f.write_str("U8"),
87 Self::U16 => f.write_str("U16"),
88 Self::F32 => f.write_str("F32"),
89 Self::F16 => f.write_str("F16"),
90 _ => write!(f, "ChannelType({})", *self as u8),
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102#[non_exhaustive]
103#[repr(u8)]
104pub enum ChannelLayout {
105 Gray = 1,
107 GrayAlpha = 2,
109 Rgb = 3,
111 Rgba = 4,
113 Bgra = 5,
115 Oklab = 6,
117 OklabA = 7,
119 Cmyk = 8,
121}
122
123impl ChannelLayout {
124 #[inline]
126 #[allow(unreachable_patterns)]
127 pub const fn channels(self) -> usize {
128 match self {
129 Self::Gray => 1,
130 Self::GrayAlpha => 2,
131 Self::Rgb | Self::Oklab => 3,
132 Self::Rgba | Self::Bgra | Self::OklabA | Self::Cmyk => 4,
133 _ => 0,
134 }
135 }
136
137 #[inline]
139 #[allow(unreachable_patterns)]
140 pub const fn has_alpha(self) -> bool {
141 matches!(
142 self,
143 Self::GrayAlpha | Self::Rgba | Self::Bgra | Self::OklabA
144 )
145 }
146}
147
148impl fmt::Display for ChannelLayout {
149 #[allow(unreachable_patterns)]
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 match self {
152 Self::Gray => f.write_str("Gray"),
153 Self::GrayAlpha => f.write_str("GrayAlpha"),
154 Self::Rgb => f.write_str("RGB"),
155 Self::Rgba => f.write_str("RGBA"),
156 Self::Bgra => f.write_str("BGRA"),
157 Self::Oklab => f.write_str("Oklab"),
158 Self::OklabA => f.write_str("OklabA"),
159 Self::Cmyk => f.write_str("CMYK"),
160 _ => write!(f, "ChannelLayout({})", *self as u8),
161 }
162 }
163}
164
165#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176#[non_exhaustive]
177#[repr(u8)]
178pub enum AlphaMode {
179 Undefined = 1,
181 Straight = 2,
183 Premultiplied = 3,
185 Opaque = 4,
187}
188
189impl AlphaMode {
190 #[inline]
192 pub const fn has_alpha(self) -> bool {
193 matches!(self, Self::Straight | Self::Premultiplied | Self::Opaque)
194 }
195}
196
197impl fmt::Display for AlphaMode {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 Self::Undefined => f.write_str("undefined"),
201 Self::Straight => f.write_str("straight"),
202 Self::Premultiplied => f.write_str("premultiplied"),
203 Self::Opaque => f.write_str("opaque"),
204 }
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223#[non_exhaustive]
224#[repr(u8)]
225pub enum TransferFunction {
226 Linear = 0,
228 Srgb = 1,
230 Bt709 = 2,
232 Pq = 3,
234 Gamma22 = 5,
250 Hlg = 4,
252 Unknown = 255,
286}
287
288impl TransferFunction {
289 #[inline]
291 pub const fn from_cicp(tc: u8) -> Option<Self> {
292 match tc {
293 1 => Some(Self::Bt709),
294 6 | 7 => Some(Self::Bt709),
297 8 => Some(Self::Linear),
298 13 => Some(Self::Srgb),
299 16 => Some(Self::Pq),
300 18 => Some(Self::Hlg),
301 _ => None,
302 }
303 }
304
305 #[allow(unreachable_patterns)]
307 #[inline]
308 pub const fn to_cicp(self) -> Option<u8> {
309 match self {
310 Self::Bt709 => Some(1),
311 Self::Linear => Some(8),
312 Self::Srgb => Some(13),
313 Self::Pq => Some(16),
314 Self::Hlg => Some(18),
315 Self::Unknown => None,
316 _ => None,
317 }
318 }
319
320 #[allow(unreachable_patterns)]
326 pub fn reference_white_nits(&self) -> f32 {
327 match self {
328 Self::Pq => 203.0,
329 _ => 1.0,
330 }
331 }
332}
333
334impl fmt::Display for TransferFunction {
335 #[allow(unreachable_patterns)]
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 match self {
338 Self::Linear => f.write_str("linear"),
339 Self::Srgb => f.write_str("sRGB"),
340 Self::Bt709 => f.write_str("BT.709"),
341 Self::Pq => f.write_str("PQ"),
342 Self::Gamma22 => f.write_str("gamma 2.2"),
343 Self::Hlg => f.write_str("HLG"),
344 Self::Unknown => f.write_str("unknown"),
345 _ => f.write_str("TransferFunction(?)"),
346 }
347 }
348}
349
350#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362#[non_exhaustive]
363#[repr(u8)]
364pub enum ColorPrimaries {
365 #[default]
367 Bt709 = 1,
368 Bt2020 = 9,
370 DisplayP3 = 12,
373 AdobeRgb = 13,
375 Unknown = 255,
432}
433
434impl ColorPrimaries {
435 pub const WHITE_D65: (f32, f32) = (0.3127, 0.3290);
437 #[allow(dead_code)] pub(crate) const WHITE_DCI: (f32, f32) = (0.314, 0.351);
443
444 #[allow(unreachable_patterns, clippy::type_complexity)]
450 pub const fn chromaticity(self) -> Option<((f32, f32), (f32, f32), (f32, f32))> {
451 match self {
452 Self::Bt709 => Some(((0.64, 0.33), (0.30, 0.60), (0.15, 0.06))),
453 Self::DisplayP3 => Some(((0.680, 0.320), (0.265, 0.690), (0.150, 0.060))),
454 Self::Bt2020 => Some(((0.708, 0.292), (0.170, 0.797), (0.131, 0.046))),
455 Self::AdobeRgb => Some(((0.64, 0.33), (0.21, 0.71), (0.15, 0.06))),
456 Self::Unknown => None,
457 _ => None,
458 }
459 }
460
461 #[inline]
463 pub const fn from_cicp(code: u8) -> Option<Self> {
464 match code {
465 1 => Some(Self::Bt709),
466 9 => Some(Self::Bt2020),
467 12 => Some(Self::DisplayP3),
468 _ => None,
469 }
470 }
471
472 #[allow(unreachable_patterns)]
474 #[inline]
475 pub const fn to_cicp(self) -> Option<u8> {
476 match self {
477 Self::Bt709 => Some(1),
478 Self::Bt2020 => Some(9),
479 Self::DisplayP3 => Some(12),
480 Self::Unknown => None,
481 _ => None,
482 }
483 }
484
485 #[allow(unreachable_patterns)]
487 #[inline]
488 pub const fn white_point(self) -> (f32, f32) {
489 match self {
490 Self::Bt709 | Self::Bt2020 | Self::DisplayP3 | Self::AdobeRgb => Self::WHITE_D65,
491 Self::Unknown => Self::WHITE_D65,
492 _ => Self::WHITE_D65,
493 }
494 }
495
496 #[inline]
499 pub(crate) const fn needs_chromatic_adaptation(self, other: Self) -> bool {
500 let (sx, sy) = self.white_point();
501 let (ox, oy) = other.white_point();
502 sx.to_bits() != ox.to_bits() || sy.to_bits() != oy.to_bits()
503 }
504
505 pub const fn gamut_matrix_to(self, dst: Self) -> Option<[[f32; 3]; 3]> {
521 crate::registry::gamut_matrix(self, dst)
522 }
523
524 #[inline]
530 pub const fn contains(self, other: Self) -> bool {
531 !self.needs_chromatic_adaptation(other)
532 && self.gamut_width() >= other.gamut_width()
533 && !matches!(self, Self::Unknown)
534 && !matches!(other, Self::Unknown)
535 }
536
537 #[allow(unreachable_patterns)]
538 const fn gamut_width(self) -> u8 {
539 match self {
540 Self::Bt709 => 1,
541 Self::AdobeRgb => 2,
542 Self::DisplayP3 => 3,
543 Self::Bt2020 => 4,
544 Self::Unknown => 0,
545 _ => 0,
546 }
547 }
548}
549
550impl fmt::Display for ColorPrimaries {
551 #[allow(unreachable_patterns)]
552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553 match self {
554 Self::Bt709 => f.write_str("BT.709"),
555 Self::Bt2020 => f.write_str("BT.2020"),
556 Self::DisplayP3 => f.write_str("Display P3"),
557 Self::AdobeRgb => f.write_str("Adobe RGB"),
558 Self::Unknown => f.write_str("unknown"),
559 _ => f.write_str("ColorPrimaries(?)"),
560 }
561 }
562}
563
564#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
570#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
571#[non_exhaustive]
572#[repr(u8)]
573pub enum SignalRange {
574 #[default]
576 Full = 0,
577 Narrow = 1,
579}
580
581impl fmt::Display for SignalRange {
582 #[allow(unreachable_patterns)]
583 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584 match self {
585 Self::Full => f.write_str("full"),
586 Self::Narrow => f.write_str("narrow"),
587 _ => write!(f, "SignalRange({})", *self as u8),
588 }
589 }
590}
591
592#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
601#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
602#[non_exhaustive]
603pub struct PixelDescriptor {
604 pub format: PixelFormat,
606 pub transfer: TransferFunction,
608 pub alpha: Option<AlphaMode>,
610 pub primaries: ColorPrimaries,
612 pub signal_range: SignalRange,
614}
615
616impl PixelDescriptor {
617 #[inline]
621 pub const fn pixel_format(&self) -> PixelFormat {
622 self.format
623 }
624
625 #[inline]
627 pub const fn channel_type(&self) -> ChannelType {
628 self.format.channel_type()
629 }
630
631 #[inline]
633 pub const fn alpha(&self) -> Option<AlphaMode> {
634 self.alpha
635 }
636
637 #[inline]
639 pub const fn transfer(&self) -> TransferFunction {
640 self.transfer
641 }
642
643 #[inline]
645 pub const fn color_profile_source(&self) -> crate::ColorProfileSource<'static> {
646 crate::ColorProfileSource::PrimariesTransferPair {
647 primaries: self.primaries,
648 transfer: self.transfer,
649 }
650 }
651
652 #[inline]
654 pub const fn byte_order(&self) -> ByteOrder {
655 self.format.byte_order()
656 }
657
658 #[inline]
660 pub const fn color_model(&self) -> ColorModel {
661 self.format.color_model()
662 }
663
664 #[inline]
666 pub const fn layout(&self) -> ChannelLayout {
667 self.format.layout()
668 }
669
670 #[inline]
688 pub const fn new(
689 channel_type: ChannelType,
690 layout: ChannelLayout,
691 alpha: Option<AlphaMode>,
692 transfer: TransferFunction,
693 ) -> Self {
694 let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
695 Some(f) => f,
696 None => panic!("unsupported PixelFormat combination"),
697 };
698 Self {
699 format,
700 transfer,
701 alpha,
702 primaries: ColorPrimaries::Bt709,
703 signal_range: SignalRange::Full,
704 }
705 }
706
707 #[inline]
722 pub const fn new_full(
723 channel_type: ChannelType,
724 layout: ChannelLayout,
725 alpha: Option<AlphaMode>,
726 transfer: TransferFunction,
727 primaries: ColorPrimaries,
728 ) -> Self {
729 let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
730 Some(f) => f,
731 None => panic!("unsupported PixelFormat combination"),
732 };
733 Self {
734 format,
735 transfer,
736 alpha,
737 primaries,
738 signal_range: SignalRange::Full,
739 }
740 }
741
742 #[inline]
745 pub const fn from_pixel_format(format: PixelFormat) -> Self {
746 Self {
747 format,
748 transfer: TransferFunction::Unknown,
749 alpha: format.default_alpha(),
750 primaries: ColorPrimaries::Bt709,
751 signal_range: SignalRange::Full,
752 }
753 }
754
755 pub const RGB8_SRGB: Self = Self::new(
759 ChannelType::U8,
760 ChannelLayout::Rgb,
761 None,
762 TransferFunction::Srgb,
763 );
764 pub const RGBA8_SRGB: Self = Self::new(
766 ChannelType::U8,
767 ChannelLayout::Rgba,
768 Some(AlphaMode::Straight),
769 TransferFunction::Srgb,
770 );
771 pub const RGB16_SRGB: Self = Self::new(
773 ChannelType::U16,
774 ChannelLayout::Rgb,
775 None,
776 TransferFunction::Srgb,
777 );
778 pub const RGBA16_SRGB: Self = Self::new(
780 ChannelType::U16,
781 ChannelLayout::Rgba,
782 Some(AlphaMode::Straight),
783 TransferFunction::Srgb,
784 );
785 pub const RGBF32_LINEAR: Self = Self::new(
787 ChannelType::F32,
788 ChannelLayout::Rgb,
789 None,
790 TransferFunction::Linear,
791 );
792 pub const RGBAF32_LINEAR: Self = Self::new(
794 ChannelType::F32,
795 ChannelLayout::Rgba,
796 Some(AlphaMode::Straight),
797 TransferFunction::Linear,
798 );
799 pub const GRAY8_SRGB: Self = Self::new(
801 ChannelType::U8,
802 ChannelLayout::Gray,
803 None,
804 TransferFunction::Srgb,
805 );
806 pub const GRAY16_SRGB: Self = Self::new(
808 ChannelType::U16,
809 ChannelLayout::Gray,
810 None,
811 TransferFunction::Srgb,
812 );
813 pub const GRAYF32_LINEAR: Self = Self::new(
815 ChannelType::F32,
816 ChannelLayout::Gray,
817 None,
818 TransferFunction::Linear,
819 );
820 pub const GRAYA8_SRGB: Self = Self::new(
822 ChannelType::U8,
823 ChannelLayout::GrayAlpha,
824 Some(AlphaMode::Straight),
825 TransferFunction::Srgb,
826 );
827 pub const GRAYA16_SRGB: Self = Self::new(
829 ChannelType::U16,
830 ChannelLayout::GrayAlpha,
831 Some(AlphaMode::Straight),
832 TransferFunction::Srgb,
833 );
834 pub const GRAYAF32_LINEAR: Self = Self::new(
836 ChannelType::F32,
837 ChannelLayout::GrayAlpha,
838 Some(AlphaMode::Straight),
839 TransferFunction::Linear,
840 );
841 pub const BGRA8_SRGB: Self = Self::new(
843 ChannelType::U8,
844 ChannelLayout::Bgra,
845 Some(AlphaMode::Straight),
846 TransferFunction::Srgb,
847 );
848 pub const RGBX8_SRGB: Self = Self::new(
850 ChannelType::U8,
851 ChannelLayout::Rgba,
852 Some(AlphaMode::Undefined),
853 TransferFunction::Srgb,
854 );
855 pub const BGRX8_SRGB: Self = Self::new(
857 ChannelType::U8,
858 ChannelLayout::Bgra,
859 Some(AlphaMode::Undefined),
860 TransferFunction::Srgb,
861 );
862
863 pub const RGB16_BT2100_PQ: Self = Self::new_full(
872 ChannelType::U16,
873 ChannelLayout::Rgb,
874 None,
875 TransferFunction::Pq,
876 ColorPrimaries::Bt2020,
877 );
878 pub const RGB16_BT2100_HLG: Self = Self::new_full(
884 ChannelType::U16,
885 ChannelLayout::Rgb,
886 None,
887 TransferFunction::Hlg,
888 ColorPrimaries::Bt2020,
889 );
890
891 pub const RGB8: Self = Self::new(
895 ChannelType::U8,
896 ChannelLayout::Rgb,
897 None,
898 TransferFunction::Unknown,
899 );
900 pub const RGBA8: Self = Self::new(
902 ChannelType::U8,
903 ChannelLayout::Rgba,
904 Some(AlphaMode::Straight),
905 TransferFunction::Unknown,
906 );
907 pub const RGB16: Self = Self::new(
909 ChannelType::U16,
910 ChannelLayout::Rgb,
911 None,
912 TransferFunction::Unknown,
913 );
914 pub const RGBA16: Self = Self::new(
916 ChannelType::U16,
917 ChannelLayout::Rgba,
918 Some(AlphaMode::Straight),
919 TransferFunction::Unknown,
920 );
921 pub const RGBF32: Self = Self::new(
923 ChannelType::F32,
924 ChannelLayout::Rgb,
925 None,
926 TransferFunction::Unknown,
927 );
928 pub const RGBAF32: Self = Self::new(
930 ChannelType::F32,
931 ChannelLayout::Rgba,
932 Some(AlphaMode::Straight),
933 TransferFunction::Unknown,
934 );
935 pub const GRAY8: Self = Self::new(
937 ChannelType::U8,
938 ChannelLayout::Gray,
939 None,
940 TransferFunction::Unknown,
941 );
942 pub const GRAY16: Self = Self::new(
944 ChannelType::U16,
945 ChannelLayout::Gray,
946 None,
947 TransferFunction::Unknown,
948 );
949 pub const GRAYF32: Self = Self::new(
951 ChannelType::F32,
952 ChannelLayout::Gray,
953 None,
954 TransferFunction::Unknown,
955 );
956 pub const GRAYA8: Self = Self::new(
958 ChannelType::U8,
959 ChannelLayout::GrayAlpha,
960 Some(AlphaMode::Straight),
961 TransferFunction::Unknown,
962 );
963 pub const GRAYA16: Self = Self::new(
965 ChannelType::U16,
966 ChannelLayout::GrayAlpha,
967 Some(AlphaMode::Straight),
968 TransferFunction::Unknown,
969 );
970 pub const GRAYAF32: Self = Self::new(
972 ChannelType::F32,
973 ChannelLayout::GrayAlpha,
974 Some(AlphaMode::Straight),
975 TransferFunction::Unknown,
976 );
977
978 pub(crate) const RGBF16: Self = Self::new(
984 ChannelType::F16,
985 ChannelLayout::Rgb,
986 None,
987 TransferFunction::Unknown,
988 );
989 pub(crate) const RGBAF16: Self = Self::new(
990 ChannelType::F16,
991 ChannelLayout::Rgba,
992 Some(AlphaMode::Straight),
993 TransferFunction::Unknown,
994 );
995 pub(crate) const GRAYF16: Self = Self::new(
996 ChannelType::F16,
997 ChannelLayout::Gray,
998 None,
999 TransferFunction::Unknown,
1000 );
1001 pub(crate) const GRAYAF16: Self = Self::new(
1002 ChannelType::F16,
1003 ChannelLayout::GrayAlpha,
1004 Some(AlphaMode::Straight),
1005 TransferFunction::Unknown,
1006 );
1007 pub const BGRA8: Self = Self::new(
1009 ChannelType::U8,
1010 ChannelLayout::Bgra,
1011 Some(AlphaMode::Straight),
1012 TransferFunction::Unknown,
1013 );
1014 pub const RGBX8: Self = Self::new(
1016 ChannelType::U8,
1017 ChannelLayout::Rgba,
1018 Some(AlphaMode::Undefined),
1019 TransferFunction::Unknown,
1020 );
1021 pub const BGRX8: Self = Self::new(
1023 ChannelType::U8,
1024 ChannelLayout::Bgra,
1025 Some(AlphaMode::Undefined),
1026 TransferFunction::Unknown,
1027 );
1028
1029 pub const OKLABF32: Self = Self {
1033 format: PixelFormat::OklabF32,
1034 transfer: TransferFunction::Unknown,
1035 alpha: None,
1036 primaries: ColorPrimaries::Bt709,
1037 signal_range: SignalRange::Full,
1038 };
1039 pub const OKLABAF32: Self = Self {
1041 format: PixelFormat::OklabaF32,
1042 transfer: TransferFunction::Unknown,
1043 alpha: Some(AlphaMode::Straight),
1044 primaries: ColorPrimaries::Bt709,
1045 signal_range: SignalRange::Full,
1046 };
1047
1048 pub const CMYK8: Self = Self::from_pixel_format(PixelFormat::Cmyk8);
1052
1053 #[inline]
1057 pub const fn channels(self) -> usize {
1058 self.format.channels()
1059 }
1060
1061 #[inline]
1063 pub const fn bytes_per_pixel(self) -> usize {
1064 self.format.bytes_per_pixel()
1065 }
1066
1067 #[inline]
1074 pub const fn bytes_per_channel(self) -> usize {
1075 self.channel_type().byte_size()
1076 }
1077
1078 #[inline]
1080 pub const fn has_alpha(self) -> bool {
1081 matches!(
1082 self.alpha,
1083 Some(AlphaMode::Straight) | Some(AlphaMode::Premultiplied) | Some(AlphaMode::Opaque)
1084 )
1085 }
1086
1087 #[inline]
1089 pub const fn is_grayscale(self) -> bool {
1090 self.format.is_grayscale()
1091 }
1092
1093 #[inline]
1095 pub const fn is_bgr(self) -> bool {
1096 matches!(self.format.byte_order(), ByteOrder::Bgr)
1097 }
1098
1099 #[inline]
1114 #[must_use]
1115 pub const fn with_transfer(self, transfer: TransferFunction) -> Self {
1116 Self { transfer, ..self }
1117 }
1118
1119 #[inline]
1133 #[must_use]
1134 pub const fn with_primaries(self, primaries: ColorPrimaries) -> Self {
1135 Self { primaries, ..self }
1136 }
1137
1138 #[inline]
1154 #[must_use]
1155 pub const fn with_alpha(self, alpha: Option<AlphaMode>) -> Self {
1156 Self { alpha, ..self }
1157 }
1158
1159 #[inline]
1161 #[must_use]
1162 pub const fn with_alpha_mode(self, alpha: Option<AlphaMode>) -> Self {
1163 self.with_alpha(alpha)
1164 }
1165
1166 #[inline]
1168 #[must_use]
1169 pub const fn with_signal_range(self, signal_range: SignalRange) -> Self {
1170 Self {
1171 signal_range,
1172 ..self
1173 }
1174 }
1175
1176 #[inline]
1181 pub const fn is_opaque(self) -> bool {
1182 matches!(
1183 self.alpha,
1184 None | Some(AlphaMode::Undefined | AlphaMode::Opaque)
1185 )
1186 }
1187
1188 #[inline]
1193 #[allow(unreachable_patterns)]
1194 pub const fn may_have_transparency(self) -> bool {
1195 matches!(
1196 self.alpha,
1197 Some(AlphaMode::Straight | AlphaMode::Premultiplied)
1198 )
1199 }
1200
1201 #[inline]
1203 pub const fn is_linear(self) -> bool {
1204 matches!(self.transfer, TransferFunction::Linear)
1205 }
1206
1207 #[inline]
1209 pub const fn is_unknown_transfer(self) -> bool {
1210 matches!(self.transfer, TransferFunction::Unknown)
1211 }
1212
1213 #[inline]
1215 pub const fn min_alignment(self) -> usize {
1216 self.format.channel_type().byte_size()
1217 }
1218
1219 #[inline]
1221 pub const fn aligned_stride(self, width: u32) -> usize {
1222 width as usize * self.bytes_per_pixel()
1223 }
1224
1225 #[inline]
1231 pub const fn simd_aligned_stride(self, width: u32, simd_align: usize) -> usize {
1232 let bpp = self.bytes_per_pixel();
1233 let raw = width as usize * bpp;
1234 let align = lcm(bpp, simd_align);
1235 align_up_general(raw, align)
1236 }
1237
1238 #[inline]
1243 pub const fn layout_compatible(self, other: Self) -> bool {
1244 self.format.channel_type() as u8 == other.format.channel_type() as u8
1245 && self.layout() as u8 == other.layout() as u8
1246 }
1247}
1248
1249const fn gcd(mut a: usize, mut b: usize) -> usize {
1252 while b != 0 {
1253 let t = b;
1254 b = a % b;
1255 a = t;
1256 }
1257 a
1258}
1259
1260const fn lcm(a: usize, b: usize) -> usize {
1261 if a == 0 || b == 0 {
1262 0
1263 } else {
1264 a / gcd(a, b) * b
1265 }
1266}
1267
1268const fn align_up_general(value: usize, align: usize) -> usize {
1269 if align == 0 {
1270 return value;
1271 }
1272 let rem = value % align;
1273 if rem == 0 { value } else { value + align - rem }
1274}
1275
1276impl fmt::Display for PixelDescriptor {
1277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1278 write!(
1279 f,
1280 "{} {} {}",
1281 self.format,
1282 self.format.channel_type(),
1283 self.transfer
1284 )?;
1285 if let Some(alpha) = self.alpha {
1286 if alpha.has_alpha() {
1287 write!(f, " alpha={alpha}")?;
1288 }
1289 }
1290 Ok(())
1291 }
1292}
1293
1294#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1300#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1301#[non_exhaustive]
1302#[repr(u8)]
1303pub enum ColorModel {
1304 Gray = 0,
1306 Rgb = 1,
1308 YCbCr = 2,
1310 Oklab = 3,
1312 Cmyk = 4,
1316}
1317
1318impl ColorModel {
1319 #[inline]
1321 #[allow(unreachable_patterns)]
1322 pub const fn color_channels(self) -> u8 {
1323 match self {
1324 Self::Gray => 1,
1325 Self::Cmyk => 4,
1326 _ => 3,
1327 }
1328 }
1329}
1330
1331impl fmt::Display for ColorModel {
1332 #[allow(unreachable_patterns)]
1333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1334 match self {
1335 Self::Gray => f.write_str("Gray"),
1336 Self::Rgb => f.write_str("RGB"),
1337 Self::YCbCr => f.write_str("YCbCr"),
1338 Self::Oklab => f.write_str("Oklab"),
1339 Self::Cmyk => f.write_str("CMYK"),
1340 _ => write!(f, "ColorModel({})", *self as u8),
1341 }
1342 }
1343}
1344
1345#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
1351#[non_exhaustive]
1352#[repr(u8)]
1353pub enum ByteOrder {
1354 #[default]
1356 Native = 0,
1357 Bgr = 1,
1359}
1360
1361impl fmt::Display for ByteOrder {
1362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1363 match self {
1364 Self::Native => f.write_str("native"),
1365 Self::Bgr => f.write_str("BGR"),
1366 }
1367 }
1368}
1369
1370#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1384#[non_exhaustive]
1385#[repr(u8)]
1386pub enum PixelFormat {
1387 Rgb8 = 1,
1388 Rgba8 = 2,
1389 Rgb16 = 3,
1390 Rgba16 = 4,
1391 RgbF32 = 5,
1392 RgbaF32 = 6,
1393 Gray8 = 7,
1394 Gray16 = 8,
1395 GrayF32 = 9,
1396 GrayA8 = 10,
1397 GrayA16 = 11,
1398 GrayAF32 = 12,
1399 Bgra8 = 13,
1400 Rgbx8 = 14,
1401 Bgrx8 = 15,
1402 OklabF32 = 16,
1403 OklabaF32 = 17,
1404 Cmyk8 = 18,
1406 RgbF16 = 19,
1408 RgbaF16 = 20,
1410 GrayF16 = 21,
1412 GrayAF16 = 22,
1414}
1415
1416impl PixelFormat {
1417 #[inline]
1419 #[allow(unreachable_patterns)]
1420 pub const fn channel_type(self) -> ChannelType {
1421 match self {
1422 Self::Rgb8
1423 | Self::Rgba8
1424 | Self::Gray8
1425 | Self::GrayA8
1426 | Self::Bgra8
1427 | Self::Rgbx8
1428 | Self::Bgrx8
1429 | Self::Cmyk8 => ChannelType::U8,
1430 Self::Rgb16 | Self::Rgba16 | Self::Gray16 | Self::GrayA16 => ChannelType::U16,
1431 Self::RgbF32
1432 | Self::RgbaF32
1433 | Self::GrayF32
1434 | Self::GrayAF32
1435 | Self::OklabF32
1436 | Self::OklabaF32 => ChannelType::F32,
1437 Self::RgbF16 | Self::RgbaF16 | Self::GrayF16 | Self::GrayAF16 => ChannelType::F16,
1438 _ => ChannelType::U8,
1439 }
1440 }
1441
1442 #[inline]
1444 #[allow(unreachable_patterns)]
1445 pub const fn layout(self) -> ChannelLayout {
1446 match self {
1447 Self::Rgb8 | Self::Rgb16 | Self::RgbF32 | Self::RgbF16 => ChannelLayout::Rgb,
1448 Self::Rgba8 | Self::Rgba16 | Self::RgbaF32 | Self::RgbaF16 | Self::Rgbx8 => {
1449 ChannelLayout::Rgba
1450 }
1451 Self::Gray8 | Self::Gray16 | Self::GrayF32 | Self::GrayF16 => ChannelLayout::Gray,
1452 Self::GrayA8 | Self::GrayA16 | Self::GrayAF32 | Self::GrayAF16 => {
1453 ChannelLayout::GrayAlpha
1454 }
1455 Self::Bgra8 | Self::Bgrx8 => ChannelLayout::Bgra,
1456 Self::OklabF32 => ChannelLayout::Oklab,
1457 Self::OklabaF32 => ChannelLayout::OklabA,
1458 Self::Cmyk8 => ChannelLayout::Cmyk,
1459 _ => ChannelLayout::Rgb,
1460 }
1461 }
1462
1463 #[inline]
1465 #[allow(unreachable_patterns)]
1466 pub const fn color_model(self) -> ColorModel {
1467 match self {
1468 Self::Gray8
1469 | Self::Gray16
1470 | Self::GrayF32
1471 | Self::GrayF16
1472 | Self::GrayA8
1473 | Self::GrayA16
1474 | Self::GrayAF32
1475 | Self::GrayAF16 => ColorModel::Gray,
1476 Self::OklabF32 | Self::OklabaF32 => ColorModel::Oklab,
1477 Self::Cmyk8 => ColorModel::Cmyk,
1478 _ => ColorModel::Rgb,
1479 }
1480 }
1481
1482 #[inline]
1484 #[allow(unreachable_patterns)]
1485 pub const fn byte_order(self) -> ByteOrder {
1486 match self {
1487 Self::Bgra8 | Self::Bgrx8 => ByteOrder::Bgr,
1488 _ => ByteOrder::Native,
1489 }
1490 }
1491
1492 #[inline]
1494 pub const fn channels(self) -> usize {
1495 self.layout().channels()
1496 }
1497
1498 #[inline]
1500 pub const fn bytes_per_pixel(self) -> usize {
1501 self.channels() * self.channel_type().byte_size()
1502 }
1503
1504 #[inline]
1506 pub const fn has_alpha_bytes(self) -> bool {
1507 self.layout().has_alpha()
1508 }
1509
1510 #[inline]
1512 pub const fn is_grayscale(self) -> bool {
1513 matches!(self.color_model(), ColorModel::Gray)
1514 }
1515
1516 #[allow(unreachable_patterns)]
1522 #[inline]
1523 pub const fn default_alpha(self) -> Option<AlphaMode> {
1524 match self {
1525 Self::Rgb8
1526 | Self::Rgb16
1527 | Self::RgbF32
1528 | Self::RgbF16
1529 | Self::Gray8
1530 | Self::Gray16
1531 | Self::GrayF32
1532 | Self::GrayF16
1533 | Self::OklabF32
1534 | Self::Cmyk8 => None,
1535 Self::Rgbx8 | Self::Bgrx8 => Some(AlphaMode::Undefined),
1536 _ => Some(AlphaMode::Straight),
1537 }
1538 }
1539
1540 #[allow(unreachable_patterns)]
1542 #[inline]
1543 pub const fn name(self) -> &'static str {
1544 match self {
1545 Self::Rgb8 => "RGB8",
1546 Self::Rgba8 => "RGBA8",
1547 Self::Rgb16 => "RGB16",
1548 Self::Rgba16 => "RGBA16",
1549 Self::RgbF32 => "RgbF32",
1550 Self::RgbaF32 => "RgbaF32",
1551 Self::Gray8 => "Gray8",
1552 Self::Gray16 => "Gray16",
1553 Self::GrayF32 => "GrayF32",
1554 Self::GrayA8 => "GrayA8",
1555 Self::GrayA16 => "GrayA16",
1556 Self::GrayAF32 => "GrayAF32",
1557 Self::Bgra8 => "BGRA8",
1558 Self::Rgbx8 => "RGBX8",
1559 Self::Bgrx8 => "BGRX8",
1560 Self::OklabF32 => "OklabF32",
1561 Self::OklabaF32 => "OklabaF32",
1562 Self::Cmyk8 => "CMYK8",
1563 Self::RgbF16 => "RgbF16",
1564 Self::RgbaF16 => "RgbaF16",
1565 Self::GrayF16 => "GrayF16",
1566 Self::GrayAF16 => "GrayAF16",
1567 _ => "Unknown",
1568 }
1569 }
1570
1571 #[inline]
1576 pub const fn from_parts(
1577 channel_type: ChannelType,
1578 layout: ChannelLayout,
1579 alpha: Option<AlphaMode>,
1580 ) -> Option<Self> {
1581 let is_padding = matches!(alpha, Some(AlphaMode::Undefined));
1582 match (channel_type, layout, is_padding) {
1583 (ChannelType::U8, ChannelLayout::Rgb, _) => Some(Self::Rgb8),
1584 (ChannelType::U16, ChannelLayout::Rgb, _) => Some(Self::Rgb16),
1585 (ChannelType::F32, ChannelLayout::Rgb, _) => Some(Self::RgbF32),
1586 (ChannelType::F16, ChannelLayout::Rgb, _) => Some(Self::RgbF16),
1587
1588 (ChannelType::U8, ChannelLayout::Rgba, true) => Some(Self::Rgbx8),
1589 (ChannelType::U8, ChannelLayout::Rgba, false) => Some(Self::Rgba8),
1590 (ChannelType::U16, ChannelLayout::Rgba, _) => Some(Self::Rgba16),
1591 (ChannelType::F32, ChannelLayout::Rgba, _) => Some(Self::RgbaF32),
1592 (ChannelType::F16, ChannelLayout::Rgba, _) => Some(Self::RgbaF16),
1593
1594 (ChannelType::U8, ChannelLayout::Gray, _) => Some(Self::Gray8),
1595 (ChannelType::U16, ChannelLayout::Gray, _) => Some(Self::Gray16),
1596 (ChannelType::F32, ChannelLayout::Gray, _) => Some(Self::GrayF32),
1597 (ChannelType::F16, ChannelLayout::Gray, _) => Some(Self::GrayF16),
1598
1599 (ChannelType::U8, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA8),
1600 (ChannelType::U16, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA16),
1601 (ChannelType::F32, ChannelLayout::GrayAlpha, _) => Some(Self::GrayAF32),
1602 (ChannelType::F16, ChannelLayout::GrayAlpha, _) => Some(Self::GrayAF16),
1603
1604 (ChannelType::U8, ChannelLayout::Bgra, true) => Some(Self::Bgrx8),
1605 (ChannelType::U8, ChannelLayout::Bgra, false) => Some(Self::Bgra8),
1606
1607 (ChannelType::F32, ChannelLayout::Oklab, _) => Some(Self::OklabF32),
1608 (ChannelType::F32, ChannelLayout::OklabA, _) => Some(Self::OklabaF32),
1609
1610 (ChannelType::U8, ChannelLayout::Cmyk, _) => Some(Self::Cmyk8),
1611
1612 _ => None,
1613 }
1614 }
1615
1616 #[allow(unreachable_patterns)]
1618 #[inline]
1619 pub const fn descriptor(self) -> PixelDescriptor {
1620 match self {
1621 Self::Rgb8 => PixelDescriptor::RGB8,
1622 Self::Rgba8 => PixelDescriptor::RGBA8,
1623 Self::Rgb16 => PixelDescriptor::RGB16,
1624 Self::Rgba16 => PixelDescriptor::RGBA16,
1625 Self::RgbF32 => PixelDescriptor::RGBF32,
1626 Self::RgbaF32 => PixelDescriptor::RGBAF32,
1627 Self::Gray8 => PixelDescriptor::GRAY8,
1628 Self::Gray16 => PixelDescriptor::GRAY16,
1629 Self::GrayF32 => PixelDescriptor::GRAYF32,
1630 Self::GrayA8 => PixelDescriptor::GRAYA8,
1631 Self::GrayA16 => PixelDescriptor::GRAYA16,
1632 Self::GrayAF32 => PixelDescriptor::GRAYAF32,
1633 Self::Bgra8 => PixelDescriptor::BGRA8,
1634 Self::Rgbx8 => PixelDescriptor::RGBX8,
1635 Self::Bgrx8 => PixelDescriptor::BGRX8,
1636 Self::OklabF32 => PixelDescriptor::OKLABF32,
1637 Self::OklabaF32 => PixelDescriptor::OKLABAF32,
1638 Self::Cmyk8 => PixelDescriptor::CMYK8,
1639 Self::RgbF16 => PixelDescriptor::RGBF16,
1640 Self::RgbaF16 => PixelDescriptor::RGBAF16,
1641 Self::GrayF16 => PixelDescriptor::GRAYF16,
1642 Self::GrayAF16 => PixelDescriptor::GRAYAF16,
1643 _ => PixelDescriptor::RGB8,
1644 }
1645 }
1646}
1647
1648impl fmt::Display for PixelFormat {
1649 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1650 f.write_str(self.name())
1651 }
1652}
1653
1654#[cfg(test)]
1659mod tests {
1660 use alloc::format;
1661 use core::mem::size_of;
1662
1663 use super::*;
1664
1665 #[test]
1666 fn channel_type_byte_size() {
1667 assert_eq!(ChannelType::U8.byte_size(), 1);
1668 assert_eq!(ChannelType::U16.byte_size(), 2);
1669 assert_eq!(ChannelType::F16.byte_size(), 2);
1670 assert_eq!(ChannelType::F32.byte_size(), 4);
1671 }
1672
1673 #[test]
1674 fn descriptor_bytes_per_pixel() {
1675 assert_eq!(PixelDescriptor::RGB8.bytes_per_pixel(), 3);
1676 assert_eq!(PixelDescriptor::RGBA8.bytes_per_pixel(), 4);
1677 assert_eq!(PixelDescriptor::GRAY8.bytes_per_pixel(), 1);
1678 assert_eq!(PixelDescriptor::RGBAF32.bytes_per_pixel(), 16);
1679 assert_eq!(PixelDescriptor::GRAYA8.bytes_per_pixel(), 2);
1680 }
1681
1682 #[test]
1683 fn descriptor_bytes_per_channel() {
1684 assert_eq!(PixelDescriptor::RGB8.bytes_per_channel(), 1);
1685 assert_eq!(PixelDescriptor::RGB16_SRGB.bytes_per_channel(), 2);
1686 assert_eq!(PixelDescriptor::RGBAF32.bytes_per_channel(), 4);
1687 assert_ne!(
1690 PixelDescriptor::RGBA8.bytes_per_channel(),
1691 PixelDescriptor::RGBA8.bytes_per_pixel()
1692 );
1693 }
1694
1695 #[test]
1696 fn bt2100_presets_carry_hdr_signaling() {
1697 let pq = PixelDescriptor::RGB16_BT2100_PQ;
1698 assert_eq!(pq.format, PixelFormat::Rgb16);
1699 assert_eq!(pq.transfer, TransferFunction::Pq);
1700 assert_eq!(pq.primaries, ColorPrimaries::Bt2020);
1701 assert_eq!(pq.signal_range, SignalRange::Full);
1702
1703 let hlg = PixelDescriptor::RGB16_BT2100_HLG;
1704 assert_eq!(hlg.format, PixelFormat::Rgb16);
1705 assert_eq!(hlg.transfer, TransferFunction::Hlg);
1706 assert_eq!(hlg.primaries, ColorPrimaries::Bt2020);
1707 assert_eq!(hlg.signal_range, SignalRange::Full);
1708
1709 assert_eq!(
1711 pq,
1712 PixelDescriptor::new_full(
1713 ChannelType::U16,
1714 ChannelLayout::Rgb,
1715 None,
1716 TransferFunction::Pq,
1717 ColorPrimaries::Bt2020,
1718 )
1719 );
1720 }
1721
1722 #[test]
1723 fn descriptor_has_alpha() {
1724 assert!(!PixelDescriptor::RGB8.has_alpha());
1725 assert!(PixelDescriptor::RGBA8.has_alpha());
1726 assert!(!PixelDescriptor::RGBX8.has_alpha());
1727 assert!(PixelDescriptor::GRAYA8.has_alpha());
1728 }
1729
1730 #[test]
1731 fn descriptor_is_grayscale() {
1732 assert!(PixelDescriptor::GRAY8.is_grayscale());
1733 assert!(PixelDescriptor::GRAYA8.is_grayscale());
1734 assert!(!PixelDescriptor::RGB8.is_grayscale());
1735 }
1736
1737 #[test]
1738 fn layout_compatible() {
1739 assert!(PixelDescriptor::RGB8_SRGB.layout_compatible(PixelDescriptor::RGB8));
1740 assert!(!PixelDescriptor::RGB8.layout_compatible(PixelDescriptor::RGBA8));
1741 }
1742
1743 #[test]
1744 fn pixel_format_descriptor_roundtrip() {
1745 let desc = PixelFormat::Rgba8.descriptor();
1746 assert_eq!(desc.layout(), ChannelLayout::Rgba);
1747 assert_eq!(desc.channel_type(), ChannelType::U8);
1748 }
1749
1750 #[test]
1751 fn pixel_format_enum_basics() {
1752 assert_eq!(PixelFormat::Rgb8.channels(), 3);
1753 assert_eq!(PixelFormat::Rgba8.channels(), 4);
1754 assert!(PixelFormat::Rgba8.has_alpha_bytes());
1755 assert!(!PixelFormat::Rgb8.has_alpha_bytes());
1756 assert_eq!(PixelFormat::RgbF32.bytes_per_pixel(), 12);
1757 assert_eq!(PixelFormat::RgbaF32.bytes_per_pixel(), 16);
1758 assert_eq!(PixelFormat::Gray8.channels(), 1);
1759 assert!(PixelFormat::Gray8.is_grayscale());
1760 assert!(!PixelFormat::Rgb8.is_grayscale());
1761 assert_eq!(PixelFormat::Bgra8.byte_order(), ByteOrder::Bgr);
1762 assert_eq!(PixelFormat::Rgb8.byte_order(), ByteOrder::Native);
1763 }
1764
1765 #[test]
1766 fn pixel_format_enum_size() {
1767 assert!(size_of::<PixelFormat>() <= 2);
1769 }
1770
1771 #[test]
1772 fn pixel_format_from_parts_roundtrip() {
1773 let fmt = PixelFormat::Rgba8;
1774 let rebuilt =
1775 PixelFormat::from_parts(fmt.channel_type(), fmt.layout(), fmt.default_alpha());
1776 assert_eq!(rebuilt, Some(fmt));
1777
1778 let fmt2 = PixelFormat::Bgra8;
1779 let rebuilt2 =
1780 PixelFormat::from_parts(fmt2.channel_type(), fmt2.layout(), fmt2.default_alpha());
1781 assert_eq!(rebuilt2, Some(fmt2));
1782
1783 let fmt3 = PixelFormat::Gray8;
1784 let rebuilt3 =
1785 PixelFormat::from_parts(fmt3.channel_type(), fmt3.layout(), fmt3.default_alpha());
1786 assert_eq!(rebuilt3, Some(fmt3));
1787 }
1788
1789 #[test]
1790 fn alpha_mode_semantics() {
1791 assert!(!PixelDescriptor::RGB8.has_alpha());
1793 assert!(!AlphaMode::Undefined.has_alpha());
1795 assert!(AlphaMode::Straight.has_alpha());
1797 assert!(AlphaMode::Premultiplied.has_alpha());
1798 assert!(AlphaMode::Opaque.has_alpha());
1799 }
1800
1801 #[test]
1802 fn color_primaries_containment() {
1803 assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::DisplayP3));
1804 assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::Bt709));
1805 assert!(ColorPrimaries::DisplayP3.contains(ColorPrimaries::Bt709));
1806 assert!(!ColorPrimaries::Bt709.contains(ColorPrimaries::DisplayP3));
1807 assert!(!ColorPrimaries::Unknown.contains(ColorPrimaries::Bt709));
1808 }
1809
1810 #[test]
1811 fn descriptor_size() {
1812 assert!(size_of::<PixelDescriptor>() <= 8);
1814 }
1815
1816 #[test]
1817 fn color_model_channels() {
1818 assert_eq!(ColorModel::Gray.color_channels(), 1);
1819 assert_eq!(ColorModel::Rgb.color_channels(), 3);
1820 assert_eq!(ColorModel::YCbCr.color_channels(), 3);
1821 assert_eq!(ColorModel::Oklab.color_channels(), 3);
1822 assert_eq!(ColorModel::Cmyk.color_channels(), 4);
1823 }
1824
1825 #[test]
1838 fn reference_white_nits_values() {
1839 assert_eq!(TransferFunction::Pq.reference_white_nits(), 203.0);
1840 assert_eq!(TransferFunction::Srgb.reference_white_nits(), 1.0);
1841 assert_eq!(TransferFunction::Linear.reference_white_nits(), 1.0);
1842 assert_eq!(TransferFunction::Unknown.reference_white_nits(), 1.0);
1843 }
1844
1845 #[test]
1850 fn channel_type_display() {
1851 assert_eq!(format!("{}", ChannelType::U8), "U8");
1852 assert_eq!(format!("{}", ChannelType::U16), "U16");
1853 assert_eq!(format!("{}", ChannelType::F32), "F32");
1854 assert_eq!(format!("{}", ChannelType::F16), "F16");
1855 }
1856
1857 #[test]
1858 fn channel_layout_display() {
1859 assert_eq!(format!("{}", ChannelLayout::Gray), "Gray");
1860 assert_eq!(format!("{}", ChannelLayout::GrayAlpha), "GrayAlpha");
1861 assert_eq!(format!("{}", ChannelLayout::Rgb), "RGB");
1862 assert_eq!(format!("{}", ChannelLayout::Rgba), "RGBA");
1863 assert_eq!(format!("{}", ChannelLayout::Bgra), "BGRA");
1864 assert_eq!(format!("{}", ChannelLayout::Oklab), "Oklab");
1865 assert_eq!(format!("{}", ChannelLayout::OklabA), "OklabA");
1866 }
1867
1868 #[test]
1869 fn alpha_mode_display() {
1870 assert_eq!(format!("{}", AlphaMode::Undefined), "undefined");
1871 assert_eq!(format!("{}", AlphaMode::Straight), "straight");
1872 assert_eq!(format!("{}", AlphaMode::Premultiplied), "premultiplied");
1873 assert_eq!(format!("{}", AlphaMode::Opaque), "opaque");
1874 }
1875
1876 #[test]
1877 fn transfer_function_display() {
1878 assert_eq!(format!("{}", TransferFunction::Linear), "linear");
1879 assert_eq!(format!("{}", TransferFunction::Srgb), "sRGB");
1880 assert_eq!(format!("{}", TransferFunction::Bt709), "BT.709");
1881 assert_eq!(format!("{}", TransferFunction::Pq), "PQ");
1882 assert_eq!(format!("{}", TransferFunction::Unknown), "unknown");
1883 }
1884
1885 #[test]
1886 fn color_primaries_display() {
1887 assert_eq!(format!("{}", ColorPrimaries::Bt709), "BT.709");
1888 assert_eq!(format!("{}", ColorPrimaries::Bt2020), "BT.2020");
1889 assert_eq!(format!("{}", ColorPrimaries::DisplayP3), "Display P3");
1890 assert_eq!(format!("{}", ColorPrimaries::Unknown), "unknown");
1891 }
1892
1893 #[test]
1894 fn signal_range_display() {
1895 assert_eq!(format!("{}", SignalRange::Full), "full");
1896 assert_eq!(format!("{}", SignalRange::Narrow), "narrow");
1897 }
1898
1899 #[test]
1900 fn pixel_descriptor_display() {
1901 let s = format!("{}", PixelDescriptor::RGB8_SRGB);
1902 assert!(s.contains("U8"), "expected U8 in: {s}");
1903 assert!(s.contains("sRGB"), "expected sRGB in: {s}");
1904
1905 let s = format!("{}", PixelDescriptor::RGBA8_SRGB);
1906 assert!(s.contains("alpha=straight"), "expected alpha in: {s}");
1907 }
1908
1909 #[test]
1910 fn pixel_format_display() {
1911 let s = format!("{}", PixelFormat::Rgb8);
1912 assert!(s.contains("RGB8"));
1913 let s = format!("{}", PixelFormat::Bgra8);
1914 assert!(s.contains("BGRA8"));
1915 }
1916
1917 #[test]
1920 fn transfer_function_from_cicp() {
1921 assert_eq!(
1922 TransferFunction::from_cicp(1),
1923 Some(TransferFunction::Bt709)
1924 );
1925 assert_eq!(
1926 TransferFunction::from_cicp(8),
1927 Some(TransferFunction::Linear)
1928 );
1929 assert_eq!(
1930 TransferFunction::from_cicp(13),
1931 Some(TransferFunction::Srgb)
1932 );
1933 assert_eq!(TransferFunction::from_cicp(16), Some(TransferFunction::Pq));
1934 assert_eq!(TransferFunction::from_cicp(18), Some(TransferFunction::Hlg));
1935 assert_eq!(TransferFunction::from_cicp(99), None);
1936 }
1937
1938 #[test]
1939 fn transfer_function_to_cicp() {
1940 assert_eq!(TransferFunction::Bt709.to_cicp(), Some(1));
1941 assert_eq!(TransferFunction::Linear.to_cicp(), Some(8));
1942 assert_eq!(TransferFunction::Srgb.to_cicp(), Some(13));
1943 assert_eq!(TransferFunction::Pq.to_cicp(), Some(16));
1944 assert_eq!(TransferFunction::Hlg.to_cicp(), Some(18));
1945 assert_eq!(TransferFunction::Unknown.to_cicp(), None);
1946 }
1947
1948 #[test]
1949 fn transfer_function_cicp_roundtrip() {
1950 for tf in [
1951 TransferFunction::Bt709,
1952 TransferFunction::Linear,
1953 TransferFunction::Srgb,
1954 TransferFunction::Pq,
1955 ] {
1956 let code = tf.to_cicp().unwrap();
1957 assert_eq!(TransferFunction::from_cicp(code), Some(tf));
1958 }
1959 }
1960
1961 #[test]
1962 fn color_primaries_from_cicp() {
1963 assert_eq!(ColorPrimaries::from_cicp(1), Some(ColorPrimaries::Bt709));
1964 assert_eq!(ColorPrimaries::from_cicp(9), Some(ColorPrimaries::Bt2020));
1965 assert_eq!(
1966 ColorPrimaries::from_cicp(12),
1967 Some(ColorPrimaries::DisplayP3)
1968 );
1969 assert_eq!(ColorPrimaries::from_cicp(99), None);
1970 }
1971
1972 #[test]
1973 fn color_primaries_to_cicp() {
1974 assert_eq!(ColorPrimaries::Bt709.to_cicp(), Some(1));
1975 assert_eq!(ColorPrimaries::Bt2020.to_cicp(), Some(9));
1976 assert_eq!(ColorPrimaries::DisplayP3.to_cicp(), Some(12));
1977 assert_eq!(ColorPrimaries::Unknown.to_cicp(), None);
1978 }
1979
1980 #[test]
1983 fn channel_type_helpers() {
1984 assert!(ChannelType::U8.is_u8());
1985 assert!(!ChannelType::U8.is_u16());
1986 assert!(ChannelType::U16.is_u16());
1987 assert!(ChannelType::F32.is_f32());
1988 assert!(ChannelType::F16.is_f16());
1989 assert!(ChannelType::U8.is_integer());
1990 assert!(ChannelType::U16.is_integer());
1991 assert!(!ChannelType::F32.is_integer());
1992 assert!(ChannelType::F32.is_float());
1993 assert!(ChannelType::F16.is_float());
1994 assert!(!ChannelType::U8.is_float());
1995 }
1996
1997 #[test]
2000 fn channel_layout_channels() {
2001 assert_eq!(ChannelLayout::Gray.channels(), 1);
2002 assert_eq!(ChannelLayout::GrayAlpha.channels(), 2);
2003 assert_eq!(ChannelLayout::Rgb.channels(), 3);
2004 assert_eq!(ChannelLayout::Rgba.channels(), 4);
2005 assert_eq!(ChannelLayout::Bgra.channels(), 4);
2006 assert_eq!(ChannelLayout::Oklab.channels(), 3);
2007 assert_eq!(ChannelLayout::OklabA.channels(), 4);
2008 }
2009
2010 #[test]
2011 fn channel_layout_has_alpha() {
2012 assert!(!ChannelLayout::Gray.has_alpha());
2013 assert!(ChannelLayout::GrayAlpha.has_alpha());
2014 assert!(!ChannelLayout::Rgb.has_alpha());
2015 assert!(ChannelLayout::Rgba.has_alpha());
2016 assert!(ChannelLayout::Bgra.has_alpha());
2017 assert!(!ChannelLayout::Oklab.has_alpha());
2018 assert!(ChannelLayout::OklabA.has_alpha());
2019 }
2020
2021 #[test]
2024 fn with_transfer() {
2025 let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Linear);
2026 assert_eq!(desc.transfer(), TransferFunction::Linear);
2027 assert_eq!(desc.layout(), ChannelLayout::Rgb);
2028 }
2029
2030 #[test]
2031 fn with_primaries() {
2032 let desc = PixelDescriptor::RGB8_SRGB.with_primaries(ColorPrimaries::DisplayP3);
2033 assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
2034 }
2035
2036 #[test]
2037 fn with_signal_range() {
2038 let desc = PixelDescriptor::RGB8_SRGB.with_signal_range(SignalRange::Narrow);
2039 assert_eq!(desc.signal_range, SignalRange::Narrow);
2040 }
2041
2042 #[test]
2043 fn with_alpha_mode() {
2044 let desc = PixelDescriptor::RGBA8_SRGB.with_alpha(Some(AlphaMode::Premultiplied));
2045 assert_eq!(desc.alpha(), Some(AlphaMode::Premultiplied));
2046 }
2047
2048 #[test]
2051 fn is_opaque_and_may_have_transparency() {
2052 assert!(PixelDescriptor::RGB8_SRGB.is_opaque());
2053 assert!(!PixelDescriptor::RGB8_SRGB.may_have_transparency());
2054 assert!(!PixelDescriptor::RGBA8_SRGB.is_opaque());
2055 assert!(PixelDescriptor::RGBA8_SRGB.may_have_transparency());
2056
2057 let rgbx = PixelDescriptor::new(
2058 ChannelType::U8,
2059 ChannelLayout::Rgba,
2060 Some(AlphaMode::Undefined),
2061 TransferFunction::Srgb,
2062 );
2063 assert!(rgbx.is_opaque());
2064 assert!(!rgbx.may_have_transparency());
2065 }
2066
2067 #[test]
2068 fn is_linear_and_is_unknown_transfer() {
2069 assert!(!PixelDescriptor::RGB8_SRGB.is_linear());
2070 assert!(PixelDescriptor::RGBF32_LINEAR.is_linear());
2071 assert!(!PixelDescriptor::RGB8_SRGB.is_unknown_transfer());
2072 let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Unknown);
2073 assert!(desc.is_unknown_transfer());
2074 }
2075
2076 #[test]
2077 fn min_alignment() {
2078 assert_eq!(PixelDescriptor::RGB8_SRGB.min_alignment(), 1);
2079 assert_eq!(PixelDescriptor::RGBF32_LINEAR.min_alignment(), 4);
2080 }
2081
2082 #[test]
2083 fn aligned_stride() {
2084 assert_eq!(PixelDescriptor::RGB8_SRGB.aligned_stride(100), 300);
2085 assert_eq!(PixelDescriptor::RGBA8_SRGB.aligned_stride(100), 400);
2086 assert_eq!(PixelDescriptor::RGBF32_LINEAR.aligned_stride(10), 120);
2087 }
2088
2089 #[test]
2090 fn simd_aligned_stride() {
2091 let stride = PixelDescriptor::RGB8_SRGB.simd_aligned_stride(100, 16);
2092 assert!(stride >= 300);
2093 assert_eq!(stride % 16, 0);
2094 assert_eq!(stride % 3, 0); }
2096
2097 #[test]
2100 fn new_full_constructor() {
2101 let desc = PixelDescriptor::new_full(
2102 ChannelType::U8,
2103 ChannelLayout::Rgb,
2104 None,
2105 TransferFunction::Srgb,
2106 ColorPrimaries::DisplayP3,
2107 );
2108 assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
2109 assert_eq!(desc.transfer(), TransferFunction::Srgb);
2110 }
2111
2112 #[test]
2113 fn from_pixel_format_constructor() {
2114 let desc = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8);
2115 assert_eq!(desc.layout(), ChannelLayout::Rgba);
2116 assert_eq!(desc.transfer(), TransferFunction::Unknown);
2117 assert_eq!(desc.primaries, ColorPrimaries::Bt709);
2118 assert_eq!(desc.signal_range, SignalRange::Full);
2119 }
2120
2121 #[test]
2124 fn pixel_format_name() {
2125 assert_eq!(PixelFormat::Rgb8.name(), "RGB8");
2126 assert_eq!(PixelFormat::Bgra8.name(), "BGRA8");
2127 assert_eq!(PixelFormat::Gray8.name(), "Gray8");
2128 }
2129
2130 #[test]
2133 fn color_model_display() {
2134 assert_eq!(format!("{}", ColorModel::Gray), "Gray");
2135 assert_eq!(format!("{}", ColorModel::Rgb), "RGB");
2136 assert_eq!(format!("{}", ColorModel::YCbCr), "YCbCr");
2137 assert_eq!(format!("{}", ColorModel::Oklab), "Oklab");
2138 assert_eq!(format!("{}", ColorModel::Cmyk), "CMYK");
2139 }
2140
2141 #[test]
2144 fn signal_range_default() {
2145 assert_eq!(SignalRange::default(), SignalRange::Full);
2146 }
2147
2148 #[test]
2151 fn color_primaries_default() {
2152 assert_eq!(ColorPrimaries::default(), ColorPrimaries::Bt709);
2153 }
2154
2155 #[test]
2158 fn cmyk8_descriptor() {
2159 let d = PixelDescriptor::CMYK8;
2160 assert_eq!(d.color_model(), ColorModel::Cmyk);
2161 assert_eq!(d.channels(), 4);
2162 assert_eq!(d.bytes_per_pixel(), 4);
2163 assert_eq!(d.layout(), ChannelLayout::Cmyk);
2164 assert_eq!(d.channel_type(), ChannelType::U8);
2165 assert_eq!(d.transfer(), TransferFunction::Unknown);
2166 assert_eq!(d.primaries, ColorPrimaries::Bt709);
2167 assert!(!d.has_alpha());
2168 assert!(d.is_opaque());
2169 }
2170
2171 #[test]
2172 fn cmyk8_pixel_format() {
2173 let fmt = PixelFormat::Cmyk8;
2174 assert_eq!(fmt.channels(), 4);
2175 assert_eq!(fmt.bytes_per_pixel(), 4);
2176 assert_eq!(fmt.channel_type(), ChannelType::U8);
2177 assert_eq!(fmt.layout(), ChannelLayout::Cmyk);
2178 assert_eq!(fmt.color_model(), ColorModel::Cmyk);
2179 assert!(!fmt.has_alpha_bytes());
2180 assert!(!fmt.is_grayscale());
2181 assert_eq!(fmt.name(), "CMYK8");
2182 assert_eq!(fmt.default_alpha(), None);
2183 }
2184
2185 #[test]
2186 fn cmyk8_from_parts_roundtrip() {
2187 let fmt = PixelFormat::Cmyk8;
2188 let rebuilt =
2189 PixelFormat::from_parts(fmt.channel_type(), fmt.layout(), fmt.default_alpha());
2190 assert_eq!(rebuilt, Some(fmt));
2191 }
2192
2193 #[test]
2194 fn cmyk8_descriptor_roundtrip() {
2195 let desc = PixelFormat::Cmyk8.descriptor();
2196 assert_eq!(desc, PixelDescriptor::CMYK8);
2197 }
2198
2199 #[test]
2200 fn cmyk_channel_layout_display() {
2201 assert_eq!(format!("{}", ChannelLayout::Cmyk), "CMYK");
2202 }
2203
2204 #[test]
2205 fn cmyk_channel_layout_no_alpha() {
2206 assert!(!ChannelLayout::Cmyk.has_alpha());
2207 }
2208}