1#![cfg_attr(docsrs, feature(doc_cfg))]
2use core::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[non_exhaustive]
10#[repr(u32)]
11pub enum PixelFormat {
12 Mono8 = 0x0108_0001,
13 Mono10 = 0x0110_0003,
14 Mono12 = 0x0110_0005,
15 Mono14 = 0x0110_0025,
16 Mono16 = 0x0110_0007,
17 Confidence8 = 0x0108_00C6,
18 Coord3DC32f = 0x0120_00BF,
19 Coord3DAC16 = 0x0220_00BB,
20 Coord3DAC32f = 0x0240_00C2,
21 Coord3DABC32f = 0x0260_00C0,
22 BayerRG8 = 0x0108_0009,
23 BayerGB8 = 0x0108_000A,
24 BayerBG8 = 0x0108_000B,
25 BayerGR8 = 0x0108_0008,
26 BayerGR16 = 0x0110_000E,
27 BayerRG16 = 0x0110_000F,
28 BayerGB16 = 0x0110_0010,
29 BayerBG16 = 0x0110_0011,
30 RGB8Packed = 0x0218_0014,
31 BGR8Packed = 0x0218_0015,
32 Unknown(u32),
34}
35
36impl PixelFormat {
37 pub const fn from_code(code: u32) -> PixelFormat {
39 match code {
40 0x0108_0001 => PixelFormat::Mono8,
41 0x0110_0003 => PixelFormat::Mono10,
42 0x0110_0005 => PixelFormat::Mono12,
43 0x0110_0025 => PixelFormat::Mono14,
44 0x0110_0007 => PixelFormat::Mono16,
45 0x0108_00C6 => PixelFormat::Confidence8,
46 0x0120_00BF => PixelFormat::Coord3DC32f,
47 0x0220_00BB => PixelFormat::Coord3DAC16,
48 0x0240_00C2 => PixelFormat::Coord3DAC32f,
49 0x0260_00C0 => PixelFormat::Coord3DABC32f,
50 0x0108_0009 => PixelFormat::BayerRG8,
51 0x0108_000A => PixelFormat::BayerGB8,
52 0x0108_000B => PixelFormat::BayerBG8,
53 0x0108_0008 => PixelFormat::BayerGR8,
54 0x0110_000E => PixelFormat::BayerGR16,
55 0x0110_000F => PixelFormat::BayerRG16,
56 0x0110_0010 => PixelFormat::BayerGB16,
57 0x0110_0011 => PixelFormat::BayerBG16,
58 0x0218_0014 => PixelFormat::RGB8Packed,
59 0x0218_0015 => PixelFormat::BGR8Packed,
60 other => PixelFormat::Unknown(other),
61 }
62 }
63
64 pub const fn code(self) -> u32 {
66 match self {
67 PixelFormat::Mono8 => 0x0108_0001,
68 PixelFormat::Mono10 => 0x0110_0003,
69 PixelFormat::Mono12 => 0x0110_0005,
70 PixelFormat::Mono14 => 0x0110_0025,
71 PixelFormat::Mono16 => 0x0110_0007,
72 PixelFormat::Confidence8 => 0x0108_00C6,
73 PixelFormat::Coord3DC32f => 0x0120_00BF,
74 PixelFormat::Coord3DAC16 => 0x0220_00BB,
75 PixelFormat::Coord3DAC32f => 0x0240_00C2,
76 PixelFormat::Coord3DABC32f => 0x0260_00C0,
77 PixelFormat::BayerRG8 => 0x0108_0009,
78 PixelFormat::BayerGB8 => 0x0108_000A,
79 PixelFormat::BayerBG8 => 0x0108_000B,
80 PixelFormat::BayerGR8 => 0x0108_0008,
81 PixelFormat::BayerGR16 => 0x0110_000E,
82 PixelFormat::BayerRG16 => 0x0110_000F,
83 PixelFormat::BayerGB16 => 0x0110_0010,
84 PixelFormat::BayerBG16 => 0x0110_0011,
85 PixelFormat::RGB8Packed => 0x0218_0014,
86 PixelFormat::BGR8Packed => 0x0218_0015,
87 PixelFormat::Unknown(code) => code,
88 }
89 }
90
91 pub const fn bytes_per_pixel(self) -> Option<usize> {
98 match self {
99 PixelFormat::Mono8 => Some(1),
100 PixelFormat::Mono10 | PixelFormat::Mono12 | PixelFormat::Mono14 => Some(2),
101 PixelFormat::Mono16 => Some(2),
102 PixelFormat::Confidence8 => Some(1),
103 PixelFormat::Coord3DC32f => Some(4),
104 PixelFormat::Coord3DAC16 => Some(4),
105 PixelFormat::Coord3DAC32f => Some(8),
106 PixelFormat::Coord3DABC32f => Some(12),
107 PixelFormat::RGB8Packed | PixelFormat::BGR8Packed => Some(3),
108 PixelFormat::BayerRG8
109 | PixelFormat::BayerGB8
110 | PixelFormat::BayerBG8
111 | PixelFormat::BayerGR8 => Some(1),
112 PixelFormat::BayerGR16
113 | PixelFormat::BayerRG16
114 | PixelFormat::BayerGB16
115 | PixelFormat::BayerBG16 => Some(2),
116 PixelFormat::Unknown(code) => PixelFormat::bytes_from_code(code),
117 }
118 }
119
120 const fn bytes_from_code(code: u32) -> Option<usize> {
138 let bits = (code >> 16) & 0xFF;
139 if bits == 0 || !bits.is_multiple_of(8) {
140 return None;
141 }
142 Some((bits / 8) as usize)
143 }
144
145 pub fn from_name(name: &str) -> PixelFormat {
149 match name {
150 "Mono8" => PixelFormat::Mono8,
151 "Mono10" => PixelFormat::Mono10,
152 "Mono12" => PixelFormat::Mono12,
153 "Mono14" => PixelFormat::Mono14,
154 "Mono16" => PixelFormat::Mono16,
155 "Confidence8" => PixelFormat::Confidence8,
156 "Coord3D_C32f" => PixelFormat::Coord3DC32f,
157 "Coord3D_AC16" => PixelFormat::Coord3DAC16,
158 "Coord3D_AC32f" => PixelFormat::Coord3DAC32f,
159 "Coord3D_ABC32f" => PixelFormat::Coord3DABC32f,
160 "BayerRG8" => PixelFormat::BayerRG8,
161 "BayerGB8" => PixelFormat::BayerGB8,
162 "BayerBG8" => PixelFormat::BayerBG8,
163 "BayerGR8" => PixelFormat::BayerGR8,
164 "BayerGR16" => PixelFormat::BayerGR16,
165 "BayerRG16" => PixelFormat::BayerRG16,
166 "BayerGB16" => PixelFormat::BayerGB16,
167 "BayerBG16" => PixelFormat::BayerBG16,
168 "RGB8Packed" | "RGB8" => PixelFormat::RGB8Packed,
169 "BGR8Packed" | "BGR8" => PixelFormat::BGR8Packed,
170 _ => PixelFormat::Unknown(0),
171 }
172 }
173
174 pub const fn is_bayer(self) -> bool {
176 matches!(
177 self,
178 PixelFormat::BayerRG8
179 | PixelFormat::BayerGB8
180 | PixelFormat::BayerBG8
181 | PixelFormat::BayerGR8
182 | PixelFormat::BayerGR16
183 | PixelFormat::BayerRG16
184 | PixelFormat::BayerGB16
185 | PixelFormat::BayerBG16
186 )
187 }
188
189 pub const fn cfa_pattern(self) -> Option<(&'static str, u8, u8)> {
195 match self {
196 PixelFormat::BayerRG8 => Some(("RGGB", 0, 0)),
197 PixelFormat::BayerGR8 => Some(("RGGB", 1, 0)),
198 PixelFormat::BayerGB8 => Some(("RGGB", 0, 1)),
199 PixelFormat::BayerBG8 => Some(("RGGB", 1, 1)),
200 _ => None,
201 }
202 }
203}
204
205impl fmt::Display for PixelFormat {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 match self {
208 PixelFormat::Mono8 => f.write_str("Mono8"),
209 PixelFormat::Mono10 => f.write_str("Mono10"),
210 PixelFormat::Mono12 => f.write_str("Mono12"),
211 PixelFormat::Mono14 => f.write_str("Mono14"),
212 PixelFormat::Mono16 => f.write_str("Mono16"),
213 PixelFormat::Confidence8 => f.write_str("Confidence8"),
214 PixelFormat::Coord3DC32f => f.write_str("Coord3D_C32f"),
215 PixelFormat::Coord3DAC16 => f.write_str("Coord3D_AC16"),
216 PixelFormat::Coord3DAC32f => f.write_str("Coord3D_AC32f"),
217 PixelFormat::Coord3DABC32f => f.write_str("Coord3D_ABC32f"),
218 PixelFormat::BayerRG8 => f.write_str("BayerRG8"),
219 PixelFormat::BayerGB8 => f.write_str("BayerGB8"),
220 PixelFormat::BayerBG8 => f.write_str("BayerBG8"),
221 PixelFormat::BayerGR8 => f.write_str("BayerGR8"),
222 PixelFormat::BayerGR16 => f.write_str("BayerGR16"),
223 PixelFormat::BayerRG16 => f.write_str("BayerRG16"),
224 PixelFormat::BayerGB16 => f.write_str("BayerGB16"),
225 PixelFormat::BayerBG16 => f.write_str("BayerBG16"),
226 PixelFormat::RGB8Packed => f.write_str("RGB8Packed"),
227 PixelFormat::BGR8Packed => f.write_str("BGR8Packed"),
228 PixelFormat::Unknown(code) => write!(f, "Unknown(0x{code:08X})"),
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::PixelFormat;
236
237 #[test]
238 fn roundtrip_known_codes() {
239 let formats = [
240 PixelFormat::Mono8,
241 PixelFormat::Mono10,
242 PixelFormat::Mono12,
243 PixelFormat::Mono14,
244 PixelFormat::Mono16,
245 PixelFormat::Confidence8,
246 PixelFormat::Coord3DC32f,
247 PixelFormat::Coord3DAC16,
248 PixelFormat::Coord3DAC32f,
249 PixelFormat::Coord3DABC32f,
250 PixelFormat::BayerRG8,
251 PixelFormat::BayerGB8,
252 PixelFormat::BayerBG8,
253 PixelFormat::BayerGR8,
254 PixelFormat::BayerGR16,
255 PixelFormat::BayerRG16,
256 PixelFormat::BayerGB16,
257 PixelFormat::BayerBG16,
258 PixelFormat::RGB8Packed,
259 PixelFormat::BGR8Packed,
260 ];
261
262 for fmt in formats {
263 let code = fmt.code();
264 assert_eq!(PixelFormat::from_code(code), fmt);
265 }
266 }
267
268 #[test]
269 fn unknown_code_roundtrip() {
270 let code = 0xDEAD_BEEF;
271 let fmt = PixelFormat::from_code(code);
272 assert!(matches!(fmt, PixelFormat::Unknown(value) if value == code));
273 assert_eq!(fmt.code(), code);
274 }
275
276 #[test]
277 fn bytes_per_pixel_matches_expectations() {
278 assert_eq!(PixelFormat::Mono8.bytes_per_pixel(), Some(1));
279 assert_eq!(PixelFormat::Mono10.bytes_per_pixel(), Some(2));
280 assert_eq!(PixelFormat::Mono16.bytes_per_pixel(), Some(2));
281 assert_eq!(PixelFormat::Confidence8.bytes_per_pixel(), Some(1));
282 assert_eq!(PixelFormat::Coord3DC32f.bytes_per_pixel(), Some(4));
283 assert_eq!(PixelFormat::Coord3DAC16.bytes_per_pixel(), Some(4));
284 assert_eq!(PixelFormat::Coord3DAC32f.bytes_per_pixel(), Some(8));
285 assert_eq!(PixelFormat::Coord3DABC32f.bytes_per_pixel(), Some(12));
286 assert_eq!(PixelFormat::RGB8Packed.bytes_per_pixel(), Some(3));
287 assert_eq!(PixelFormat::BayerRG8.bytes_per_pixel(), Some(1));
288 assert_eq!(PixelFormat::BayerRG16.bytes_per_pixel(), Some(2));
289 assert_eq!(PixelFormat::BayerGR16.bytes_per_pixel(), Some(2));
290 assert_eq!(PixelFormat::Unknown(0).bytes_per_pixel(), None);
291 }
292
293 #[test]
296 fn named_formats_agree_with_their_own_pfnc_code() {
297 let formats = [
298 PixelFormat::Mono8,
299 PixelFormat::Mono10,
300 PixelFormat::Mono12,
301 PixelFormat::Mono14,
302 PixelFormat::Mono16,
303 PixelFormat::Confidence8,
304 PixelFormat::Coord3DC32f,
305 PixelFormat::Coord3DAC16,
306 PixelFormat::Coord3DAC32f,
307 PixelFormat::Coord3DABC32f,
308 PixelFormat::BayerRG8,
309 PixelFormat::BayerGB8,
310 PixelFormat::BayerBG8,
311 PixelFormat::BayerGR8,
312 PixelFormat::BayerGR16,
313 PixelFormat::BayerRG16,
314 PixelFormat::BayerGB16,
315 PixelFormat::BayerBG16,
316 PixelFormat::RGB8Packed,
317 PixelFormat::BGR8Packed,
318 ];
319
320 for fmt in formats {
321 let disguised = PixelFormat::from_code((fmt.code() & 0xFFFF_0000) | 0xFFFF);
325 assert!(
326 matches!(disguised, PixelFormat::Unknown(_)),
327 "{fmt}: the disguise resolved to a named format"
328 );
329 assert_eq!(
330 fmt.bytes_per_pixel(),
331 disguised.bytes_per_pixel(),
332 "{fmt}: the hardcoded size disagrees with bits 23-16 of its own code"
333 );
334 }
335 }
336
337 #[test]
340 fn unknown_formats_are_sized_from_their_code() {
341 assert_eq!(
344 PixelFormat::from_code(0x0220_0016).bytes_per_pixel(),
345 Some(4)
346 );
347 assert_eq!(
348 PixelFormat::from_code(0x0230_0033).bytes_per_pixel(),
349 Some(6)
350 );
351 assert_eq!(
352 PixelFormat::from_code(0x0260_00C1).bytes_per_pixel(),
353 Some(12)
354 );
355 }
356
357 #[test]
364 fn packed_formats_report_no_whole_byte_size() {
365 for (name, code) in [
366 ("Mono10Packed", 0x010C_0004_u32),
367 ("Mono12Packed", 0x010C_0006),
368 ("YUV411Packed", 0x020C_001E),
369 ("BayerGR12Packed", 0x010C_002C),
370 ("BayerRG12Packed", 0x010C_002D),
371 ] {
372 assert_eq!(
373 PixelFormat::from_code(code).bytes_per_pixel(),
374 None,
375 "{name} declares 12 bits per pixel and must not be rounded"
376 );
377 }
378 }
379
380 #[test]
381 fn scancontrol_names_resolve_to_known_formats() {
382 let formats = [
383 ("Mono10", PixelFormat::Mono10),
384 ("Confidence8", PixelFormat::Confidence8),
385 ("Coord3D_C32f", PixelFormat::Coord3DC32f),
386 ("Coord3D_AC16", PixelFormat::Coord3DAC16),
387 ("Coord3D_AC32f", PixelFormat::Coord3DAC32f),
388 ("Coord3D_ABC32f", PixelFormat::Coord3DABC32f),
389 ];
390
391 for (name, format) in formats {
392 assert_eq!(PixelFormat::from_name(name), format);
393 assert_eq!(format.to_string(), name);
394 }
395 }
396
397 #[test]
398 fn cfa_offsets_align_to_rggb() {
399 assert_eq!(PixelFormat::BayerRG8.cfa_pattern(), Some(("RGGB", 0, 0)));
400 assert_eq!(PixelFormat::BayerGR8.cfa_pattern(), Some(("RGGB", 1, 0)));
401 assert_eq!(PixelFormat::BayerGB8.cfa_pattern(), Some(("RGGB", 0, 1)));
402 assert_eq!(PixelFormat::BayerBG8.cfa_pattern(), Some(("RGGB", 1, 1)));
403 assert_eq!(PixelFormat::Mono8.cfa_pattern(), None);
404 }
405}