1use rusty_esp_core::error::{Error, Result};
9use rusty_esp_core::frame::{Frame, Geometry, PixelFormat};
10use rusty_esp_core::time::Micros;
11
12pub trait ImageSource {
14 fn geometry(&self) -> Geometry;
16
17 fn grab<'b>(&mut self, out: &'b mut [u8]) -> Result<Frame<'b>>;
21}
22
23#[derive(Debug, Clone)]
28pub struct TestPattern {
29 geometry: Geometry,
30 frame_micros: u64,
31 sequence: u32,
32 timestamp: Micros,
33}
34
35impl TestPattern {
36 pub fn new(geometry: Geometry, fps: u32) -> Result<Self> {
40 if fps == 0 {
41 return Err(Error::InvalidGeometry);
42 }
43 match geometry.format {
44 PixelFormat::Gray8 | PixelFormat::Rgb565 | PixelFormat::Rgb888 => Ok(TestPattern {
45 geometry,
46 frame_micros: 1_000_000 / u64::from(fps),
47 sequence: 0,
48 timestamp: Micros::ZERO,
49 }),
50 _ => Err(Error::Unsupported),
51 }
52 }
53
54 const BARS: [[u8; 3]; 7] = [
56 [0xC0, 0xC0, 0xC0], [0xC0, 0xC0, 0x00], [0x00, 0xC0, 0xC0], [0x00, 0xC0, 0x00], [0xC0, 0x00, 0xC0], [0xC0, 0x00, 0x00], [0x00, 0x00, 0xC0], ];
64
65 fn colour_at(&self, x: u32, y: u32) -> [u8; 3] {
66 let w = self.geometry.width;
67 let h = self.geometry.height;
68 let marker_y = self.sequence % h.max(1);
70 if y == marker_y {
71 return [0xFF, 0xFF, 0xFF];
72 }
73 let bar = ((u64::from(x) * 7) / u64::from(w.max(1))) as usize;
74 Self::BARS[bar.min(6)]
75 }
76}
77
78impl ImageSource for TestPattern {
79 fn geometry(&self) -> Geometry {
80 self.geometry
81 }
82
83 fn grab<'b>(&mut self, out: &'b mut [u8]) -> Result<Frame<'b>> {
84 let needed = self.geometry.byte_len().ok_or(Error::Unsupported)?;
85 if out.len() < needed {
86 return Err(Error::BufferTooSmall { needed });
87 }
88 let (w, h) = (self.geometry.width, self.geometry.height);
89 let mut i = 0usize;
90 for y in 0..h {
91 for x in 0..w {
92 let [r, g, b] = self.colour_at(x, y);
93 match self.geometry.format {
94 PixelFormat::Gray8 => {
95 out[i] = ((77 * u32::from(r) + 150 * u32::from(g) + 29 * u32::from(b)) >> 8)
97 as u8;
98 i += 1;
99 }
100 PixelFormat::Rgb565 => {
101 let p = crate::ops::pack_rgb565(r, g, b);
102 out[i..i + 2].copy_from_slice(&p.to_le_bytes());
103 i += 2;
104 }
105 PixelFormat::Rgb888 => {
106 out[i..i + 3].copy_from_slice(&[r, g, b]);
107 i += 3;
108 }
109 _ => return Err(Error::Unsupported),
110 }
111 }
112 }
113 let frame = Frame::packed(self.geometry, self.timestamp, self.sequence, &out[..needed])?;
114 self.sequence = self.sequence.wrapping_add(1);
115 self.timestamp = self.timestamp.add_micros(self.frame_micros);
116 Ok(frame)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn pattern_is_deterministic_and_advances() {
126 let g = Geometry::new(14, 4, PixelFormat::Rgb888).unwrap();
127 let mut a = TestPattern::new(g, 10).unwrap();
128 let mut b = TestPattern::new(g, 10).unwrap();
129 let mut ba = [0u8; 14 * 4 * 3];
130 let mut bb = [0u8; 14 * 4 * 3];
131 let fa = a.grab(&mut ba).unwrap();
132 assert_eq!(fa.sequence, 0);
133 assert_eq!(fa.timestamp, Micros::ZERO);
134 let fb = b.grab(&mut bb).unwrap();
135 assert_eq!(fa.byte_len(), fb.byte_len());
136 assert_eq!(ba, bb);
137 let f2 = a.grab(&mut ba).unwrap();
139 assert_eq!(f2.sequence, 1);
140 assert_eq!(f2.timestamp.as_millis(), 100);
141 assert_ne!(ba, bb);
142 let row1 = 14 * 3;
144 assert_eq!(&bb[row1..row1 + 3], &[0xC0, 0xC0, 0xC0]);
145 assert_eq!(&bb[..3], &[0xFF, 0xFF, 0xFF], "marker row of frame 0");
146 assert_eq!(&ba[row1..row1 + 3], &[0xFF, 0xFF, 0xFF]);
148 }
149
150 #[test]
151 fn rejects_unsupported_and_small_buffers() {
152 let g = Geometry::new(8, 8, PixelFormat::Jpeg).unwrap();
153 assert_eq!(TestPattern::new(g, 10).err(), Some(Error::Unsupported));
154 let g = Geometry::new(8, 8, PixelFormat::Gray8).unwrap();
155 assert_eq!(TestPattern::new(g, 0).err(), Some(Error::InvalidGeometry));
156 let mut p = TestPattern::new(g, 30).unwrap();
157 let mut small = [0u8; 10];
158 assert_eq!(
159 p.grab(&mut small).err(),
160 Some(Error::BufferTooSmall { needed: 64 })
161 );
162 }
163}