mod error;
pub use error::FrameError;
#[cfg(test)]
mod tests;
use core::num::NonZeroU8;
use crate::{
chroma::ChromaSubsampling,
pixel::Pixel,
plane::{Plane, PlaneGeometry},
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frame<T: Pixel> {
pub y_plane: Plane<T>,
pub u_plane: Option<Plane<T>>,
pub v_plane: Option<Plane<T>>,
pub subsampling: ChromaSubsampling,
pub bit_depth: NonZeroU8,
}
impl<T: Pixel> Frame<T> {
#[inline]
#[must_use]
pub fn plane(&self, index: usize) -> Option<&Plane<T>> {
match index {
0 => Some(&self.y_plane),
1 => self.u_plane.as_ref(),
2 => self.v_plane.as_ref(),
_ => None,
}
}
#[inline]
#[must_use]
pub fn plane_mut(&mut self, index: usize) -> Option<&mut Plane<T>> {
match index {
0 => Some(&mut self.y_plane),
1 => self.u_plane.as_mut(),
2 => self.v_plane.as_mut(),
_ => None,
}
}
}
pub struct FrameBuilder {
width: usize,
height: usize,
subsampling: ChromaSubsampling,
bit_depth: u8,
luma_padding_left: usize,
luma_padding_right: usize,
luma_padding_top: usize,
luma_padding_bottom: usize,
}
impl FrameBuilder {
#[inline]
#[must_use]
pub fn new(width: usize, height: usize, subsampling: ChromaSubsampling, bit_depth: u8) -> Self {
Self {
width,
height,
subsampling,
bit_depth,
luma_padding_left: 0,
luma_padding_right: 0,
luma_padding_top: 0,
luma_padding_bottom: 0,
}
}
#[inline]
#[must_use]
pub fn luma_padding_left(mut self, luma_padding_left: usize) -> Self {
self.luma_padding_left = luma_padding_left;
self
}
#[inline]
#[must_use]
pub fn luma_padding_right(mut self, luma_padding_right: usize) -> Self {
self.luma_padding_right = luma_padding_right;
self
}
#[inline]
#[must_use]
pub fn luma_padding_top(mut self, luma_padding_top: usize) -> Self {
self.luma_padding_top = luma_padding_top;
self
}
#[inline]
#[must_use]
pub fn luma_padding_bottom(mut self, luma_padding_bottom: usize) -> Self {
self.luma_padding_bottom = luma_padding_bottom;
self
}
#[inline]
pub fn build<T: Pixel>(self) -> Result<Frame<T>, FrameError> {
let byte_width = const {
let sz = size_of::<T>();
assert!(sz > 0 && sz <= 2, "T must have a size of 1 or 2 bytes");
sz
};
if self.bit_depth < 8 || self.bit_depth > 16 {
return Err(FrameError::UnsupportedBitDepth {
found: self.bit_depth,
});
}
if (byte_width == 1 && self.bit_depth != 8) || (byte_width == 2 && self.bit_depth <= 8) {
return Err(FrameError::DataTypeMismatch);
}
let Some(luma_geometry) = PlaneGeometry::new(
self.width,
self.height,
self.luma_padding_left,
self.luma_padding_right,
self.luma_padding_top,
self.luma_padding_bottom,
1,
1,
) else {
return Err(FrameError::UnsupportedResolution);
};
let (u_plane, v_plane) = match luma_geometry.for_subsampling(self.subsampling) {
Ok(Some(g)) => (Some(Plane::new(g)), Some(Plane::new(g))),
Ok(None) => (None, None),
Err(_) => return Err(FrameError::UnsupportedResolution),
};
Ok(Frame {
y_plane: Plane::new(luma_geometry),
u_plane,
v_plane,
subsampling: self.subsampling,
bit_depth: NonZeroU8::new(self.bit_depth)
.ok_or(FrameError::UnsupportedBitDepth { found: 0 })?,
})
}
}