1use std::time::SystemTime;
2
3use crate::error::CaptureError;
4use crate::region::PxRect;
5
6#[derive(Debug, Clone)]
8pub struct Frame {
9 width: u32,
10 height: u32,
11 data: Vec<u8>,
12 timestamp: SystemTime,
13}
14
15impl Frame {
16 pub fn new(width: u32, height: u32, data: Vec<u8>) -> Result<Self, CaptureError> {
17 let expected = width as u64 * height as u64 * 4;
18 if data.len() as u64 != expected {
19 return Err(CaptureError::InvalidFrameData {
20 width,
21 height,
22 expected: expected as usize,
23 actual: data.len(),
24 });
25 }
26 Ok(Self {
27 width,
28 height,
29 data,
30 timestamp: SystemTime::now(),
31 })
32 }
33
34 pub fn solid(width: u32, height: u32, bgra: [u8; 4]) -> Self {
39 let data = bgra.repeat(width as usize * height as usize);
40 Self {
41 width,
42 height,
43 data,
44 timestamp: SystemTime::now(),
45 }
46 }
47
48 pub fn from_fn(width: u32, height: u32, mut f: impl FnMut(u32, u32) -> [u8; 4]) -> Self {
55 let mut data = Vec::with_capacity(width as usize * height as usize * 4);
56 for y in 0..height {
57 for x in 0..width {
58 data.extend_from_slice(&f(x, y));
59 }
60 }
61 Self {
62 width,
63 height,
64 data,
65 timestamp: SystemTime::now(),
66 }
67 }
68
69 pub fn width(&self) -> u32 {
70 self.width
71 }
72 pub fn height(&self) -> u32 {
73 self.height
74 }
75 pub fn data(&self) -> &[u8] {
76 &self.data
77 }
78 pub fn timestamp(&self) -> SystemTime {
79 self.timestamp
80 }
81
82 pub fn view(&self, rect: PxRect) -> Result<FrameView<'_>, CaptureError> {
84 let in_bounds = rect.w > 0
85 && rect.h > 0
86 && rect.x.checked_add(rect.w).is_some_and(|r| r <= self.width)
87 && rect.y.checked_add(rect.h).is_some_and(|b| b <= self.height);
88 if !in_bounds {
89 return Err(CaptureError::InvalidRegion(format!(
90 "{rect:?} out of bounds for {}x{}",
91 self.width, self.height
92 )));
93 }
94 Ok(FrameView { frame: self, rect })
95 }
96}
97
98pub struct FrameView<'a> {
100 frame: &'a Frame,
101 rect: PxRect,
102}
103
104impl<'a> FrameView<'a> {
105 pub fn width(&self) -> u32 {
106 self.rect.w
107 }
108 pub fn height(&self) -> u32 {
109 self.rect.h
110 }
111 pub fn rect(&self) -> PxRect {
112 self.rect
113 }
114
115 pub fn rows(&self) -> impl Iterator<Item = &'a [u8]> + '_ {
117 let fw = self.frame.width as usize;
118 let data: &'a [u8] = &self.frame.data;
119 let PxRect { x, y, w, h } = self.rect;
120 (y..y + h).map(move |row| {
121 let start = (row as usize * fw + x as usize) * 4;
122 &data[start..start + w as usize * 4]
123 })
124 }
125
126 pub fn to_vec(&self) -> Vec<u8> {
128 let mut out = Vec::with_capacity(self.rect.w as usize * self.rect.h as usize * 4);
129 for row in self.rows() {
130 out.extend_from_slice(row);
131 }
132 out
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn new_validates_byte_length() {
142 let ok = Frame::new(2, 2, vec![0u8; 16]);
143 assert!(ok.is_ok());
144
145 let err = Frame::new(2, 2, vec![0u8; 15]).unwrap_err();
146 assert!(matches!(
147 err,
148 CaptureError::InvalidFrameData {
149 expected: 16,
150 actual: 15,
151 ..
152 }
153 ));
154 }
155
156 #[test]
157 fn solid_fills_every_pixel() {
158 let f = Frame::solid(3, 2, [1, 2, 3, 4]);
159 assert_eq!(f.width(), 3);
160 assert_eq!(f.height(), 2);
161 assert_eq!(f.data().len(), 24);
162 assert!(f.data().chunks(4).all(|px| px == [1, 2, 3, 4]));
163 }
164
165 #[test]
166 fn view_crops_rows_correctly() {
167 let data: Vec<u8> = (0..12u8).flat_map(|n| [n, n, n, 255]).collect();
169 let f = Frame::new(4, 3, data).unwrap();
170 let view = f
171 .view(PxRect {
172 x: 1,
173 y: 1,
174 w: 2,
175 h: 2,
176 })
177 .unwrap();
178
179 assert_eq!(view.width(), 2);
180 assert_eq!(view.height(), 2);
181 let rows: Vec<&[u8]> = view.rows().collect();
182 assert_eq!(rows.len(), 2);
183 assert_eq!(rows[0], &[5, 5, 5, 255, 6, 6, 6, 255][..]);
185 assert_eq!(rows[1], &[9, 9, 9, 255, 10, 10, 10, 255][..]);
187 }
188
189 #[test]
190 fn view_to_vec_is_contiguous() {
191 let f = Frame::solid(4, 4, [7, 8, 9, 255]);
192 let view = f
193 .view(PxRect {
194 x: 0,
195 y: 0,
196 w: 2,
197 h: 2,
198 })
199 .unwrap();
200 assert_eq!(view.to_vec().len(), 16);
201 }
202
203 #[test]
204 fn view_rejects_out_of_bounds_rect() {
205 let f = Frame::solid(4, 4, [0, 0, 0, 255]);
206 assert!(f
207 .view(PxRect {
208 x: 3,
209 y: 0,
210 w: 2,
211 h: 1
212 })
213 .is_err());
214 }
215
216 #[test]
217 fn from_fn_evaluates_every_pixel() {
218 let f = Frame::from_fn(3, 2, |x, y| [x as u8, y as u8, 7, 255]);
219 assert_eq!(f.width(), 3);
220 assert_eq!(f.height(), 2);
221 assert_eq!(&f.data()[20..24], &[2, 1, 7, 255]);
223 }
224}