use rusty_esp_core::error::{Error, Result};
use rusty_esp_core::frame::{Geometry, PixelFormat};
pub use rusty_esp_dsp::pixel::{
downscale2x_gray8, downscale2x_rgb565, pack_rgb565, rgb565_to_rgb888, rgb888_to_rgb565,
unpack_rgb565, yuv_to_rgb, yuyv_to_gray8, yuyv_to_rgb565, yuyv_to_rgb888,
};
fn expect_len(buf: &[u8], needed: usize) -> Result<()> {
if buf.len() < needed {
Err(Error::BufferTooSmall { needed })
} else {
Ok(())
}
}
pub fn crop(
src: &[u8],
geometry: Geometry,
x: u32,
y: u32,
w: u32,
h: u32,
dst: &mut [u8],
) -> Result<Geometry> {
let bpp = geometry
.format
.packed_bits_per_pixel()
.ok_or(Error::Unsupported)? as usize
/ 8;
if x.checked_add(w).is_none_or(|r| r > geometry.width)
|| y.checked_add(h).is_none_or(|b| b > geometry.height)
{
return Err(Error::InvalidGeometry);
}
let out = Geometry::new(w, h, geometry.format)?;
expect_len(src, geometry.byte_len().ok_or(Error::Unsupported)?)?;
let row_out = w as usize * bpp;
expect_len(dst, row_out * h as usize)?;
let stride = geometry.width as usize * bpp;
for row in 0..h as usize {
let s = (y as usize + row) * stride + x as usize * bpp;
dst[row * row_out..(row + 1) * row_out].copy_from_slice(&src[s..s + row_out]);
}
Ok(out)
}
pub fn rotate90_gray8(src: &[u8], width: u32, height: u32, dst: &mut [u8]) -> Result<Geometry> {
let (w, h) = (width as usize, height as usize);
expect_len(src, w * h)?;
expect_len(dst, w * h)?;
if w != 0 && h != 0 {
let dst = &mut dst[..w * h];
let src = &src[..w * h];
if pie::rotate90_gray8(src, width, height, dst) {
return Geometry::new(height, width, PixelFormat::Gray8);
}
const T: usize = 8;
let mut x0 = 0;
while x0 < w {
let xe = (x0 + T).min(w);
let mut y0 = 0;
while y0 < h {
let ye = (y0 + T).min(h);
for x in x0..xe {
let col = &mut dst[x * h..x * h + h];
for y in y0..ye {
col[h - 1 - y] = src[y * w + x];
}
}
y0 += T;
}
x0 += T;
}
}
Geometry::new(height, width, PixelFormat::Gray8)
}
pub fn rotate180(src: &[u8], bytes_per_pixel: usize, dst: &mut [u8]) -> Result<usize> {
if bytes_per_pixel == 0 || src.len() % bytes_per_pixel != 0 {
return Err(Error::InvalidGeometry);
}
expect_len(dst, src.len())?;
let n = src.len() / bytes_per_pixel;
let dst = &mut dst[..src.len()];
macro_rules! reversed {
($bpp:expr) => {{
for (d, s) in dst.chunks_exact_mut($bpp).rev().zip(src.chunks_exact($bpp)) {
d.copy_from_slice(s);
}
}};
}
match bytes_per_pixel {
1 => reversed!(1),
2 => reversed!(2),
3 => reversed!(3),
4 => reversed!(4),
bpp => reversed!(bpp),
}
Ok(n)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn downscale_crop_rotate() {
let src = [0u8, 4, 8, 12, 4, 8, 12, 16];
let mut dst = [0u8; 2];
let g = downscale2x_gray8(&src, 4, 2, &mut dst).unwrap();
assert_eq!((g.width, g.height), (2, 1));
assert_eq!(dst, [4, 12]);
let c = pack_rgb565(200, 100, 50).to_le_bytes();
let src: std::vec::Vec<u8> = c.iter().copied().cycle().take(8).collect();
let mut dst = [0u8; 2];
downscale2x_rgb565(&src, 2, 2, &mut dst).unwrap();
assert_eq!(dst, c);
let src = [1u8, 2, 3, 4, 5, 6];
let geo = Geometry::new(3, 2, PixelFormat::Gray8).unwrap();
let mut dst = [0u8; 2];
let g = crop(&src, geo, 1, 0, 1, 2, &mut dst).unwrap();
assert_eq!((g.width, g.height), (1, 2));
assert_eq!(dst, [2, 5]);
assert_eq!(
crop(&src, geo, 3, 0, 1, 1, &mut dst),
Err(Error::InvalidGeometry)
);
let mut r = [0u8; 6];
let g = rotate90_gray8(&src, 3, 2, &mut r).unwrap();
assert_eq!((g.width, g.height), (2, 3));
assert_eq!(r, [4, 1, 5, 2, 6, 3]);
let mut r = [0u8; 6];
rotate180(&src, 1, &mut r).unwrap();
assert_eq!(r, [6, 5, 4, 3, 2, 1]);
let mut r2 = [0u8; 6];
rotate180(&[1, 2, 3, 4, 5, 6], 2, &mut r2).unwrap();
assert_eq!(r2, [5, 6, 3, 4, 1, 2]);
}
}
mod pie {
#[cfg(all(feature = "pie-s3", target_arch = "xtensa"))]
pub fn rotate90_gray8(src: &[u8], width: u32, height: u32, dst: &mut [u8]) -> bool {
rusty_esp_dsp_esp::pie_s3::rotate90_gray8(src, width, height, dst).is_ok()
}
#[cfg(not(all(feature = "pie-s3", target_arch = "xtensa")))]
pub fn rotate90_gray8(_: &[u8], _: u32, _: u32, _: &mut [u8]) -> bool {
false
}
}