use crate::error::{Error, Result};
use crate::time::Micros;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PixelFormat {
Jpeg,
Gray8,
Rgb565,
Rgb888,
Bgr888,
Rgba8888,
Yuyv422,
Yuv420p,
Raw8,
}
impl PixelFormat {
#[must_use]
pub const fn packed_bits_per_pixel(self) -> Option<u32> {
match self {
PixelFormat::Gray8 | PixelFormat::Raw8 => Some(8),
PixelFormat::Rgb565 | PixelFormat::Yuyv422 => Some(16),
PixelFormat::Rgb888 | PixelFormat::Bgr888 => Some(24),
PixelFormat::Rgba8888 => Some(32),
PixelFormat::Jpeg | PixelFormat::Yuv420p => None,
}
}
#[must_use]
pub const fn is_compressed(self) -> bool {
matches!(self, PixelFormat::Jpeg)
}
#[must_use]
pub const fn is_planar(self) -> bool {
matches!(self, PixelFormat::Yuv420p)
}
#[must_use]
pub const fn tag(self) -> &'static str {
match self {
PixelFormat::Jpeg => "jpeg",
PixelFormat::Gray8 => "gray8",
PixelFormat::Rgb565 => "rgb565",
PixelFormat::Rgb888 => "rgb888",
PixelFormat::Bgr888 => "bgr888",
PixelFormat::Rgba8888 => "rgba8888",
PixelFormat::Yuyv422 => "yuyv422",
PixelFormat::Yuv420p => "yuv420p",
PixelFormat::Raw8 => "raw8",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Geometry {
pub width: u32,
pub height: u32,
pub format: PixelFormat,
}
impl Geometry {
pub const fn new(width: u32, height: u32, format: PixelFormat) -> Result<Self> {
if width == 0 || height == 0 {
return Err(Error::InvalidGeometry);
}
let even_w = width % 2 == 0;
let even_h = height % 2 == 0;
let ok = match format {
PixelFormat::Yuyv422 => even_w,
PixelFormat::Yuv420p => even_w && even_h,
_ => true,
};
if ok {
Ok(Geometry {
width,
height,
format,
})
} else {
Err(Error::InvalidGeometry)
}
}
#[must_use]
pub const fn byte_len(&self) -> Option<usize> {
let pixels = (self.width as u64) * (self.height as u64);
let bytes = match self.format {
PixelFormat::Yuv420p => pixels * 3 / 2,
PixelFormat::Jpeg => return None,
other => match other.packed_bits_per_pixel() {
Some(bits) => pixels * (bits as u64) / 8,
None => return None,
},
};
if bytes > usize::MAX as u64 {
None
} else {
Some(bytes as usize)
}
}
#[must_use]
pub const fn packed_stride(&self) -> Option<usize> {
match self.format.packed_bits_per_pixel() {
Some(bits) => Some((self.width as usize) * (bits as usize) / 8),
None => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Plane<'a> {
pub data: &'a [u8],
pub stride: usize,
}
impl<'a> Plane<'a> {
pub fn new(data: &'a [u8], stride: usize, rows: usize, row_bytes: usize) -> Result<Self> {
if row_bytes > stride || rows == 0 {
return Err(Error::InvalidGeometry);
}
let needed = stride
.checked_mul(rows - 1)
.and_then(|n| n.checked_add(row_bytes))
.ok_or(Error::InvalidGeometry)?;
if data.len() < needed {
return Err(Error::BufferTooSmall { needed });
}
Ok(Plane { data, stride })
}
#[must_use]
pub fn row(&self, index: usize) -> Option<&'a [u8]> {
let start = index.checked_mul(self.stride)?;
let end = start.checked_add(self.stride)?;
self.data.get(start..end)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Planes<'a> {
Packed(&'a [u8]),
Planar {
y: Plane<'a>,
u: Plane<'a>,
v: Plane<'a>,
},
}
#[derive(Debug, Clone, Copy)]
pub struct Frame<'a> {
pub geometry: Geometry,
pub timestamp: Micros,
pub sequence: u32,
pub planes: Planes<'a>,
}
impl<'a> Frame<'a> {
pub fn packed(
geometry: Geometry,
timestamp: Micros,
sequence: u32,
data: &'a [u8],
) -> Result<Self> {
if geometry.format.is_planar() {
return Err(Error::InvalidGeometry);
}
match geometry.byte_len() {
Some(needed) if data.len() < needed => return Err(Error::BufferTooSmall { needed }),
Some(_) => {}
None => {
if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
return Err(Error::InvalidFormat);
}
}
}
Ok(Frame {
geometry,
timestamp,
sequence,
planes: Planes::Packed(data),
})
}
pub fn yuv420p(
geometry: Geometry,
timestamp: Micros,
sequence: u32,
y: (&'a [u8], usize),
u: (&'a [u8], usize),
v: (&'a [u8], usize),
) -> Result<Self> {
if geometry.format != PixelFormat::Yuv420p {
return Err(Error::InvalidGeometry);
}
let (w, h) = (geometry.width as usize, geometry.height as usize);
let y = Plane::new(y.0, y.1, h, w)?;
let u = Plane::new(u.0, u.1, h / 2, w / 2)?;
let v = Plane::new(v.0, v.1, h / 2, w / 2)?;
Ok(Frame {
geometry,
timestamp,
sequence,
planes: Planes::Planar { y, u, v },
})
}
#[must_use]
pub fn coded(&self) -> Option<&'a [u8]> {
match self.planes {
Planes::Packed(data) if self.geometry.format.is_compressed() => Some(data),
_ => None,
}
}
#[must_use]
pub fn byte_len(&self) -> usize {
match self.planes {
Planes::Packed(data) => data.len(),
Planes::Planar { y, u, v } => y.data.len() + u.data.len() + v.data.len(),
}
}
}
#[derive(Debug)]
pub struct PlaneMut<'a> {
pub data: &'a mut [u8],
pub stride: usize,
}
impl<'a> PlaneMut<'a> {
pub fn new(data: &'a mut [u8], stride: usize, rows: usize, row_bytes: usize) -> Result<Self> {
Plane::new(data, stride, rows, row_bytes)?;
Ok(PlaneMut { data, stride })
}
#[must_use]
pub fn row_mut(&mut self, index: usize) -> Option<&mut [u8]> {
let start = index.checked_mul(self.stride)?;
let end = start.checked_add(self.stride)?;
self.data.get_mut(start..end)
}
#[must_use]
pub fn as_plane(&self) -> Plane<'_> {
Plane {
data: self.data,
stride: self.stride,
}
}
}
#[derive(Debug)]
pub enum PlanesMut<'a> {
Packed(&'a mut [u8]),
Planar {
y: PlaneMut<'a>,
u: PlaneMut<'a>,
v: PlaneMut<'a>,
},
}
#[derive(Debug)]
pub struct FrameMut<'a> {
pub geometry: Geometry,
pub timestamp: Micros,
pub sequence: u32,
pub planes: PlanesMut<'a>,
}
impl<'a> FrameMut<'a> {
pub fn packed(
geometry: Geometry,
timestamp: Micros,
sequence: u32,
data: &'a mut [u8],
) -> Result<Self> {
if geometry.format.is_planar() {
return Err(Error::InvalidGeometry);
}
if let Some(needed) = geometry.byte_len() {
if data.len() < needed {
return Err(Error::BufferTooSmall { needed });
}
}
Ok(FrameMut {
geometry,
timestamp,
sequence,
planes: PlanesMut::Packed(data),
})
}
pub fn yuv420p(
geometry: Geometry,
timestamp: Micros,
sequence: u32,
y: (&'a mut [u8], usize),
u: (&'a mut [u8], usize),
v: (&'a mut [u8], usize),
) -> Result<Self> {
if geometry.format != PixelFormat::Yuv420p {
return Err(Error::InvalidGeometry);
}
let (w, h) = (geometry.width as usize, geometry.height as usize);
let y = PlaneMut::new(y.0, y.1, h, w)?;
let u = PlaneMut::new(u.0, u.1, h / 2, w / 2)?;
let v = PlaneMut::new(v.0, v.1, h / 2, w / 2)?;
Ok(FrameMut {
geometry,
timestamp,
sequence,
planes: PlanesMut::Planar { y, u, v },
})
}
pub fn as_frame(&self) -> Result<Frame<'_>> {
match &self.planes {
PlanesMut::Packed(data) => {
Frame::packed(self.geometry, self.timestamp, self.sequence, data)
}
PlanesMut::Planar { y, u, v } => Ok(Frame {
geometry: self.geometry,
timestamp: self.timestamp,
sequence: self.sequence,
planes: Planes::Planar {
y: y.as_plane(),
u: u.as_plane(),
v: v.as_plane(),
},
}),
}
}
#[must_use]
pub fn packed_mut(&mut self) -> Option<&mut [u8]> {
match &mut self.planes {
PlanesMut::Packed(data) => Some(data),
PlanesMut::Planar { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_mut_is_written_then_viewed() {
let g = Geometry::new(4, 2, PixelFormat::Rgb888).unwrap();
let mut buf = [0u8; 4 * 2 * 3];
let mut f = FrameMut::packed(g, Micros(5), 9, &mut buf).unwrap();
f.packed_mut().unwrap().fill(0x42);
let view = f.as_frame().unwrap();
assert_eq!(view.byte_len(), 24);
assert!(matches!(view.planes, Planes::Packed(d) if d.iter().all(|&b| b == 0x42)));
assert!(FrameMut::packed(g, Micros(0), 0, &mut [0u8; 10]).is_err());
let gy = Geometry::new(4, 2, PixelFormat::Yuv420p).unwrap();
let (mut y, mut u, mut v) = ([0u8; 8], [0u8; 2], [0u8; 2]);
let mut p =
FrameMut::yuv420p(gy, Micros(1), 1, (&mut y, 4), (&mut u, 2), (&mut v, 2)).unwrap();
if let PlanesMut::Planar { y, .. } = &mut p.planes {
y.row_mut(1).unwrap().fill(7);
}
let view = p.as_frame().unwrap();
assert_eq!(view.byte_len(), 12);
if let Planes::Planar { y, .. } = view.planes {
assert_eq!(y.row(1).unwrap(), &[7, 7, 7, 7]);
} else {
panic!("planar");
}
assert!(p.packed_mut().is_none());
}
#[test]
fn geometry_byte_lengths() {
let g = Geometry::new(320, 240, PixelFormat::Rgb565).unwrap();
assert_eq!(g.byte_len(), Some(320 * 240 * 2));
assert_eq!(g.packed_stride(), Some(640));
let g = Geometry::new(320, 240, PixelFormat::Yuv420p).unwrap();
assert_eq!(g.byte_len(), Some(320 * 240 * 3 / 2));
let g = Geometry::new(320, 240, PixelFormat::Jpeg).unwrap();
assert_eq!(g.byte_len(), None);
}
#[test]
fn geometry_rejects_bad_dimensions() {
assert_eq!(
Geometry::new(0, 10, PixelFormat::Gray8),
Err(Error::InvalidGeometry)
);
assert_eq!(
Geometry::new(11, 10, PixelFormat::Yuyv422),
Err(Error::InvalidGeometry)
);
assert_eq!(
Geometry::new(10, 11, PixelFormat::Yuv420p),
Err(Error::InvalidGeometry)
);
assert!(Geometry::new(11, 11, PixelFormat::Gray8).is_ok());
}
#[test]
fn packed_frame_checks_buffer_size() {
let g = Geometry::new(4, 2, PixelFormat::Gray8).unwrap();
let buf = [0u8; 7];
assert_eq!(
Frame::packed(g, Micros::ZERO, 0, &buf).err(),
Some(Error::BufferTooSmall { needed: 8 })
);
let buf = [0u8; 8];
let f = Frame::packed(g, Micros::ZERO, 0, &buf).unwrap();
assert_eq!(f.byte_len(), 8);
assert!(f.coded().is_none());
}
#[test]
fn jpeg_frame_needs_soi() {
let g = Geometry::new(4, 2, PixelFormat::Jpeg).unwrap();
assert_eq!(
Frame::packed(g, Micros::ZERO, 0, &[0, 0]).err(),
Some(Error::InvalidFormat)
);
let f = Frame::packed(g, Micros::ZERO, 1, &[0xFF, 0xD8, 0xFF, 0xD9]).unwrap();
assert_eq!(f.coded().map(<[u8]>::len), Some(4));
}
#[test]
fn planar_frame_validates_each_plane() {
let g = Geometry::new(4, 2, PixelFormat::Yuv420p).unwrap();
let y = [0u8; 8];
let u = [0u8; 2];
let v = [0u8; 1];
assert_eq!(
Frame::yuv420p(g, Micros::ZERO, 0, (&y, 4), (&u, 2), (&v, 2)).err(),
Some(Error::BufferTooSmall { needed: 2 })
);
let v = [0u8; 2];
let f = Frame::yuv420p(g, Micros::ZERO, 0, (&y, 4), (&u, 2), (&v, 2)).unwrap();
assert_eq!(f.byte_len(), 12);
if let Planes::Planar { y, .. } = f.planes {
assert_eq!(y.row(1).map(<[u8]>::len), Some(4));
assert!(y.row(2).is_none());
} else {
panic!("expected planar");
}
}
}