use rusty_esp_core::error::{Error, Result};
use rusty_esp_core::frame::{Frame, Geometry, PixelFormat};
use rusty_esp_core::time::Micros;
pub trait ImageSource {
fn geometry(&self) -> Geometry;
fn grab<'b>(&mut self, out: &'b mut [u8]) -> Result<Frame<'b>>;
}
#[derive(Debug, Clone)]
pub struct TestPattern {
geometry: Geometry,
frame_micros: u64,
sequence: u32,
timestamp: Micros,
}
impl TestPattern {
pub fn new(geometry: Geometry, fps: u32) -> Result<Self> {
if fps == 0 {
return Err(Error::InvalidGeometry);
}
match geometry.format {
PixelFormat::Gray8 | PixelFormat::Rgb565 | PixelFormat::Rgb888 => Ok(TestPattern {
geometry,
frame_micros: 1_000_000 / u64::from(fps),
sequence: 0,
timestamp: Micros::ZERO,
}),
_ => Err(Error::Unsupported),
}
}
const BARS: [[u8; 3]; 7] = [
[0xC0, 0xC0, 0xC0], [0xC0, 0xC0, 0x00], [0x00, 0xC0, 0xC0], [0x00, 0xC0, 0x00], [0xC0, 0x00, 0xC0], [0xC0, 0x00, 0x00], [0x00, 0x00, 0xC0], ];
}
fn encode_pixel(colour: [u8; 3], bpp: usize, dst: &mut [u8; 3]) {
let [r, g, b] = colour;
match bpp {
1 => dst[0] = ((77 * u32::from(r) + 150 * u32::from(g) + 29 * u32::from(b)) >> 8) as u8,
2 => dst[..2].copy_from_slice(&crate::ops::pack_rgb565(r, g, b).to_le_bytes()),
_ => *dst = colour,
}
}
impl ImageSource for TestPattern {
fn geometry(&self) -> Geometry {
self.geometry
}
fn grab<'b>(&mut self, out: &'b mut [u8]) -> Result<Frame<'b>> {
let needed = self.geometry.byte_len().ok_or(Error::Unsupported)?;
if out.len() < needed {
return Err(Error::BufferTooSmall { needed });
}
let (w, h) = (self.geometry.width, self.geometry.height);
let bpp = match self.geometry.format {
PixelFormat::Gray8 => 1usize,
PixelFormat::Rgb565 => 2,
PixelFormat::Rgb888 => 3,
_ => return Err(Error::Unsupported),
};
let row = w as usize * bpp;
if row != 0 && h != 0 {
let marker_y = (self.sequence % h) as usize;
let buf = &mut out[..row * h as usize];
let mut solid = [0u8; 3];
encode_pixel([0xFF, 0xFF, 0xFF], bpp, &mut solid);
for px in buf[marker_y * row..(marker_y + 1) * row].chunks_exact_mut(bpp) {
px.copy_from_slice(&solid[..bpp]);
}
let bars_at = if marker_y != 0 {
0
} else if h > 1 {
1
} else {
usize::MAX
};
if bars_at != usize::MAX {
let (mut bar, mut acc) = (0usize, 0u32);
for px in buf[bars_at * row..(bars_at + 1) * row].chunks_exact_mut(bpp) {
let mut p = [0u8; 3];
encode_pixel(Self::BARS[bar.min(6)], bpp, &mut p);
px.copy_from_slice(&p[..bpp]);
acc += 7;
while acc >= w {
acc -= w;
bar += 1;
}
}
for y in 0..h as usize {
if y != marker_y && y != bars_at {
buf.copy_within(bars_at * row..(bars_at + 1) * row, y * row);
}
}
}
}
let frame = Frame::packed(self.geometry, self.timestamp, self.sequence, &out[..needed])?;
self.sequence = self.sequence.wrapping_add(1);
self.timestamp = self.timestamp.add_micros(self.frame_micros);
Ok(frame)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pattern_is_deterministic_and_advances() {
let g = Geometry::new(14, 4, PixelFormat::Rgb888).unwrap();
let mut a = TestPattern::new(g, 10).unwrap();
let mut b = TestPattern::new(g, 10).unwrap();
let mut ba = [0u8; 14 * 4 * 3];
let mut bb = [0u8; 14 * 4 * 3];
let fa = a.grab(&mut ba).unwrap();
assert_eq!(fa.sequence, 0);
assert_eq!(fa.timestamp, Micros::ZERO);
let fb = b.grab(&mut bb).unwrap();
assert_eq!(fa.byte_len(), fb.byte_len());
assert_eq!(ba, bb);
let f2 = a.grab(&mut ba).unwrap();
assert_eq!(f2.sequence, 1);
assert_eq!(f2.timestamp.as_millis(), 100);
assert_ne!(ba, bb);
let row1 = 14 * 3;
assert_eq!(&bb[row1..row1 + 3], &[0xC0, 0xC0, 0xC0]);
assert_eq!(&bb[..3], &[0xFF, 0xFF, 0xFF], "marker row of frame 0");
assert_eq!(&ba[row1..row1 + 3], &[0xFF, 0xFF, 0xFF]);
}
#[test]
fn rejects_unsupported_and_small_buffers() {
let g = Geometry::new(8, 8, PixelFormat::Jpeg).unwrap();
assert_eq!(TestPattern::new(g, 10).err(), Some(Error::Unsupported));
let g = Geometry::new(8, 8, PixelFormat::Gray8).unwrap();
assert_eq!(TestPattern::new(g, 0).err(), Some(Error::InvalidGeometry));
let mut p = TestPattern::new(g, 30).unwrap();
let mut small = [0u8; 10];
assert_eq!(
p.grab(&mut small).err(),
Some(Error::BufferTooSmall { needed: 64 })
);
}
}