pfnc/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Pixel Format Naming Convention helpers.
3
4use core::fmt;
5
6/// Enumeration of the pixel formats supported by the helper routines.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[repr(u32)]
9pub enum PixelFormat {
10    Mono8 = 0x0108_0001,
11    Mono16 = 0x0110_0007,
12    BayerRG8 = 0x0108_0009,
13    BayerGB8 = 0x0108_000A,
14    BayerBG8 = 0x0108_000B,
15    BayerGR8 = 0x0108_0008,
16    RGB8Packed = 0x0218_0014,
17    BGR8Packed = 0x0218_0015,
18    /// Unknown PFNC code reported by the device.
19    Unknown(u32),
20}
21
22impl PixelFormat {
23    /// Convert a raw PFNC code into a [`PixelFormat`] enumeration.
24    pub const fn from_code(code: u32) -> PixelFormat {
25        match code {
26            0x0108_0001 => PixelFormat::Mono8,
27            0x0110_0007 => PixelFormat::Mono16,
28            0x0108_0009 => PixelFormat::BayerRG8,
29            0x0108_000A => PixelFormat::BayerGB8,
30            0x0108_000B => PixelFormat::BayerBG8,
31            0x0108_0008 => PixelFormat::BayerGR8,
32            0x0218_0014 => PixelFormat::RGB8Packed,
33            0x0218_0015 => PixelFormat::BGR8Packed,
34            other => PixelFormat::Unknown(other),
35        }
36    }
37
38    /// Return the PFNC code associated with the pixel format.
39    pub const fn code(self) -> u32 {
40        match self {
41            PixelFormat::Mono8 => 0x0108_0001,
42            PixelFormat::Mono16 => 0x0110_0007,
43            PixelFormat::BayerRG8 => 0x0108_0009,
44            PixelFormat::BayerGB8 => 0x0108_000A,
45            PixelFormat::BayerBG8 => 0x0108_000B,
46            PixelFormat::BayerGR8 => 0x0108_0008,
47            PixelFormat::RGB8Packed => 0x0218_0014,
48            PixelFormat::BGR8Packed => 0x0218_0015,
49            PixelFormat::Unknown(code) => code,
50        }
51    }
52
53    /// Number of bytes used to encode a single pixel for well-known formats.
54    pub const fn bytes_per_pixel(self) -> Option<usize> {
55        match self {
56            PixelFormat::Mono8 => Some(1),
57            PixelFormat::Mono16 => Some(2),
58            PixelFormat::RGB8Packed | PixelFormat::BGR8Packed => Some(3),
59            PixelFormat::BayerRG8
60            | PixelFormat::BayerGB8
61            | PixelFormat::BayerBG8
62            | PixelFormat::BayerGR8 => Some(1),
63            PixelFormat::Unknown(_) => None,
64        }
65    }
66
67    /// Whether the pixel format represents a Bayer mosaic.
68    pub const fn is_bayer(self) -> bool {
69        matches!(
70            self,
71            PixelFormat::BayerRG8
72                | PixelFormat::BayerGB8
73                | PixelFormat::BayerBG8
74                | PixelFormat::BayerGR8
75        )
76    }
77
78    /// Return the Color Filter Array pattern and canonical offsets.
79    ///
80    /// The tuple encodes `(pattern, x_offset, y_offset)` where the offsets
81    /// describe how the sensor mosaic aligns to the canonical `"RGGB"`
82    /// ordering.
83    pub const fn cfa_pattern(self) -> Option<(&'static str, u8, u8)> {
84        match self {
85            PixelFormat::BayerRG8 => Some(("RGGB", 0, 0)),
86            PixelFormat::BayerGR8 => Some(("RGGB", 1, 0)),
87            PixelFormat::BayerGB8 => Some(("RGGB", 0, 1)),
88            PixelFormat::BayerBG8 => Some(("RGGB", 1, 1)),
89            _ => None,
90        }
91    }
92}
93
94impl fmt::Display for PixelFormat {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self {
97            PixelFormat::Mono8 => f.write_str("Mono8"),
98            PixelFormat::Mono16 => f.write_str("Mono16"),
99            PixelFormat::BayerRG8 => f.write_str("BayerRG8"),
100            PixelFormat::BayerGB8 => f.write_str("BayerGB8"),
101            PixelFormat::BayerBG8 => f.write_str("BayerBG8"),
102            PixelFormat::BayerGR8 => f.write_str("BayerGR8"),
103            PixelFormat::RGB8Packed => f.write_str("RGB8Packed"),
104            PixelFormat::BGR8Packed => f.write_str("BGR8Packed"),
105            PixelFormat::Unknown(code) => write!(f, "Unknown(0x{code:08X})"),
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::PixelFormat;
113
114    #[test]
115    fn roundtrip_known_codes() {
116        let formats = [
117            PixelFormat::Mono8,
118            PixelFormat::Mono16,
119            PixelFormat::BayerRG8,
120            PixelFormat::BayerGB8,
121            PixelFormat::BayerBG8,
122            PixelFormat::BayerGR8,
123            PixelFormat::RGB8Packed,
124            PixelFormat::BGR8Packed,
125        ];
126
127        for fmt in formats {
128            let code = fmt.code();
129            assert_eq!(PixelFormat::from_code(code), fmt);
130        }
131    }
132
133    #[test]
134    fn unknown_code_roundtrip() {
135        let code = 0xDEAD_BEEF;
136        let fmt = PixelFormat::from_code(code);
137        assert!(matches!(fmt, PixelFormat::Unknown(value) if value == code));
138        assert_eq!(fmt.code(), code);
139    }
140
141    #[test]
142    fn bytes_per_pixel_matches_expectations() {
143        assert_eq!(PixelFormat::Mono8.bytes_per_pixel(), Some(1));
144        assert_eq!(PixelFormat::Mono16.bytes_per_pixel(), Some(2));
145        assert_eq!(PixelFormat::RGB8Packed.bytes_per_pixel(), Some(3));
146        assert_eq!(PixelFormat::BayerRG8.bytes_per_pixel(), Some(1));
147        assert_eq!(PixelFormat::Unknown(0).bytes_per_pixel(), None);
148    }
149
150    #[test]
151    fn cfa_offsets_align_to_rggb() {
152        assert_eq!(PixelFormat::BayerRG8.cfa_pattern(), Some(("RGGB", 0, 0)));
153        assert_eq!(PixelFormat::BayerGR8.cfa_pattern(), Some(("RGGB", 1, 0)));
154        assert_eq!(PixelFormat::BayerGB8.cfa_pattern(), Some(("RGGB", 0, 1)));
155        assert_eq!(PixelFormat::BayerBG8.cfa_pattern(), Some(("RGGB", 1, 1)));
156        assert_eq!(PixelFormat::Mono8.cfa_pattern(), None);
157    }
158}