1use std::time::SystemTime;
4
5use bytes::Bytes;
6use pfnc::PixelFormat;
7use tracing::debug;
8
9use crate::chunks::{ChunkKind, ChunkMap, ChunkValue};
10
11#[derive(Debug, Clone)]
13pub struct Frame {
14 pub payload: Bytes,
16 pub width: u32,
18 pub height: u32,
20 pub pixel_format: PixelFormat,
22 pub chunks: Option<ChunkMap>,
24 pub ts_dev: Option<u64>,
26 pub ts_host: Option<SystemTime>,
28}
29
30impl Frame {
31 pub fn chunk(&self, kind: ChunkKind) -> Option<&ChunkValue> {
33 self.chunks.as_ref()?.get(&kind)
34 }
35
36 pub fn host_time(&self) -> Option<SystemTime> {
38 self.ts_host
39 }
40
41 pub fn as_rgb8(&self) -> Option<&[u8]> {
43 match self.pixel_format {
44 PixelFormat::RGB8Packed => Some(self.payload.as_ref()),
45 _ => None,
46 }
47 }
48
49 pub fn to_rgb8(&self) -> Result<Vec<u8>, crate::GenicamError> {
51 if let Some(rgb) = self.as_rgb8() {
52 return Ok(rgb.to_vec());
53 }
54
55 match self.pixel_format {
56 PixelFormat::Mono8 => self.mono8_to_rgb8(),
57 PixelFormat::Mono16 => self.mono16_to_rgb8(),
58 PixelFormat::BGR8Packed => self.bgr8_to_rgb8(),
59 PixelFormat::BayerRG8
60 | PixelFormat::BayerGB8
61 | PixelFormat::BayerBG8
62 | PixelFormat::BayerGR8 => self.bayer_to_rgb8(),
63 PixelFormat::RGB8Packed => unreachable!("handled by as_rgb8 fast path"),
64 PixelFormat::Unknown(_) => Err(crate::GenicamError::UnsupportedPixelFormat(
65 self.pixel_format,
66 )),
67 }
68 }
69
70 fn total_pixels(&self) -> Result<usize, crate::GenicamError> {
71 let width = usize::try_from(self.width)
72 .map_err(|_| crate::GenicamError::Parse("frame width exceeds address space".into()))?;
73 let height = usize::try_from(self.height)
74 .map_err(|_| crate::GenicamError::Parse("frame height exceeds address space".into()))?;
75 width
76 .checked_mul(height)
77 .ok_or_else(|| crate::GenicamError::Parse("frame dimensions overflow".into()))
78 }
79
80 fn expect_payload_len(&self, expected: usize, fmt: &str) -> Result<(), crate::GenicamError> {
81 if self.payload.len() != expected {
82 return Err(crate::GenicamError::Parse(format!(
83 "payload length {} does not match {} expectation {}",
84 self.payload.len(),
85 fmt,
86 expected
87 )));
88 }
89 Ok(())
90 }
91
92 fn mono8_to_rgb8(&self) -> Result<Vec<u8>, crate::GenicamError> {
93 let pixels = self.total_pixels()?;
94 self.expect_payload_len(pixels, "Mono8")?;
95 debug!(
96 width = self.width,
97 height = self.height,
98 "converting Mono8 frame to RGB8"
99 );
100 let mut out = Vec::with_capacity(pixels * 3);
101 for &value in self.payload.as_ref() {
102 out.extend_from_slice(&[value, value, value]);
103 }
104 Ok(out)
105 }
106
107 fn mono16_to_rgb8(&self) -> Result<Vec<u8>, crate::GenicamError> {
108 let pixels = self.total_pixels()?;
109 let expected = pixels
110 .checked_mul(2)
111 .ok_or_else(|| crate::GenicamError::Parse("Mono16 payload overflow".into()))?;
112 self.expect_payload_len(expected, "Mono16")?;
113 debug!(
114 width = self.width,
115 height = self.height,
116 "converting Mono16 frame to RGB8"
117 );
118 let mut out = Vec::with_capacity(pixels * 3);
119 let data = self.payload.as_ref();
120 for idx in 0..pixels {
121 let hi = data[idx * 2 + 1];
122 out.extend_from_slice(&[hi, hi, hi]);
123 }
124 Ok(out)
125 }
126
127 fn bgr8_to_rgb8(&self) -> Result<Vec<u8>, crate::GenicamError> {
128 let pixels = self.total_pixels()?;
129 let expected = pixels
130 .checked_mul(3)
131 .ok_or_else(|| crate::GenicamError::Parse("BGR8 payload overflow".into()))?;
132 self.expect_payload_len(expected, "BGR8")?;
133 debug!(
134 width = self.width,
135 height = self.height,
136 "converting BGR8 frame to RGB8"
137 );
138 let mut out = Vec::with_capacity(expected);
139 for chunk in self.payload.chunks_exact(3) {
140 out.extend_from_slice(&[chunk[2], chunk[1], chunk[0]]);
141 }
142 Ok(out)
143 }
144
145 fn bayer_to_rgb8(&self) -> Result<Vec<u8>, crate::GenicamError> {
146 let pixels = self.total_pixels()?;
147 self.expect_payload_len(pixels, "Bayer8")?;
148 let (pattern, x_offset, y_offset) =
149 self.pixel_format
150 .cfa_pattern()
151 .ok_or(crate::GenicamError::UnsupportedPixelFormat(
152 self.pixel_format,
153 ))?;
154 debug!(
155 width = self.width,
156 height = self.height,
157 pattern,
158 x_offset,
159 y_offset,
160 "demosaicing Bayer frame"
161 );
162
163 let width = usize::try_from(self.width)
164 .map_err(|_| crate::GenicamError::Parse("frame width exceeds address space".into()))?;
165 let height = usize::try_from(self.height)
166 .map_err(|_| crate::GenicamError::Parse("frame height exceeds address space".into()))?;
167 let src = self.payload.as_ref();
168 let mut out = vec![0u8; width * height * 3];
169
170 for y in 0..height {
171 for x in 0..width {
172 let dst_idx = (y * width + x) * 3;
173 let (r, g, b) = demosaic_pixel(src, width, height, x, y, x_offset, y_offset);
174 out[dst_idx] = r;
175 out[dst_idx + 1] = g;
176 out[dst_idx + 2] = b;
177 }
178 }
179
180 Ok(out)
181 }
182}
183
184fn demosaic_pixel(
185 src: &[u8],
186 width: usize,
187 height: usize,
188 x: usize,
189 y: usize,
190 x_offset: u8,
191 y_offset: u8,
192) -> (u8, u8, u8) {
193 use core::cmp::min;
194
195 let clamp = |value: isize, upper: usize| -> usize {
196 if value < 0 {
197 0
198 } else {
199 min(value as usize, upper.saturating_sub(1))
200 }
201 };
202
203 let sample = |sx: isize, sy: isize| -> u8 {
204 let cx = clamp(sx, width);
205 let cy = clamp(sy, height);
206 src[cy * width + cx]
207 };
208
209 let x = x as isize;
210 let y = y as isize;
211 let ox = x_offset as isize;
212 let oy = y_offset as isize;
213 let mx = ((x + ox) & 1) as i32;
214 let my = ((y + oy) & 1) as i32;
215
216 match (mx, my) {
217 (0, 0) => {
218 let r = sample(x, y);
219 let g1 = sample(x + 1, y);
220 let g2 = sample(x, y + 1);
221 let b = sample(x + 1, y + 1);
222 let g = ((g1 as u16 + g2 as u16) / 2) as u8;
223 (r, g, b)
224 }
225 (1, 1) => {
226 let r = sample(x - 1, y - 1);
227 let g1 = sample(x - 1, y);
228 let g2 = sample(x, y - 1);
229 let b = sample(x, y);
230 let g = ((g1 as u16 + g2 as u16) / 2) as u8;
231 (r, g, b)
232 }
233 (1, 0) => {
234 let r = sample(x - 1, y);
235 let g = sample(x, y);
236 let b = sample(x, y + 1);
237 (r, g, b)
238 }
239 (0, 1) => {
240 let r = sample(x, y - 1);
241 let g = sample(x, y);
242 let b = sample(x + 1, y);
243 (r, g, b)
244 }
245 _ => unreachable!(),
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use bytes::Bytes;
253
254 fn frame_with_payload(
255 payload: &[u8],
256 width: u32,
257 height: u32,
258 pixel_format: PixelFormat,
259 ) -> Frame {
260 Frame {
261 payload: Bytes::copy_from_slice(payload),
262 width,
263 height,
264 pixel_format,
265 chunks: None,
266 ts_dev: None,
267 ts_host: None,
268 }
269 }
270
271 #[test]
272 fn mono8_converts_to_rgb8() {
273 let payload = [0u8, 64, 128, 255];
274 let frame = frame_with_payload(&payload, 2, 2, PixelFormat::Mono8);
275 let rgb = frame.to_rgb8().expect("mono conversion");
276 assert_eq!(rgb.len(), 12);
277 assert_eq!(&rgb[0..3], &[0, 0, 0]);
278 assert_eq!(&rgb[3..6], &[64, 64, 64]);
279 assert_eq!(&rgb[9..12], &[255, 255, 255]);
280 }
281
282 #[test]
283 fn rgb8_fast_path_borrows_payload() {
284 let payload = vec![1u8, 2, 3, 4, 5, 6];
285 let frame = frame_with_payload(&payload, 1, 2, PixelFormat::RGB8Packed);
286 assert_eq!(frame.as_rgb8().unwrap(), payload.as_slice());
287 let owned = frame.to_rgb8().expect("rgb copy");
288 assert_eq!(owned, payload);
289 }
290
291 #[test]
292 fn bayer_rg8_demosaic_basic_pattern() {
293 let payload = [
295 255, 32, 255, 32, 32, 16, 32, 240, 255, 32, 255, 32, 32, 16, 32, 240,
298 ];
299 let frame = frame_with_payload(&payload, 4, 4, PixelFormat::BayerRG8);
300 let rgb = frame.to_rgb8().expect("bayer conversion");
301 assert_eq!(rgb.len(), 4 * 4 * 3);
302 assert!(rgb[0] > rgb[1] && rgb[0] > rgb[2]);
304 let last = &rgb[rgb.len() - 3..];
306 assert_eq!(last[2], 240);
307 }
308}