use crate::frame::{
GeometryOverflow, InsufficientPlane, InsufficientStride, UnsupportedBits, WidthAlignment,
ZeroDimension,
};
use derive_more::{Display, IsVariant, TryUnwrap, Unwrap};
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Yuva420pFrameError {
#[error(transparent)]
ZeroDimension(ZeroDimension),
#[error(transparent)]
WidthAlignment(WidthAlignment),
#[error(transparent)]
InsufficientYStride(InsufficientStride),
#[error(transparent)]
InsufficientUStride(InsufficientStride),
#[error(transparent)]
InsufficientVStride(InsufficientStride),
#[error(transparent)]
InsufficientAStride(InsufficientStride),
#[error(transparent)]
InsufficientYPlane(InsufficientPlane),
#[error(transparent)]
InsufficientUPlane(InsufficientPlane),
#[error(transparent)]
InsufficientVPlane(InsufficientPlane),
#[error(transparent)]
InsufficientAPlane(InsufficientPlane),
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
}
#[derive(Debug, Clone, Copy)]
pub struct Yuva420pFrame<'a> {
y: &'a [u8],
u: &'a [u8],
v: &'a [u8],
a: &'a [u8],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
}
impl<'a> Yuva420pFrame<'a> {
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn try_new(
y: &'a [u8],
u: &'a [u8],
v: &'a [u8],
a: &'a [u8],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
) -> Result<Self, Yuva420pFrameError> {
if width == 0 || height == 0 {
return Err(Yuva420pFrameError::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if width & 1 != 0 {
return Err(Yuva420pFrameError::WidthAlignment(WidthAlignment::odd(
width as usize,
)));
}
if y_stride < width {
return Err(Yuva420pFrameError::InsufficientYStride(
InsufficientStride::new(y_stride, width),
));
}
let chroma_width = width.div_ceil(2);
if u_stride < chroma_width {
return Err(Yuva420pFrameError::InsufficientUStride(
InsufficientStride::new(u_stride, chroma_width),
));
}
if v_stride < chroma_width {
return Err(Yuva420pFrameError::InsufficientVStride(
InsufficientStride::new(v_stride, chroma_width),
));
}
if a_stride < width {
return Err(Yuva420pFrameError::InsufficientAStride(
InsufficientStride::new(a_stride, width),
));
}
let y_min = match (y_stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrameError::GeometryOverflow(GeometryOverflow::new(
y_stride, height,
)));
}
};
if y.len() < y_min {
return Err(Yuva420pFrameError::InsufficientYPlane(
InsufficientPlane::new(y_min, y.len()),
));
}
let chroma_height = height.div_ceil(2);
let u_min = match (u_stride as usize).checked_mul(chroma_height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrameError::GeometryOverflow(GeometryOverflow::new(
u_stride,
chroma_height,
)));
}
};
if u.len() < u_min {
return Err(Yuva420pFrameError::InsufficientUPlane(
InsufficientPlane::new(u_min, u.len()),
));
}
let v_min = match (v_stride as usize).checked_mul(chroma_height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrameError::GeometryOverflow(GeometryOverflow::new(
v_stride,
chroma_height,
)));
}
};
if v.len() < v_min {
return Err(Yuva420pFrameError::InsufficientVPlane(
InsufficientPlane::new(v_min, v.len()),
));
}
let a_min = match (a_stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrameError::GeometryOverflow(GeometryOverflow::new(
a_stride, height,
)));
}
};
if a.len() < a_min {
return Err(Yuva420pFrameError::InsufficientAPlane(
InsufficientPlane::new(a_min, a.len()),
));
}
Ok(Self {
y,
u,
v,
a,
width,
height,
y_stride,
u_stride,
v_stride,
a_stride,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn new(
y: &'a [u8],
u: &'a [u8],
v: &'a [u8],
a: &'a [u8],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
) -> Self {
match Self::try_new(
y, u, v, a, width, height, y_stride, u_stride, v_stride, a_stride,
) {
Ok(frame) => frame,
Err(_) => panic!("invalid Yuva420pFrame dimensions or plane lengths"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn y(&self) -> &'a [u8] {
self.y
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn u(&self) -> &'a [u8] {
self.u
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn v(&self) -> &'a [u8] {
self.v
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn a(&self) -> &'a [u8] {
self.a
}
#[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 y_stride(&self) -> u32 {
self.y_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn u_stride(&self) -> u32 {
self.u_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn v_stride(&self) -> u32 {
self.v_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn a_stride(&self) -> u32 {
self.a_stride
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Yuva420pFrame16Error {
#[error(transparent)]
UnsupportedBits(UnsupportedBits),
#[error(transparent)]
ZeroDimension(ZeroDimension),
#[error(transparent)]
WidthAlignment(WidthAlignment),
#[error(transparent)]
InsufficientYStride(InsufficientStride),
#[error(transparent)]
InsufficientUStride(InsufficientStride),
#[error(transparent)]
InsufficientVStride(InsufficientStride),
#[error(transparent)]
InsufficientAStride(InsufficientStride),
#[error(transparent)]
InsufficientYPlane(InsufficientPlane),
#[error(transparent)]
InsufficientUPlane(InsufficientPlane),
#[error(transparent)]
InsufficientVPlane(InsufficientPlane),
#[error(transparent)]
InsufficientAPlane(InsufficientPlane),
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
#[error(
"sample {} on plane {} at element {} exceeds {} ((1 << BITS) - 1)", .0.value(), .0.plane(), .0.index(), .0.max_valid()
)]
SampleOutOfRange(Yuva420pFrame16SampleOutOfRange),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display)]
pub enum Yuva420pFrame16Plane {
Y,
U,
V,
A,
}
#[derive(Debug, Clone, Copy)]
pub struct Yuva420pFrame16<'a, const BITS: u32, const BE: bool = false> {
y: &'a [u16],
u: &'a [u16],
v: &'a [u16],
a: &'a [u16],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
}
impl<'a, const BITS: u32, const BE: bool> Yuva420pFrame16<'a, BITS, BE> {
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn try_new(
y: &'a [u16],
u: &'a [u16],
v: &'a [u16],
a: &'a [u16],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
) -> Result<Self, Yuva420pFrame16Error> {
if BITS != 9 && BITS != 10 && BITS != 16 {
return Err(Yuva420pFrame16Error::UnsupportedBits(UnsupportedBits::new(
BITS,
)));
}
if width == 0 || height == 0 {
return Err(Yuva420pFrame16Error::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if width & 1 != 0 {
return Err(Yuva420pFrame16Error::WidthAlignment(WidthAlignment::odd(
width as usize,
)));
}
if y_stride < width {
return Err(Yuva420pFrame16Error::InsufficientYStride(
InsufficientStride::new(y_stride, width),
));
}
let chroma_width = width.div_ceil(2);
if u_stride < chroma_width {
return Err(Yuva420pFrame16Error::InsufficientUStride(
InsufficientStride::new(u_stride, chroma_width),
));
}
if v_stride < chroma_width {
return Err(Yuva420pFrame16Error::InsufficientVStride(
InsufficientStride::new(v_stride, chroma_width),
));
}
if a_stride < width {
return Err(Yuva420pFrame16Error::InsufficientAStride(
InsufficientStride::new(a_stride, width),
));
}
let y_min = match (y_stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrame16Error::GeometryOverflow(
GeometryOverflow::new(y_stride, height),
));
}
};
if y.len() < y_min {
return Err(Yuva420pFrame16Error::InsufficientYPlane(
InsufficientPlane::new(y_min, y.len()),
));
}
let chroma_height = height.div_ceil(2);
let u_min = match (u_stride as usize).checked_mul(chroma_height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrame16Error::GeometryOverflow(
GeometryOverflow::new(u_stride, chroma_height),
));
}
};
if u.len() < u_min {
return Err(Yuva420pFrame16Error::InsufficientUPlane(
InsufficientPlane::new(u_min, u.len()),
));
}
let v_min = match (v_stride as usize).checked_mul(chroma_height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrame16Error::GeometryOverflow(
GeometryOverflow::new(v_stride, chroma_height),
));
}
};
if v.len() < v_min {
return Err(Yuva420pFrame16Error::InsufficientVPlane(
InsufficientPlane::new(v_min, v.len()),
));
}
let a_min = match (a_stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(Yuva420pFrame16Error::GeometryOverflow(
GeometryOverflow::new(a_stride, height),
));
}
};
if a.len() < a_min {
return Err(Yuva420pFrame16Error::InsufficientAPlane(
InsufficientPlane::new(a_min, a.len()),
));
}
Ok(Self {
y,
u,
v,
a,
width,
height,
y_stride,
u_stride,
v_stride,
a_stride,
})
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub const fn new(
y: &'a [u16],
u: &'a [u16],
v: &'a [u16],
a: &'a [u16],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
) -> Self {
match Self::try_new(
y, u, v, a, width, height, y_stride, u_stride, v_stride, a_stride,
) {
Ok(frame) => frame,
Err(_) => panic!("invalid Yuva420pFrame16 dimensions or plane lengths"),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(clippy::too_many_arguments)]
pub fn try_new_checked(
y: &'a [u16],
u: &'a [u16],
v: &'a [u16],
a: &'a [u16],
width: u32,
height: u32,
y_stride: u32,
u_stride: u32,
v_stride: u32,
a_stride: u32,
) -> Result<Self, Yuva420pFrame16Error> {
let frame = Self::try_new(
y, u, v, a, width, height, y_stride, u_stride, v_stride, a_stride,
)?;
let max_valid: u16 = ((1u32 << BITS) - 1) as u16;
let w = width as usize;
let h = height as usize;
let chroma_w = w / 2;
let chroma_h = height.div_ceil(2) as usize;
for row in 0..h {
let start = row * y_stride as usize;
for (col, &s) in y[start..start + w].iter().enumerate() {
let logical = if BE { u16::from_be(s) } else { u16::from_le(s) };
if logical > max_valid {
return Err(Yuva420pFrame16Error::SampleOutOfRange(
Yuva420pFrame16SampleOutOfRange::new(
Yuva420pFrame16Plane::Y,
start + col,
logical,
max_valid,
),
));
}
}
}
for row in 0..chroma_h {
let start = row * u_stride as usize;
for (col, &s) in u[start..start + chroma_w].iter().enumerate() {
let logical = if BE { u16::from_be(s) } else { u16::from_le(s) };
if logical > max_valid {
return Err(Yuva420pFrame16Error::SampleOutOfRange(
Yuva420pFrame16SampleOutOfRange::new(
Yuva420pFrame16Plane::U,
start + col,
logical,
max_valid,
),
));
}
}
}
for row in 0..chroma_h {
let start = row * v_stride as usize;
for (col, &s) in v[start..start + chroma_w].iter().enumerate() {
let logical = if BE { u16::from_be(s) } else { u16::from_le(s) };
if logical > max_valid {
return Err(Yuva420pFrame16Error::SampleOutOfRange(
Yuva420pFrame16SampleOutOfRange::new(
Yuva420pFrame16Plane::V,
start + col,
logical,
max_valid,
),
));
}
}
}
for row in 0..h {
let start = row * a_stride as usize;
for (col, &s) in a[start..start + w].iter().enumerate() {
let logical = if BE { u16::from_be(s) } else { u16::from_le(s) };
if logical > max_valid {
return Err(Yuva420pFrame16Error::SampleOutOfRange(
Yuva420pFrame16SampleOutOfRange::new(
Yuva420pFrame16Plane::A,
start + col,
logical,
max_valid,
),
));
}
}
}
Ok(frame)
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn y(&self) -> &'a [u16] {
self.y
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn u(&self) -> &'a [u16] {
self.u
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn v(&self) -> &'a [u16] {
self.v
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn a(&self) -> &'a [u16] {
self.a
}
#[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 y_stride(&self) -> u32 {
self.y_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn u_stride(&self) -> u32 {
self.u_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn v_stride(&self) -> u32 {
self.v_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn a_stride(&self) -> u32 {
self.a_stride
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn bits(&self) -> u32 {
BITS
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn is_be(&self) -> bool {
BE
}
}
pub type Yuva420p9Frame<'a> = Yuva420pFrame16<'a, 9>;
pub type Yuva420p10Frame<'a> = Yuva420pFrame16<'a, 10>;
pub type Yuva420p16Frame<'a> = Yuva420pFrame16<'a, 16>;
pub type Yuva420p9LeFrame<'a> = Yuva420pFrame16<'a, 9, false>;
pub type Yuva420p9BeFrame<'a> = Yuva420pFrame16<'a, 9, true>;
pub type Yuva420p10LeFrame<'a> = Yuva420pFrame16<'a, 10, false>;
pub type Yuva420p10BeFrame<'a> = Yuva420pFrame16<'a, 10, true>;
pub type Yuva420p16LeFrame<'a> = Yuva420pFrame16<'a, 16, false>;
pub type Yuva420p16BeFrame<'a> = Yuva420pFrame16<'a, 16, true>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Yuva420pFrame16SampleOutOfRange {
plane: Yuva420pFrame16Plane,
index: usize,
value: u16,
max_valid: u16,
}
impl Yuva420pFrame16SampleOutOfRange {
#[inline]
pub const fn new(plane: Yuva420pFrame16Plane, index: usize, value: u16, max_valid: u16) -> Self {
Self {
plane,
index,
value,
max_valid,
}
}
#[inline]
pub const fn plane(&self) -> Yuva420pFrame16Plane {
self.plane
}
#[inline]
pub const fn index(&self) -> usize {
self.index
}
#[inline]
pub const fn value(&self) -> u16 {
self.value
}
#[inline]
pub const fn max_valid(&self) -> u16 {
self.max_valid
}
}