use crate::frame::WidthAlignment;
use super::{
GeometryOverflow, InsufficientPlane, InsufficientStride, UnsupportedBits, WidthOverflow,
ZeroDimension,
};
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use thiserror::Error;
#[derive(Debug, Clone, Copy)]
pub struct Y2xxFrame<'a, const BITS: u32, const BE: bool = false> {
packed: &'a [u16],
width: u32,
height: u32,
stride: u32,
}
pub type Y210Frame<'a> = Y2xxFrame<'a, 10, false>;
pub type Y210LeFrame<'a> = Y2xxFrame<'a, 10, false>;
pub type Y210BeFrame<'a> = Y2xxFrame<'a, 10, true>;
pub type Y212Frame<'a> = Y2xxFrame<'a, 12, false>;
pub type Y212LeFrame<'a> = Y2xxFrame<'a, 12, false>;
pub type Y212BeFrame<'a> = Y2xxFrame<'a, 12, true>;
pub type Y216Frame<'a> = Y2xxFrame<'a, 16, false>;
pub type Y216LeFrame<'a> = Y2xxFrame<'a, 16, false>;
pub type Y216BeFrame<'a> = Y2xxFrame<'a, 16, true>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Y2xxFrameError {
#[error(transparent)]
UnsupportedBits(UnsupportedBits),
#[error(transparent)]
ZeroDimension(ZeroDimension),
#[error(transparent)]
WidthAlignment(WidthAlignment),
#[error(transparent)]
InsufficientStride(InsufficientStride),
#[error(transparent)]
InsufficientPlane(InsufficientPlane),
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
#[error(transparent)]
WidthOverflow(WidthOverflow),
#[error("Y2xxFrame: sample with non-zero low bits found; expected MSB-aligned data")]
SampleLowBitsSet,
}
impl<'a, const BITS: u32, const BE: bool> Y2xxFrame<'a, BITS, BE> {
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(
packed: &'a [u16],
width: u32,
height: u32,
stride: u32,
) -> Result<Self, Y2xxFrameError> {
if BITS != 10 && BITS != 12 && BITS != 16 {
return Err(Y2xxFrameError::UnsupportedBits(UnsupportedBits::new(BITS)));
}
if width == 0 || height == 0 {
return Err(Y2xxFrameError::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if !width.is_multiple_of(2) {
return Err(Y2xxFrameError::WidthAlignment(WidthAlignment::odd(
width as usize,
)));
}
let min_stride = match width.checked_mul(2) {
Some(n) => n,
None => return Err(Y2xxFrameError::WidthOverflow(WidthOverflow::new(width))),
};
if stride < min_stride {
return Err(Y2xxFrameError::InsufficientStride(InsufficientStride::new(
stride, width,
)));
}
let plane_min = match (stride as usize).checked_mul(height as usize) {
Some(n) => n,
None => {
return Err(Y2xxFrameError::GeometryOverflow(GeometryOverflow::new(
stride, height,
)));
}
};
if packed.len() < plane_min {
return Err(Y2xxFrameError::InsufficientPlane(InsufficientPlane::new(
plane_min,
packed.len(),
)));
}
Ok(Self {
packed,
width,
height,
stride,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub fn try_new_checked(
packed: &'a [u16],
width: u32,
height: u32,
stride: u32,
) -> Result<Self, Y2xxFrameError> {
let frame = Self::try_new(packed, width, height, stride)?;
if BITS < 16 {
let low_mask: u16 = (1u16 << (16 - BITS)) - 1;
let row_elems = (width * 2) as usize;
let h = height as usize;
let stride_us = stride as usize;
for row in 0..h {
let start = row * stride_us;
for &sample in &packed[start..start + row_elems] {
let host = if BE {
u16::from_be(sample)
} else {
u16::from_le(sample)
};
if host & low_mask != 0 {
return Err(Y2xxFrameError::SampleLowBitsSet);
}
}
}
}
Ok(frame)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(packed: &'a [u16], width: u32, height: u32, stride: u32) -> Self {
match Self::try_new(packed, width, height, stride) {
Ok(f) => f,
Err(e) => match e {
Y2xxFrameError::UnsupportedBits(_) => panic!("invalid Y2xxFrame: unsupported BITS"),
Y2xxFrameError::ZeroDimension(_) => panic!("invalid Y2xxFrame: zero dimension"),
Y2xxFrameError::WidthAlignment(_) => panic!("invalid Y2xxFrame: odd width"),
Y2xxFrameError::InsufficientStride(_) => panic!("invalid Y2xxFrame: stride too small"),
Y2xxFrameError::InsufficientPlane(_) => panic!("invalid Y2xxFrame: plane too short"),
Y2xxFrameError::GeometryOverflow(_) => panic!("invalid Y2xxFrame: geometry overflow"),
Y2xxFrameError::WidthOverflow(_) => panic!("invalid Y2xxFrame: width overflow"),
Y2xxFrameError::SampleLowBitsSet => {
panic!("invalid Y2xxFrame: sample low bits set (unreachable from try_new)")
}
},
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn packed(&self) -> &'a [u16] {
self.packed
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> u32 {
self.width
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> u32 {
self.height
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stride(&self) -> u32 {
self.stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_be(&self) -> bool {
BE
}
}