#[cfg(test)]
mod tests;
use std::num::{NonZeroU8, NonZeroUsize};
use crate::{
chroma::ChromaSubsampling,
error::Error,
pixel::Pixel,
plane::{Plane, PlaneGeometry},
};
#[derive(Clone)]
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,
}
pub struct FrameBuilder {
width: NonZeroUsize,
height: NonZeroUsize,
subsampling: ChromaSubsampling,
bit_depth: NonZeroU8,
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: NonZeroUsize,
height: NonZeroUsize,
subsampling: ChromaSubsampling,
bit_depth: NonZeroU8,
) -> 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>, Error> {
if self.bit_depth.get() < 8 || self.bit_depth.get() > 16 {
return Err(Error::UnsupportedBitDepth {
found: self.bit_depth.get(),
});
}
let byte_width = size_of::<T>();
assert!(
byte_width <= 2,
"unsupported pixel byte width: {byte_width}"
);
if (byte_width == 1 && self.bit_depth.get() != 8)
|| (byte_width == 2 && self.bit_depth.get() <= 8)
{
return Err(Error::DataTypeMismatch);
}
let luma_stride = self
.width
.saturating_add(self.luma_padding_left)
.saturating_add(self.luma_padding_right);
let luma_geometry = PlaneGeometry {
width: self.width,
height: self.height,
stride: luma_stride,
pad_left: self.luma_padding_left,
pad_right: self.luma_padding_right,
pad_top: self.luma_padding_top,
pad_bottom: self.luma_padding_bottom,
subsampling_x: NonZeroU8::new(1).expect("non-zero constant"),
subsampling_y: NonZeroU8::new(1).expect("non-zero constant"),
};
if !self.subsampling.has_chroma() {
return Ok(Frame {
y_plane: Plane::new(luma_geometry),
u_plane: None,
v_plane: None,
subsampling: self.subsampling,
bit_depth: self.bit_depth,
});
}
let Some((chroma_width, chroma_height)) = self
.subsampling
.chroma_dimensions(self.width.get(), self.height.get())
else {
return Err(Error::UnsupportedResolution);
};
let (ss_x, ss_y) = self.subsampling.subsample_ratio().expect("not monochrome");
if self.luma_padding_left % ss_x.get() as usize > 0
|| self.luma_padding_right % ss_x.get() as usize > 0
|| self.luma_padding_top % ss_y.get() as usize > 0
|| self.luma_padding_bottom % ss_y.get() as usize > 0
{
return Err(Error::UnsupportedResolution);
}
let chroma_padding_left = self.luma_padding_left / ss_x.get() as usize;
let chroma_padding_right = self.luma_padding_right / ss_x.get() as usize;
let chroma_padding_top = self.luma_padding_top / ss_y.get() as usize;
let chroma_padding_bottom = self.luma_padding_bottom / ss_y.get() as usize;
let chroma_stride = chroma_width
.saturating_add(chroma_padding_left)
.saturating_add(chroma_padding_right);
let chroma_geometry = PlaneGeometry {
width: NonZeroUsize::new(chroma_width).expect("cannot be zero"),
height: NonZeroUsize::new(chroma_height).expect("cannot be zero"),
stride: NonZeroUsize::new(chroma_stride).expect("cannot be zero"),
pad_left: chroma_padding_left,
pad_right: chroma_padding_right,
pad_top: chroma_padding_top,
pad_bottom: chroma_padding_bottom,
subsampling_x: ss_x,
subsampling_y: ss_y,
};
Ok(Frame {
y_plane: Plane::new(luma_geometry),
u_plane: Some(Plane::new(chroma_geometry)),
v_plane: Some(Plane::new(chroma_geometry)),
subsampling: self.subsampling,
bit_depth: self.bit_depth,
})
}
}