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]
679 pub const fn new(
680 channel_type: ChannelType,
681 layout: ChannelLayout,
682 alpha: Option<AlphaMode>,
683 transfer: TransferFunction,
684 ) -> Self {
685 let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
686 Some(f) => f,
687 None => panic!("unsupported PixelFormat combination"),
688 };
689 Self {
690 format,
691 transfer,
692 alpha,
693 primaries: ColorPrimaries::Bt709,
694 signal_range: SignalRange::Full,
695 }
696 }
697
698 #[inline]
705 pub const fn new_full(
706 channel_type: ChannelType,
707 layout: ChannelLayout,
708 alpha: Option<AlphaMode>,
709 transfer: TransferFunction,
710 primaries: ColorPrimaries,
711 ) -> Self {
712 let format = match PixelFormat::from_parts(channel_type, layout, alpha) {
713 Some(f) => f,
714 None => panic!("unsupported PixelFormat combination"),
715 };
716 Self {
717 format,
718 transfer,
719 alpha,
720 primaries,
721 signal_range: SignalRange::Full,
722 }
723 }
724
725 #[inline]
728 pub const fn from_pixel_format(format: PixelFormat) -> Self {
729 Self {
730 format,
731 transfer: TransferFunction::Unknown,
732 alpha: format.default_alpha(),
733 primaries: ColorPrimaries::Bt709,
734 signal_range: SignalRange::Full,
735 }
736 }
737
738 pub const RGB8_SRGB: Self = Self::new(
742 ChannelType::U8,
743 ChannelLayout::Rgb,
744 None,
745 TransferFunction::Srgb,
746 );
747 pub const RGBA8_SRGB: Self = Self::new(
749 ChannelType::U8,
750 ChannelLayout::Rgba,
751 Some(AlphaMode::Straight),
752 TransferFunction::Srgb,
753 );
754 pub const RGB16_SRGB: Self = Self::new(
756 ChannelType::U16,
757 ChannelLayout::Rgb,
758 None,
759 TransferFunction::Srgb,
760 );
761 pub const RGBA16_SRGB: Self = Self::new(
763 ChannelType::U16,
764 ChannelLayout::Rgba,
765 Some(AlphaMode::Straight),
766 TransferFunction::Srgb,
767 );
768 pub const RGBF32_LINEAR: Self = Self::new(
770 ChannelType::F32,
771 ChannelLayout::Rgb,
772 None,
773 TransferFunction::Linear,
774 );
775 pub const RGBAF32_LINEAR: Self = Self::new(
777 ChannelType::F32,
778 ChannelLayout::Rgba,
779 Some(AlphaMode::Straight),
780 TransferFunction::Linear,
781 );
782 pub const GRAY8_SRGB: Self = Self::new(
784 ChannelType::U8,
785 ChannelLayout::Gray,
786 None,
787 TransferFunction::Srgb,
788 );
789 pub const GRAY16_SRGB: Self = Self::new(
791 ChannelType::U16,
792 ChannelLayout::Gray,
793 None,
794 TransferFunction::Srgb,
795 );
796 pub const GRAYF32_LINEAR: Self = Self::new(
798 ChannelType::F32,
799 ChannelLayout::Gray,
800 None,
801 TransferFunction::Linear,
802 );
803 pub const GRAYA8_SRGB: Self = Self::new(
805 ChannelType::U8,
806 ChannelLayout::GrayAlpha,
807 Some(AlphaMode::Straight),
808 TransferFunction::Srgb,
809 );
810 pub const GRAYA16_SRGB: Self = Self::new(
812 ChannelType::U16,
813 ChannelLayout::GrayAlpha,
814 Some(AlphaMode::Straight),
815 TransferFunction::Srgb,
816 );
817 pub const GRAYAF32_LINEAR: Self = Self::new(
819 ChannelType::F32,
820 ChannelLayout::GrayAlpha,
821 Some(AlphaMode::Straight),
822 TransferFunction::Linear,
823 );
824 pub const BGRA8_SRGB: Self = Self::new(
826 ChannelType::U8,
827 ChannelLayout::Bgra,
828 Some(AlphaMode::Straight),
829 TransferFunction::Srgb,
830 );
831 pub const RGBX8_SRGB: Self = Self::new(
833 ChannelType::U8,
834 ChannelLayout::Rgba,
835 Some(AlphaMode::Undefined),
836 TransferFunction::Srgb,
837 );
838 pub const BGRX8_SRGB: Self = Self::new(
840 ChannelType::U8,
841 ChannelLayout::Bgra,
842 Some(AlphaMode::Undefined),
843 TransferFunction::Srgb,
844 );
845
846 pub const RGB8: Self = Self::new(
850 ChannelType::U8,
851 ChannelLayout::Rgb,
852 None,
853 TransferFunction::Unknown,
854 );
855 pub const RGBA8: Self = Self::new(
857 ChannelType::U8,
858 ChannelLayout::Rgba,
859 Some(AlphaMode::Straight),
860 TransferFunction::Unknown,
861 );
862 pub const RGB16: Self = Self::new(
864 ChannelType::U16,
865 ChannelLayout::Rgb,
866 None,
867 TransferFunction::Unknown,
868 );
869 pub const RGBA16: Self = Self::new(
871 ChannelType::U16,
872 ChannelLayout::Rgba,
873 Some(AlphaMode::Straight),
874 TransferFunction::Unknown,
875 );
876 pub const RGBF32: Self = Self::new(
878 ChannelType::F32,
879 ChannelLayout::Rgb,
880 None,
881 TransferFunction::Unknown,
882 );
883 pub const RGBAF32: Self = Self::new(
885 ChannelType::F32,
886 ChannelLayout::Rgba,
887 Some(AlphaMode::Straight),
888 TransferFunction::Unknown,
889 );
890 pub const GRAY8: Self = Self::new(
892 ChannelType::U8,
893 ChannelLayout::Gray,
894 None,
895 TransferFunction::Unknown,
896 );
897 pub const GRAY16: Self = Self::new(
899 ChannelType::U16,
900 ChannelLayout::Gray,
901 None,
902 TransferFunction::Unknown,
903 );
904 pub const GRAYF32: Self = Self::new(
906 ChannelType::F32,
907 ChannelLayout::Gray,
908 None,
909 TransferFunction::Unknown,
910 );
911 pub const GRAYA8: Self = Self::new(
913 ChannelType::U8,
914 ChannelLayout::GrayAlpha,
915 Some(AlphaMode::Straight),
916 TransferFunction::Unknown,
917 );
918 pub const GRAYA16: Self = Self::new(
920 ChannelType::U16,
921 ChannelLayout::GrayAlpha,
922 Some(AlphaMode::Straight),
923 TransferFunction::Unknown,
924 );
925 pub const GRAYAF32: Self = Self::new(
927 ChannelType::F32,
928 ChannelLayout::GrayAlpha,
929 Some(AlphaMode::Straight),
930 TransferFunction::Unknown,
931 );
932
933 pub(crate) const RGBF16: Self = Self::new(
939 ChannelType::F16,
940 ChannelLayout::Rgb,
941 None,
942 TransferFunction::Unknown,
943 );
944 pub(crate) const RGBAF16: Self = Self::new(
945 ChannelType::F16,
946 ChannelLayout::Rgba,
947 Some(AlphaMode::Straight),
948 TransferFunction::Unknown,
949 );
950 pub(crate) const GRAYF16: Self = Self::new(
951 ChannelType::F16,
952 ChannelLayout::Gray,
953 None,
954 TransferFunction::Unknown,
955 );
956 pub(crate) const GRAYAF16: Self = Self::new(
957 ChannelType::F16,
958 ChannelLayout::GrayAlpha,
959 Some(AlphaMode::Straight),
960 TransferFunction::Unknown,
961 );
962 pub const BGRA8: Self = Self::new(
964 ChannelType::U8,
965 ChannelLayout::Bgra,
966 Some(AlphaMode::Straight),
967 TransferFunction::Unknown,
968 );
969 pub const RGBX8: Self = Self::new(
971 ChannelType::U8,
972 ChannelLayout::Rgba,
973 Some(AlphaMode::Undefined),
974 TransferFunction::Unknown,
975 );
976 pub const BGRX8: Self = Self::new(
978 ChannelType::U8,
979 ChannelLayout::Bgra,
980 Some(AlphaMode::Undefined),
981 TransferFunction::Unknown,
982 );
983
984 pub const OKLABF32: Self = Self {
988 format: PixelFormat::OklabF32,
989 transfer: TransferFunction::Unknown,
990 alpha: None,
991 primaries: ColorPrimaries::Bt709,
992 signal_range: SignalRange::Full,
993 };
994 pub const OKLABAF32: Self = Self {
996 format: PixelFormat::OklabaF32,
997 transfer: TransferFunction::Unknown,
998 alpha: Some(AlphaMode::Straight),
999 primaries: ColorPrimaries::Bt709,
1000 signal_range: SignalRange::Full,
1001 };
1002
1003 pub const CMYK8: Self = Self::from_pixel_format(PixelFormat::Cmyk8);
1007
1008 #[inline]
1012 pub const fn channels(self) -> usize {
1013 self.format.channels()
1014 }
1015
1016 #[inline]
1018 pub const fn bytes_per_pixel(self) -> usize {
1019 self.format.bytes_per_pixel()
1020 }
1021
1022 #[inline]
1024 pub const fn has_alpha(self) -> bool {
1025 matches!(
1026 self.alpha,
1027 Some(AlphaMode::Straight) | Some(AlphaMode::Premultiplied) | Some(AlphaMode::Opaque)
1028 )
1029 }
1030
1031 #[inline]
1033 pub const fn is_grayscale(self) -> bool {
1034 self.format.is_grayscale()
1035 }
1036
1037 #[inline]
1039 pub const fn is_bgr(self) -> bool {
1040 matches!(self.format.byte_order(), ByteOrder::Bgr)
1041 }
1042
1043 #[inline]
1058 #[must_use]
1059 pub const fn with_transfer(self, transfer: TransferFunction) -> Self {
1060 Self { transfer, ..self }
1061 }
1062
1063 #[inline]
1077 #[must_use]
1078 pub const fn with_primaries(self, primaries: ColorPrimaries) -> Self {
1079 Self { primaries, ..self }
1080 }
1081
1082 #[inline]
1098 #[must_use]
1099 pub const fn with_alpha(self, alpha: Option<AlphaMode>) -> Self {
1100 Self { alpha, ..self }
1101 }
1102
1103 #[inline]
1105 #[must_use]
1106 pub const fn with_alpha_mode(self, alpha: Option<AlphaMode>) -> Self {
1107 self.with_alpha(alpha)
1108 }
1109
1110 #[inline]
1112 #[must_use]
1113 pub const fn with_signal_range(self, signal_range: SignalRange) -> Self {
1114 Self {
1115 signal_range,
1116 ..self
1117 }
1118 }
1119
1120 #[inline]
1125 pub const fn is_opaque(self) -> bool {
1126 matches!(
1127 self.alpha,
1128 None | Some(AlphaMode::Undefined | AlphaMode::Opaque)
1129 )
1130 }
1131
1132 #[inline]
1137 #[allow(unreachable_patterns)]
1138 pub const fn may_have_transparency(self) -> bool {
1139 matches!(
1140 self.alpha,
1141 Some(AlphaMode::Straight | AlphaMode::Premultiplied)
1142 )
1143 }
1144
1145 #[inline]
1147 pub const fn is_linear(self) -> bool {
1148 matches!(self.transfer, TransferFunction::Linear)
1149 }
1150
1151 #[inline]
1153 pub const fn is_unknown_transfer(self) -> bool {
1154 matches!(self.transfer, TransferFunction::Unknown)
1155 }
1156
1157 #[inline]
1159 pub const fn min_alignment(self) -> usize {
1160 self.format.channel_type().byte_size()
1161 }
1162
1163 #[inline]
1165 pub const fn aligned_stride(self, width: u32) -> usize {
1166 width as usize * self.bytes_per_pixel()
1167 }
1168
1169 #[inline]
1175 pub const fn simd_aligned_stride(self, width: u32, simd_align: usize) -> usize {
1176 let bpp = self.bytes_per_pixel();
1177 let raw = width as usize * bpp;
1178 let align = lcm(bpp, simd_align);
1179 align_up_general(raw, align)
1180 }
1181
1182 #[inline]
1187 pub const fn layout_compatible(self, other: Self) -> bool {
1188 self.format.channel_type() as u8 == other.format.channel_type() as u8
1189 && self.layout() as u8 == other.layout() as u8
1190 }
1191}
1192
1193const fn gcd(mut a: usize, mut b: usize) -> usize {
1196 while b != 0 {
1197 let t = b;
1198 b = a % b;
1199 a = t;
1200 }
1201 a
1202}
1203
1204const fn lcm(a: usize, b: usize) -> usize {
1205 if a == 0 || b == 0 {
1206 0
1207 } else {
1208 a / gcd(a, b) * b
1209 }
1210}
1211
1212const fn align_up_general(value: usize, align: usize) -> usize {
1213 if align == 0 {
1214 return value;
1215 }
1216 let rem = value % align;
1217 if rem == 0 { value } else { value + align - rem }
1218}
1219
1220impl fmt::Display for PixelDescriptor {
1221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1222 write!(
1223 f,
1224 "{} {} {}",
1225 self.format,
1226 self.format.channel_type(),
1227 self.transfer
1228 )?;
1229 if let Some(alpha) = self.alpha {
1230 if alpha.has_alpha() {
1231 write!(f, " alpha={alpha}")?;
1232 }
1233 }
1234 Ok(())
1235 }
1236}
1237
1238#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1245#[non_exhaustive]
1246#[repr(u8)]
1247pub enum ColorModel {
1248 Gray = 0,
1250 Rgb = 1,
1252 YCbCr = 2,
1254 Oklab = 3,
1256 Cmyk = 4,
1260}
1261
1262impl ColorModel {
1263 #[inline]
1265 #[allow(unreachable_patterns)]
1266 pub const fn color_channels(self) -> u8 {
1267 match self {
1268 Self::Gray => 1,
1269 Self::Cmyk => 4,
1270 _ => 3,
1271 }
1272 }
1273}
1274
1275impl fmt::Display for ColorModel {
1276 #[allow(unreachable_patterns)]
1277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1278 match self {
1279 Self::Gray => f.write_str("Gray"),
1280 Self::Rgb => f.write_str("RGB"),
1281 Self::YCbCr => f.write_str("YCbCr"),
1282 Self::Oklab => f.write_str("Oklab"),
1283 Self::Cmyk => f.write_str("CMYK"),
1284 _ => write!(f, "ColorModel({})", *self as u8),
1285 }
1286 }
1287}
1288
1289#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
1295#[non_exhaustive]
1296#[repr(u8)]
1297pub enum ByteOrder {
1298 #[default]
1300 Native = 0,
1301 Bgr = 1,
1303}
1304
1305impl fmt::Display for ByteOrder {
1306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1307 match self {
1308 Self::Native => f.write_str("native"),
1309 Self::Bgr => f.write_str("BGR"),
1310 }
1311 }
1312}
1313
1314#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1328#[non_exhaustive]
1329#[repr(u8)]
1330pub enum PixelFormat {
1331 Rgb8 = 1,
1332 Rgba8 = 2,
1333 Rgb16 = 3,
1334 Rgba16 = 4,
1335 RgbF32 = 5,
1336 RgbaF32 = 6,
1337 Gray8 = 7,
1338 Gray16 = 8,
1339 GrayF32 = 9,
1340 GrayA8 = 10,
1341 GrayA16 = 11,
1342 GrayAF32 = 12,
1343 Bgra8 = 13,
1344 Rgbx8 = 14,
1345 Bgrx8 = 15,
1346 OklabF32 = 16,
1347 OklabaF32 = 17,
1348 Cmyk8 = 18,
1350 RgbF16 = 19,
1352 RgbaF16 = 20,
1354 GrayF16 = 21,
1356 GrayAF16 = 22,
1358}
1359
1360impl PixelFormat {
1361 #[inline]
1363 #[allow(unreachable_patterns)]
1364 pub const fn channel_type(self) -> ChannelType {
1365 match self {
1366 Self::Rgb8
1367 | Self::Rgba8
1368 | Self::Gray8
1369 | Self::GrayA8
1370 | Self::Bgra8
1371 | Self::Rgbx8
1372 | Self::Bgrx8
1373 | Self::Cmyk8 => ChannelType::U8,
1374 Self::Rgb16 | Self::Rgba16 | Self::Gray16 | Self::GrayA16 => ChannelType::U16,
1375 Self::RgbF32
1376 | Self::RgbaF32
1377 | Self::GrayF32
1378 | Self::GrayAF32
1379 | Self::OklabF32
1380 | Self::OklabaF32 => ChannelType::F32,
1381 Self::RgbF16 | Self::RgbaF16 | Self::GrayF16 | Self::GrayAF16 => ChannelType::F16,
1382 _ => ChannelType::U8,
1383 }
1384 }
1385
1386 #[inline]
1388 #[allow(unreachable_patterns)]
1389 pub const fn layout(self) -> ChannelLayout {
1390 match self {
1391 Self::Rgb8 | Self::Rgb16 | Self::RgbF32 | Self::RgbF16 => ChannelLayout::Rgb,
1392 Self::Rgba8 | Self::Rgba16 | Self::RgbaF32 | Self::RgbaF16 | Self::Rgbx8 => {
1393 ChannelLayout::Rgba
1394 }
1395 Self::Gray8 | Self::Gray16 | Self::GrayF32 | Self::GrayF16 => ChannelLayout::Gray,
1396 Self::GrayA8 | Self::GrayA16 | Self::GrayAF32 | Self::GrayAF16 => {
1397 ChannelLayout::GrayAlpha
1398 }
1399 Self::Bgra8 | Self::Bgrx8 => ChannelLayout::Bgra,
1400 Self::OklabF32 => ChannelLayout::Oklab,
1401 Self::OklabaF32 => ChannelLayout::OklabA,
1402 Self::Cmyk8 => ChannelLayout::Cmyk,
1403 _ => ChannelLayout::Rgb,
1404 }
1405 }
1406
1407 #[inline]
1409 #[allow(unreachable_patterns)]
1410 pub const fn color_model(self) -> ColorModel {
1411 match self {
1412 Self::Gray8
1413 | Self::Gray16
1414 | Self::GrayF32
1415 | Self::GrayF16
1416 | Self::GrayA8
1417 | Self::GrayA16
1418 | Self::GrayAF32
1419 | Self::GrayAF16 => ColorModel::Gray,
1420 Self::OklabF32 | Self::OklabaF32 => ColorModel::Oklab,
1421 Self::Cmyk8 => ColorModel::Cmyk,
1422 _ => ColorModel::Rgb,
1423 }
1424 }
1425
1426 #[inline]
1428 #[allow(unreachable_patterns)]
1429 pub const fn byte_order(self) -> ByteOrder {
1430 match self {
1431 Self::Bgra8 | Self::Bgrx8 => ByteOrder::Bgr,
1432 _ => ByteOrder::Native,
1433 }
1434 }
1435
1436 #[inline]
1438 pub const fn channels(self) -> usize {
1439 self.layout().channels()
1440 }
1441
1442 #[inline]
1444 pub const fn bytes_per_pixel(self) -> usize {
1445 self.channels() * self.channel_type().byte_size()
1446 }
1447
1448 #[inline]
1450 pub const fn has_alpha_bytes(self) -> bool {
1451 self.layout().has_alpha()
1452 }
1453
1454 #[inline]
1456 pub const fn is_grayscale(self) -> bool {
1457 matches!(self.color_model(), ColorModel::Gray)
1458 }
1459
1460 #[allow(unreachable_patterns)]
1466 #[inline]
1467 pub const fn default_alpha(self) -> Option<AlphaMode> {
1468 match self {
1469 Self::Rgb8
1470 | Self::Rgb16
1471 | Self::RgbF32
1472 | Self::RgbF16
1473 | Self::Gray8
1474 | Self::Gray16
1475 | Self::GrayF32
1476 | Self::GrayF16
1477 | Self::OklabF32
1478 | Self::Cmyk8 => None,
1479 Self::Rgbx8 | Self::Bgrx8 => Some(AlphaMode::Undefined),
1480 _ => Some(AlphaMode::Straight),
1481 }
1482 }
1483
1484 #[allow(unreachable_patterns)]
1486 #[inline]
1487 pub const fn name(self) -> &'static str {
1488 match self {
1489 Self::Rgb8 => "RGB8",
1490 Self::Rgba8 => "RGBA8",
1491 Self::Rgb16 => "RGB16",
1492 Self::Rgba16 => "RGBA16",
1493 Self::RgbF32 => "RgbF32",
1494 Self::RgbaF32 => "RgbaF32",
1495 Self::Gray8 => "Gray8",
1496 Self::Gray16 => "Gray16",
1497 Self::GrayF32 => "GrayF32",
1498 Self::GrayA8 => "GrayA8",
1499 Self::GrayA16 => "GrayA16",
1500 Self::GrayAF32 => "GrayAF32",
1501 Self::Bgra8 => "BGRA8",
1502 Self::Rgbx8 => "RGBX8",
1503 Self::Bgrx8 => "BGRX8",
1504 Self::OklabF32 => "OklabF32",
1505 Self::OklabaF32 => "OklabaF32",
1506 Self::Cmyk8 => "CMYK8",
1507 Self::RgbF16 => "RgbF16",
1508 Self::RgbaF16 => "RgbaF16",
1509 Self::GrayF16 => "GrayF16",
1510 Self::GrayAF16 => "GrayAF16",
1511 _ => "Unknown",
1512 }
1513 }
1514
1515 #[inline]
1520 pub const fn from_parts(
1521 channel_type: ChannelType,
1522 layout: ChannelLayout,
1523 alpha: Option<AlphaMode>,
1524 ) -> Option<Self> {
1525 let is_padding = matches!(alpha, Some(AlphaMode::Undefined));
1526 match (channel_type, layout, is_padding) {
1527 (ChannelType::U8, ChannelLayout::Rgb, _) => Some(Self::Rgb8),
1528 (ChannelType::U16, ChannelLayout::Rgb, _) => Some(Self::Rgb16),
1529 (ChannelType::F32, ChannelLayout::Rgb, _) => Some(Self::RgbF32),
1530 (ChannelType::F16, ChannelLayout::Rgb, _) => Some(Self::RgbF16),
1531
1532 (ChannelType::U8, ChannelLayout::Rgba, true) => Some(Self::Rgbx8),
1533 (ChannelType::U8, ChannelLayout::Rgba, false) => Some(Self::Rgba8),
1534 (ChannelType::U16, ChannelLayout::Rgba, _) => Some(Self::Rgba16),
1535 (ChannelType::F32, ChannelLayout::Rgba, _) => Some(Self::RgbaF32),
1536 (ChannelType::F16, ChannelLayout::Rgba, _) => Some(Self::RgbaF16),
1537
1538 (ChannelType::U8, ChannelLayout::Gray, _) => Some(Self::Gray8),
1539 (ChannelType::U16, ChannelLayout::Gray, _) => Some(Self::Gray16),
1540 (ChannelType::F32, ChannelLayout::Gray, _) => Some(Self::GrayF32),
1541 (ChannelType::F16, ChannelLayout::Gray, _) => Some(Self::GrayF16),
1542
1543 (ChannelType::U8, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA8),
1544 (ChannelType::U16, ChannelLayout::GrayAlpha, _) => Some(Self::GrayA16),
1545 (ChannelType::F32, ChannelLayout::GrayAlpha, _) => Some(Self::GrayAF32),
1546 (ChannelType::F16, ChannelLayout::GrayAlpha, _) => Some(Self::GrayAF16),
1547
1548 (ChannelType::U8, ChannelLayout::Bgra, true) => Some(Self::Bgrx8),
1549 (ChannelType::U8, ChannelLayout::Bgra, false) => Some(Self::Bgra8),
1550
1551 (ChannelType::F32, ChannelLayout::Oklab, _) => Some(Self::OklabF32),
1552 (ChannelType::F32, ChannelLayout::OklabA, _) => Some(Self::OklabaF32),
1553
1554 (ChannelType::U8, ChannelLayout::Cmyk, _) => Some(Self::Cmyk8),
1555
1556 _ => None,
1557 }
1558 }
1559
1560 #[allow(unreachable_patterns)]
1562 #[inline]
1563 pub const fn descriptor(self) -> PixelDescriptor {
1564 match self {
1565 Self::Rgb8 => PixelDescriptor::RGB8,
1566 Self::Rgba8 => PixelDescriptor::RGBA8,
1567 Self::Rgb16 => PixelDescriptor::RGB16,
1568 Self::Rgba16 => PixelDescriptor::RGBA16,
1569 Self::RgbF32 => PixelDescriptor::RGBF32,
1570 Self::RgbaF32 => PixelDescriptor::RGBAF32,
1571 Self::Gray8 => PixelDescriptor::GRAY8,
1572 Self::Gray16 => PixelDescriptor::GRAY16,
1573 Self::GrayF32 => PixelDescriptor::GRAYF32,
1574 Self::GrayA8 => PixelDescriptor::GRAYA8,
1575 Self::GrayA16 => PixelDescriptor::GRAYA16,
1576 Self::GrayAF32 => PixelDescriptor::GRAYAF32,
1577 Self::Bgra8 => PixelDescriptor::BGRA8,
1578 Self::Rgbx8 => PixelDescriptor::RGBX8,
1579 Self::Bgrx8 => PixelDescriptor::BGRX8,
1580 Self::OklabF32 => PixelDescriptor::OKLABF32,
1581 Self::OklabaF32 => PixelDescriptor::OKLABAF32,
1582 Self::Cmyk8 => PixelDescriptor::CMYK8,
1583 Self::RgbF16 => PixelDescriptor::RGBF16,
1584 Self::RgbaF16 => PixelDescriptor::RGBAF16,
1585 Self::GrayF16 => PixelDescriptor::GRAYF16,
1586 Self::GrayAF16 => PixelDescriptor::GRAYAF16,
1587 _ => PixelDescriptor::RGB8,
1588 }
1589 }
1590}
1591
1592impl fmt::Display for PixelFormat {
1593 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1594 f.write_str(self.name())
1595 }
1596}
1597
1598#[cfg(test)]
1603mod tests {
1604 use alloc::format;
1605 use core::mem::size_of;
1606
1607 use super::*;
1608
1609 #[test]
1610 fn channel_type_byte_size() {
1611 assert_eq!(ChannelType::U8.byte_size(), 1);
1612 assert_eq!(ChannelType::U16.byte_size(), 2);
1613 assert_eq!(ChannelType::F16.byte_size(), 2);
1614 assert_eq!(ChannelType::F32.byte_size(), 4);
1615 }
1616
1617 #[test]
1618 fn descriptor_bytes_per_pixel() {
1619 assert_eq!(PixelDescriptor::RGB8.bytes_per_pixel(), 3);
1620 assert_eq!(PixelDescriptor::RGBA8.bytes_per_pixel(), 4);
1621 assert_eq!(PixelDescriptor::GRAY8.bytes_per_pixel(), 1);
1622 assert_eq!(PixelDescriptor::RGBAF32.bytes_per_pixel(), 16);
1623 assert_eq!(PixelDescriptor::GRAYA8.bytes_per_pixel(), 2);
1624 }
1625
1626 #[test]
1627 fn descriptor_has_alpha() {
1628 assert!(!PixelDescriptor::RGB8.has_alpha());
1629 assert!(PixelDescriptor::RGBA8.has_alpha());
1630 assert!(!PixelDescriptor::RGBX8.has_alpha());
1631 assert!(PixelDescriptor::GRAYA8.has_alpha());
1632 }
1633
1634 #[test]
1635 fn descriptor_is_grayscale() {
1636 assert!(PixelDescriptor::GRAY8.is_grayscale());
1637 assert!(PixelDescriptor::GRAYA8.is_grayscale());
1638 assert!(!PixelDescriptor::RGB8.is_grayscale());
1639 }
1640
1641 #[test]
1642 fn layout_compatible() {
1643 assert!(PixelDescriptor::RGB8_SRGB.layout_compatible(PixelDescriptor::RGB8));
1644 assert!(!PixelDescriptor::RGB8.layout_compatible(PixelDescriptor::RGBA8));
1645 }
1646
1647 #[test]
1648 fn pixel_format_descriptor_roundtrip() {
1649 let desc = PixelFormat::Rgba8.descriptor();
1650 assert_eq!(desc.layout(), ChannelLayout::Rgba);
1651 assert_eq!(desc.channel_type(), ChannelType::U8);
1652 }
1653
1654 #[test]
1655 fn pixel_format_enum_basics() {
1656 assert_eq!(PixelFormat::Rgb8.channels(), 3);
1657 assert_eq!(PixelFormat::Rgba8.channels(), 4);
1658 assert!(PixelFormat::Rgba8.has_alpha_bytes());
1659 assert!(!PixelFormat::Rgb8.has_alpha_bytes());
1660 assert_eq!(PixelFormat::RgbF32.bytes_per_pixel(), 12);
1661 assert_eq!(PixelFormat::RgbaF32.bytes_per_pixel(), 16);
1662 assert_eq!(PixelFormat::Gray8.channels(), 1);
1663 assert!(PixelFormat::Gray8.is_grayscale());
1664 assert!(!PixelFormat::Rgb8.is_grayscale());
1665 assert_eq!(PixelFormat::Bgra8.byte_order(), ByteOrder::Bgr);
1666 assert_eq!(PixelFormat::Rgb8.byte_order(), ByteOrder::Native);
1667 }
1668
1669 #[test]
1670 fn pixel_format_enum_size() {
1671 assert!(size_of::<PixelFormat>() <= 2);
1673 }
1674
1675 #[test]
1676 fn pixel_format_from_parts_roundtrip() {
1677 let fmt = PixelFormat::Rgba8;
1678 let rebuilt =
1679 PixelFormat::from_parts(fmt.channel_type(), fmt.layout(), fmt.default_alpha());
1680 assert_eq!(rebuilt, Some(fmt));
1681
1682 let fmt2 = PixelFormat::Bgra8;
1683 let rebuilt2 =
1684 PixelFormat::from_parts(fmt2.channel_type(), fmt2.layout(), fmt2.default_alpha());
1685 assert_eq!(rebuilt2, Some(fmt2));
1686
1687 let fmt3 = PixelFormat::Gray8;
1688 let rebuilt3 =
1689 PixelFormat::from_parts(fmt3.channel_type(), fmt3.layout(), fmt3.default_alpha());
1690 assert_eq!(rebuilt3, Some(fmt3));
1691 }
1692
1693 #[test]
1694 fn alpha_mode_semantics() {
1695 assert!(!PixelDescriptor::RGB8.has_alpha());
1697 assert!(!AlphaMode::Undefined.has_alpha());
1699 assert!(AlphaMode::Straight.has_alpha());
1701 assert!(AlphaMode::Premultiplied.has_alpha());
1702 assert!(AlphaMode::Opaque.has_alpha());
1703 }
1704
1705 #[test]
1706 fn color_primaries_containment() {
1707 assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::DisplayP3));
1708 assert!(ColorPrimaries::Bt2020.contains(ColorPrimaries::Bt709));
1709 assert!(ColorPrimaries::DisplayP3.contains(ColorPrimaries::Bt709));
1710 assert!(!ColorPrimaries::Bt709.contains(ColorPrimaries::DisplayP3));
1711 assert!(!ColorPrimaries::Unknown.contains(ColorPrimaries::Bt709));
1712 }
1713
1714 #[test]
1715 fn descriptor_size() {
1716 assert!(size_of::<PixelDescriptor>() <= 8);
1718 }
1719
1720 #[test]
1721 fn color_model_channels() {
1722 assert_eq!(ColorModel::Gray.color_channels(), 1);
1723 assert_eq!(ColorModel::Rgb.color_channels(), 3);
1724 assert_eq!(ColorModel::YCbCr.color_channels(), 3);
1725 assert_eq!(ColorModel::Oklab.color_channels(), 3);
1726 assert_eq!(ColorModel::Cmyk.color_channels(), 4);
1727 }
1728
1729 #[test]
1742 fn reference_white_nits_values() {
1743 assert_eq!(TransferFunction::Pq.reference_white_nits(), 203.0);
1744 assert_eq!(TransferFunction::Srgb.reference_white_nits(), 1.0);
1745 assert_eq!(TransferFunction::Linear.reference_white_nits(), 1.0);
1746 assert_eq!(TransferFunction::Unknown.reference_white_nits(), 1.0);
1747 }
1748
1749 #[test]
1754 fn channel_type_display() {
1755 assert_eq!(format!("{}", ChannelType::U8), "U8");
1756 assert_eq!(format!("{}", ChannelType::U16), "U16");
1757 assert_eq!(format!("{}", ChannelType::F32), "F32");
1758 assert_eq!(format!("{}", ChannelType::F16), "F16");
1759 }
1760
1761 #[test]
1762 fn channel_layout_display() {
1763 assert_eq!(format!("{}", ChannelLayout::Gray), "Gray");
1764 assert_eq!(format!("{}", ChannelLayout::GrayAlpha), "GrayAlpha");
1765 assert_eq!(format!("{}", ChannelLayout::Rgb), "RGB");
1766 assert_eq!(format!("{}", ChannelLayout::Rgba), "RGBA");
1767 assert_eq!(format!("{}", ChannelLayout::Bgra), "BGRA");
1768 assert_eq!(format!("{}", ChannelLayout::Oklab), "Oklab");
1769 assert_eq!(format!("{}", ChannelLayout::OklabA), "OklabA");
1770 }
1771
1772 #[test]
1773 fn alpha_mode_display() {
1774 assert_eq!(format!("{}", AlphaMode::Undefined), "undefined");
1775 assert_eq!(format!("{}", AlphaMode::Straight), "straight");
1776 assert_eq!(format!("{}", AlphaMode::Premultiplied), "premultiplied");
1777 assert_eq!(format!("{}", AlphaMode::Opaque), "opaque");
1778 }
1779
1780 #[test]
1781 fn transfer_function_display() {
1782 assert_eq!(format!("{}", TransferFunction::Linear), "linear");
1783 assert_eq!(format!("{}", TransferFunction::Srgb), "sRGB");
1784 assert_eq!(format!("{}", TransferFunction::Bt709), "BT.709");
1785 assert_eq!(format!("{}", TransferFunction::Pq), "PQ");
1786 assert_eq!(format!("{}", TransferFunction::Unknown), "unknown");
1787 }
1788
1789 #[test]
1790 fn color_primaries_display() {
1791 assert_eq!(format!("{}", ColorPrimaries::Bt709), "BT.709");
1792 assert_eq!(format!("{}", ColorPrimaries::Bt2020), "BT.2020");
1793 assert_eq!(format!("{}", ColorPrimaries::DisplayP3), "Display P3");
1794 assert_eq!(format!("{}", ColorPrimaries::Unknown), "unknown");
1795 }
1796
1797 #[test]
1798 fn signal_range_display() {
1799 assert_eq!(format!("{}", SignalRange::Full), "full");
1800 assert_eq!(format!("{}", SignalRange::Narrow), "narrow");
1801 }
1802
1803 #[test]
1804 fn pixel_descriptor_display() {
1805 let s = format!("{}", PixelDescriptor::RGB8_SRGB);
1806 assert!(s.contains("U8"), "expected U8 in: {s}");
1807 assert!(s.contains("sRGB"), "expected sRGB in: {s}");
1808
1809 let s = format!("{}", PixelDescriptor::RGBA8_SRGB);
1810 assert!(s.contains("alpha=straight"), "expected alpha in: {s}");
1811 }
1812
1813 #[test]
1814 fn pixel_format_display() {
1815 let s = format!("{}", PixelFormat::Rgb8);
1816 assert!(s.contains("RGB8"));
1817 let s = format!("{}", PixelFormat::Bgra8);
1818 assert!(s.contains("BGRA8"));
1819 }
1820
1821 #[test]
1824 fn transfer_function_from_cicp() {
1825 assert_eq!(
1826 TransferFunction::from_cicp(1),
1827 Some(TransferFunction::Bt709)
1828 );
1829 assert_eq!(
1830 TransferFunction::from_cicp(8),
1831 Some(TransferFunction::Linear)
1832 );
1833 assert_eq!(
1834 TransferFunction::from_cicp(13),
1835 Some(TransferFunction::Srgb)
1836 );
1837 assert_eq!(TransferFunction::from_cicp(16), Some(TransferFunction::Pq));
1838 assert_eq!(TransferFunction::from_cicp(18), Some(TransferFunction::Hlg));
1839 assert_eq!(TransferFunction::from_cicp(99), None);
1840 }
1841
1842 #[test]
1843 fn transfer_function_to_cicp() {
1844 assert_eq!(TransferFunction::Bt709.to_cicp(), Some(1));
1845 assert_eq!(TransferFunction::Linear.to_cicp(), Some(8));
1846 assert_eq!(TransferFunction::Srgb.to_cicp(), Some(13));
1847 assert_eq!(TransferFunction::Pq.to_cicp(), Some(16));
1848 assert_eq!(TransferFunction::Hlg.to_cicp(), Some(18));
1849 assert_eq!(TransferFunction::Unknown.to_cicp(), None);
1850 }
1851
1852 #[test]
1853 fn transfer_function_cicp_roundtrip() {
1854 for tf in [
1855 TransferFunction::Bt709,
1856 TransferFunction::Linear,
1857 TransferFunction::Srgb,
1858 TransferFunction::Pq,
1859 ] {
1860 let code = tf.to_cicp().unwrap();
1861 assert_eq!(TransferFunction::from_cicp(code), Some(tf));
1862 }
1863 }
1864
1865 #[test]
1866 fn color_primaries_from_cicp() {
1867 assert_eq!(ColorPrimaries::from_cicp(1), Some(ColorPrimaries::Bt709));
1868 assert_eq!(ColorPrimaries::from_cicp(9), Some(ColorPrimaries::Bt2020));
1869 assert_eq!(
1870 ColorPrimaries::from_cicp(12),
1871 Some(ColorPrimaries::DisplayP3)
1872 );
1873 assert_eq!(ColorPrimaries::from_cicp(99), None);
1874 }
1875
1876 #[test]
1877 fn color_primaries_to_cicp() {
1878 assert_eq!(ColorPrimaries::Bt709.to_cicp(), Some(1));
1879 assert_eq!(ColorPrimaries::Bt2020.to_cicp(), Some(9));
1880 assert_eq!(ColorPrimaries::DisplayP3.to_cicp(), Some(12));
1881 assert_eq!(ColorPrimaries::Unknown.to_cicp(), None);
1882 }
1883
1884 #[test]
1887 fn channel_type_helpers() {
1888 assert!(ChannelType::U8.is_u8());
1889 assert!(!ChannelType::U8.is_u16());
1890 assert!(ChannelType::U16.is_u16());
1891 assert!(ChannelType::F32.is_f32());
1892 assert!(ChannelType::F16.is_f16());
1893 assert!(ChannelType::U8.is_integer());
1894 assert!(ChannelType::U16.is_integer());
1895 assert!(!ChannelType::F32.is_integer());
1896 assert!(ChannelType::F32.is_float());
1897 assert!(ChannelType::F16.is_float());
1898 assert!(!ChannelType::U8.is_float());
1899 }
1900
1901 #[test]
1904 fn channel_layout_channels() {
1905 assert_eq!(ChannelLayout::Gray.channels(), 1);
1906 assert_eq!(ChannelLayout::GrayAlpha.channels(), 2);
1907 assert_eq!(ChannelLayout::Rgb.channels(), 3);
1908 assert_eq!(ChannelLayout::Rgba.channels(), 4);
1909 assert_eq!(ChannelLayout::Bgra.channels(), 4);
1910 assert_eq!(ChannelLayout::Oklab.channels(), 3);
1911 assert_eq!(ChannelLayout::OklabA.channels(), 4);
1912 }
1913
1914 #[test]
1915 fn channel_layout_has_alpha() {
1916 assert!(!ChannelLayout::Gray.has_alpha());
1917 assert!(ChannelLayout::GrayAlpha.has_alpha());
1918 assert!(!ChannelLayout::Rgb.has_alpha());
1919 assert!(ChannelLayout::Rgba.has_alpha());
1920 assert!(ChannelLayout::Bgra.has_alpha());
1921 assert!(!ChannelLayout::Oklab.has_alpha());
1922 assert!(ChannelLayout::OklabA.has_alpha());
1923 }
1924
1925 #[test]
1928 fn with_transfer() {
1929 let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Linear);
1930 assert_eq!(desc.transfer(), TransferFunction::Linear);
1931 assert_eq!(desc.layout(), ChannelLayout::Rgb);
1932 }
1933
1934 #[test]
1935 fn with_primaries() {
1936 let desc = PixelDescriptor::RGB8_SRGB.with_primaries(ColorPrimaries::DisplayP3);
1937 assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
1938 }
1939
1940 #[test]
1941 fn with_signal_range() {
1942 let desc = PixelDescriptor::RGB8_SRGB.with_signal_range(SignalRange::Narrow);
1943 assert_eq!(desc.signal_range, SignalRange::Narrow);
1944 }
1945
1946 #[test]
1947 fn with_alpha_mode() {
1948 let desc = PixelDescriptor::RGBA8_SRGB.with_alpha(Some(AlphaMode::Premultiplied));
1949 assert_eq!(desc.alpha(), Some(AlphaMode::Premultiplied));
1950 }
1951
1952 #[test]
1955 fn is_opaque_and_may_have_transparency() {
1956 assert!(PixelDescriptor::RGB8_SRGB.is_opaque());
1957 assert!(!PixelDescriptor::RGB8_SRGB.may_have_transparency());
1958 assert!(!PixelDescriptor::RGBA8_SRGB.is_opaque());
1959 assert!(PixelDescriptor::RGBA8_SRGB.may_have_transparency());
1960
1961 let rgbx = PixelDescriptor::new(
1962 ChannelType::U8,
1963 ChannelLayout::Rgba,
1964 Some(AlphaMode::Undefined),
1965 TransferFunction::Srgb,
1966 );
1967 assert!(rgbx.is_opaque());
1968 assert!(!rgbx.may_have_transparency());
1969 }
1970
1971 #[test]
1972 fn is_linear_and_is_unknown_transfer() {
1973 assert!(!PixelDescriptor::RGB8_SRGB.is_linear());
1974 assert!(PixelDescriptor::RGBF32_LINEAR.is_linear());
1975 assert!(!PixelDescriptor::RGB8_SRGB.is_unknown_transfer());
1976 let desc = PixelDescriptor::RGB8_SRGB.with_transfer(TransferFunction::Unknown);
1977 assert!(desc.is_unknown_transfer());
1978 }
1979
1980 #[test]
1981 fn min_alignment() {
1982 assert_eq!(PixelDescriptor::RGB8_SRGB.min_alignment(), 1);
1983 assert_eq!(PixelDescriptor::RGBF32_LINEAR.min_alignment(), 4);
1984 }
1985
1986 #[test]
1987 fn aligned_stride() {
1988 assert_eq!(PixelDescriptor::RGB8_SRGB.aligned_stride(100), 300);
1989 assert_eq!(PixelDescriptor::RGBA8_SRGB.aligned_stride(100), 400);
1990 assert_eq!(PixelDescriptor::RGBF32_LINEAR.aligned_stride(10), 120);
1991 }
1992
1993 #[test]
1994 fn simd_aligned_stride() {
1995 let stride = PixelDescriptor::RGB8_SRGB.simd_aligned_stride(100, 16);
1996 assert!(stride >= 300);
1997 assert_eq!(stride % 16, 0);
1998 assert_eq!(stride % 3, 0); }
2000
2001 #[test]
2004 fn new_full_constructor() {
2005 let desc = PixelDescriptor::new_full(
2006 ChannelType::U8,
2007 ChannelLayout::Rgb,
2008 None,
2009 TransferFunction::Srgb,
2010 ColorPrimaries::DisplayP3,
2011 );
2012 assert_eq!(desc.primaries, ColorPrimaries::DisplayP3);
2013 assert_eq!(desc.transfer(), TransferFunction::Srgb);
2014 }
2015
2016 #[test]
2017 fn from_pixel_format_constructor() {
2018 let desc = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8);
2019 assert_eq!(desc.layout(), ChannelLayout::Rgba);
2020 assert_eq!(desc.transfer(), TransferFunction::Unknown);
2021 assert_eq!(desc.primaries, ColorPrimaries::Bt709);
2022 assert_eq!(desc.signal_range, SignalRange::Full);
2023 }
2024
2025 #[test]
2028 fn pixel_format_name() {
2029 assert_eq!(PixelFormat::Rgb8.name(), "RGB8");
2030 assert_eq!(PixelFormat::Bgra8.name(), "BGRA8");
2031 assert_eq!(PixelFormat::Gray8.name(), "Gray8");
2032 }
2033
2034 #[test]
2037 fn color_model_display() {
2038 assert_eq!(format!("{}", ColorModel::Gray), "Gray");
2039 assert_eq!(format!("{}", ColorModel::Rgb), "RGB");
2040 assert_eq!(format!("{}", ColorModel::YCbCr), "YCbCr");
2041 assert_eq!(format!("{}", ColorModel::Oklab), "Oklab");
2042 assert_eq!(format!("{}", ColorModel::Cmyk), "CMYK");
2043 }
2044
2045 #[test]
2048 fn signal_range_default() {
2049 assert_eq!(SignalRange::default(), SignalRange::Full);
2050 }
2051
2052 #[test]
2055 fn color_primaries_default() {
2056 assert_eq!(ColorPrimaries::default(), ColorPrimaries::Bt709);
2057 }
2058
2059 #[test]
2062 fn cmyk8_descriptor() {
2063 let d = PixelDescriptor::CMYK8;
2064 assert_eq!(d.color_model(), ColorModel::Cmyk);
2065 assert_eq!(d.channels(), 4);
2066 assert_eq!(d.bytes_per_pixel(), 4);
2067 assert_eq!(d.layout(), ChannelLayout::Cmyk);
2068 assert_eq!(d.channel_type(), ChannelType::U8);
2069 assert_eq!(d.transfer(), TransferFunction::Unknown);
2070 assert_eq!(d.primaries, ColorPrimaries::Bt709);
2071 assert!(!d.has_alpha());
2072 assert!(d.is_opaque());
2073 }
2074
2075 #[test]
2076 fn cmyk8_pixel_format() {
2077 let fmt = PixelFormat::Cmyk8;
2078 assert_eq!(fmt.channels(), 4);
2079 assert_eq!(fmt.bytes_per_pixel(), 4);
2080 assert_eq!(fmt.channel_type(), ChannelType::U8);
2081 assert_eq!(fmt.layout(), ChannelLayout::Cmyk);
2082 assert_eq!(fmt.color_model(), ColorModel::Cmyk);
2083 assert!(!fmt.has_alpha_bytes());
2084 assert!(!fmt.is_grayscale());
2085 assert_eq!(fmt.name(), "CMYK8");
2086 assert_eq!(fmt.default_alpha(), None);
2087 }
2088
2089 #[test]
2090 fn cmyk8_from_parts_roundtrip() {
2091 let fmt = PixelFormat::Cmyk8;
2092 let rebuilt =
2093 PixelFormat::from_parts(fmt.channel_type(), fmt.layout(), fmt.default_alpha());
2094 assert_eq!(rebuilt, Some(fmt));
2095 }
2096
2097 #[test]
2098 fn cmyk8_descriptor_roundtrip() {
2099 let desc = PixelFormat::Cmyk8.descriptor();
2100 assert_eq!(desc, PixelDescriptor::CMYK8);
2101 }
2102
2103 #[test]
2104 fn cmyk_channel_layout_display() {
2105 assert_eq!(format!("{}", ChannelLayout::Cmyk), "CMYK");
2106 }
2107
2108 #[test]
2109 fn cmyk_channel_layout_no_alpha() {
2110 assert!(!ChannelLayout::Cmyk.has_alpha());
2111 }
2112}