use std::{
iter::FusedIterator,
marker::PhantomData,
num::{NonZeroU8, NonZeroUsize},
ops::{Index, IndexMut, Range},
slice,
};
use v_frame::{
frame::FrameBuilder,
pixel::Pixel,
plane::{Plane, PlaneGeometry},
};
use super::block::{BLOCK_TO_PLANE_SHIFT, BlockOffset};
#[derive(Clone, Copy, Debug, Default)]
pub struct PlaneOffset {
pub x: isize,
pub y: isize,
}
#[derive(Debug)]
pub struct PlaneRegion<'a, T: Pixel> {
data: *const T, pub plane_cfg: PlaneGeometry,
rect: Rect,
phantom: PhantomData<&'a T>,
}
#[derive(Debug)]
pub struct PlaneRegionMut<'a, T: Pixel> {
data: *mut T, pub plane_cfg: PlaneGeometry,
rect: Rect,
phantom: PhantomData<&'a mut T>,
}
macro_rules! plane_region_common {
($name:ident, $as_ptr:ident $(,$opt_mut:tt)?) => {
impl<'a, T: Pixel> $name<'a, T> {
#[cold]
#[allow(dead_code)]
pub fn empty(plane_cfg: PlaneGeometry) -> Self {
return Self {
data: std::ptr::null_mut::<T>(),
plane_cfg,
rect: Rect::default(),
phantom: PhantomData,
}
}
pub fn from_slice(data: &'a $($opt_mut)? [T], cfg: PlaneGeometry, rect: Rect) -> Self {
assert!(rect.x >= -(cfg.pad_left as isize));
assert!(rect.y >= -(cfg.pad_top as isize));
assert!(cfg.pad_left as isize + rect.x + rect.width as isize <= cfg.stride.get() as isize);
assert!(cfg.pad_top as isize + rect.y + rect.height as isize <= cfg.alloc_height().get() as isize);
unsafe { Self::from_slice_unsafe(data, cfg, rect)}
}
unsafe fn from_slice_unsafe(data: &'a $($opt_mut)? [T], cfg: PlaneGeometry, rect: Rect) -> Self {
let origin = (cfg.pad_top as isize + rect.y) * cfg.stride.get() as isize + cfg.pad_left as isize + rect.x;
Self {
data: unsafe { data.$as_ptr().offset(origin) },
plane_cfg: cfg,
rect,
phantom: PhantomData,
}
}
#[allow(dead_code)]
pub fn data_ptr(&self) -> *const T {
self.data
}
pub fn rect(&self) -> &Rect {
&self.rect
}
#[allow(dead_code)]
pub fn rows_iter(&self) -> PlaneRegionRowsIter<'_, T> {
PlaneRegionRowsIter {
data: self.data,
stride: self.plane_cfg.stride.get(),
width: self.rect.width,
remaining: self.rect.height,
phantom: PhantomData,
}
}
#[allow(dead_code)]
pub fn vert_windows(&self, h: usize) -> VertWindows<'_, T> {
VertWindows {
data: self.data,
plane_cfg: self.plane_cfg,
remaining: (self.rect.height as isize - h as isize + 1).max(0) as usize,
output_rect: Rect {
x: self.rect.x,
y: self.rect.y,
width: self.rect.width,
height: h
},
phantom: PhantomData,
}
}
#[allow(dead_code)]
pub fn horz_windows(&self, w: usize) -> HorzWindows<'_, T> {
HorzWindows {
data: self.data,
plane_cfg: self.plane_cfg,
remaining: (self.rect.width as isize - w as isize + 1).max(0) as usize,
output_rect: Rect {
x: self.rect.x,
y: self.rect.y,
width: w,
height: self.rect.height
},
phantom: PhantomData,
}
}
#[allow(dead_code)]
pub fn subregion(&self, area: Area) -> PlaneRegion<'_, T> {
if self.data.is_null() {
return PlaneRegion::empty(self.plane_cfg);
}
let rect = area.to_rect(
self.plane_cfg.subsampling_x.get() as usize >> 1,
self.plane_cfg.subsampling_y.get() as usize >> 1,
self.rect.width,
self.rect.height,
);
assert!(rect.x >= 0 && rect.x as usize <= self.rect.width);
assert!(rect.y >= 0 && rect.y as usize <= self.rect.height);
let data = unsafe {
self.data.add(rect.y as usize * self.plane_cfg.stride.get() + rect.x as usize)
};
let absolute_rect = Rect {
x: self.rect.x + rect.x,
y: self.rect.y + rect.y,
width: rect.width,
height: rect.height,
};
PlaneRegion {
data,
plane_cfg: self.plane_cfg,
rect: absolute_rect,
phantom: PhantomData,
}
}
}
unsafe impl<T: Pixel> Send for $name<'_, T> {}
unsafe impl<T: Pixel> Sync for $name<'_, T> {}
impl<T: Pixel> Index<usize> for $name<'_, T> {
type Output = [T];
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.rect.height);
unsafe {
let ptr = self.data.add(index * self.plane_cfg.stride.get());
slice::from_raw_parts(ptr, self.rect.width)
}
}
}
}
}
plane_region_common!(PlaneRegion, as_ptr);
plane_region_common!(PlaneRegionMut, as_mut_ptr, mut);
impl<'a, T: Pixel> PlaneRegion<'a, T> {
pub fn new(plane: &'a Plane<T>, rect: Rect) -> Self {
let geometry = plane.geometry();
Self::from_slice(plane.data(), geometry, rect)
}
pub fn new_from_plane(plane: &'a Plane<T>) -> Self {
let geometry = plane.geometry();
let rect = Rect {
x: 0,
y: 0,
width: geometry.stride.get() - geometry.pad_left,
height: geometry.alloc_height().get() - geometry.pad_top,
};
unsafe { Self::from_slice_unsafe(plane.data(), geometry, rect) }
}
}
impl<'a, T: Pixel> PlaneRegionMut<'a, T> {
pub fn new(plane: &'a mut Plane<T>, rect: Rect) -> Self {
let geometry = plane.geometry();
Self::from_slice(plane.data_mut(), geometry, rect)
}
#[expect(clippy::needless_pass_by_ref_mut)]
pub fn data_ptr_mut(&mut self) -> *mut T {
self.data
}
#[expect(clippy::needless_pass_by_ref_mut)]
pub fn rows_iter_mut(&mut self) -> PlaneRegionRowsIterMut<'_, T> {
PlaneRegionRowsIterMut {
data: self.data,
stride: self.plane_cfg.stride.get(),
width: self.rect.width,
remaining: self.rect.height,
phantom: PhantomData,
}
}
pub fn as_const(&self) -> PlaneRegion<'_, T> {
PlaneRegion {
data: self.data,
plane_cfg: self.plane_cfg,
rect: self.rect,
phantom: PhantomData,
}
}
}
impl<T: Pixel> IndexMut<usize> for PlaneRegionMut<'_, T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
assert!(index < self.rect.height);
unsafe {
let ptr = self.data.add(index * self.plane_cfg.stride.get());
slice::from_raw_parts_mut(ptr, self.rect.width)
}
}
}
pub struct PlaneRegionRowsIter<'a, T: Pixel> {
data: *const T,
stride: usize,
width: usize,
remaining: usize,
phantom: PhantomData<&'a T>,
}
impl<'a, T: Pixel> Iterator for PlaneRegionRowsIter<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
(self.remaining > 0).then(|| {
let row = unsafe {
let ptr = self.data;
self.data = self.data.add(self.stride);
slice::from_raw_parts(ptr, self.width)
};
self.remaining -= 1;
row
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
pub struct PlaneRegionRowsIterMut<'a, T: Pixel> {
data: *mut T,
stride: usize,
width: usize,
remaining: usize,
phantom: PhantomData<&'a mut T>,
}
impl<'a, T: Pixel> Iterator for PlaneRegionRowsIterMut<'a, T> {
type Item = &'a mut [T];
fn next(&mut self) -> Option<Self::Item> {
(self.remaining > 0).then(|| {
let row = unsafe {
let ptr = self.data;
self.data = self.data.add(self.stride);
slice::from_raw_parts_mut(ptr, self.width)
};
self.remaining -= 1;
row
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<T: Pixel> ExactSizeIterator for PlaneRegionRowsIter<'_, T> {
}
impl<T: Pixel> FusedIterator for PlaneRegionRowsIter<'_, T> {
}
impl<T: Pixel> ExactSizeIterator for PlaneRegionRowsIterMut<'_, T> {
}
impl<T: Pixel> FusedIterator for PlaneRegionRowsIterMut<'_, T> {
}
pub struct VertWindows<'a, T: Pixel> {
data: *const T,
plane_cfg: PlaneGeometry,
remaining: usize,
output_rect: Rect,
phantom: PhantomData<&'a T>,
}
pub struct HorzWindows<'a, T: Pixel> {
data: *const T,
plane_cfg: PlaneGeometry,
remaining: usize,
output_rect: Rect,
phantom: PhantomData<&'a T>,
}
impl<'a, T: Pixel> Iterator for VertWindows<'a, T> {
type Item = PlaneRegion<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
self.nth(0)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
(self.remaining > n).then(|| {
self.data = unsafe { self.data.add(self.plane_cfg.stride.get() * n) };
self.output_rect.y += n as isize;
let output = PlaneRegion {
data: self.data,
plane_cfg: self.plane_cfg,
rect: self.output_rect,
phantom: PhantomData,
};
self.data = unsafe { self.data.add(self.plane_cfg.stride.get()) };
self.output_rect.y += 1;
self.remaining -= n + 1;
output
})
}
}
impl<'a, T: Pixel> Iterator for HorzWindows<'a, T> {
type Item = PlaneRegion<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
self.nth(0)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
(self.remaining > n).then(|| {
self.data = unsafe { self.data.add(n) };
self.output_rect.x += n as isize;
let output = PlaneRegion {
data: self.data,
plane_cfg: self.plane_cfg,
rect: self.output_rect,
phantom: PhantomData,
};
self.data = unsafe { self.data.add(1) };
self.output_rect.x += 1;
self.remaining -= n + 1;
output
})
}
}
impl<T: Pixel> ExactSizeIterator for VertWindows<'_, T> {
}
impl<T: Pixel> FusedIterator for VertWindows<'_, T> {
}
impl<T: Pixel> ExactSizeIterator for HorzWindows<'_, T> {
}
impl<T: Pixel> FusedIterator for HorzWindows<'_, T> {
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Rect {
pub x: isize,
pub y: isize,
pub width: usize,
pub height: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum Area {
Rect(Rect),
StartingAt { x: isize, y: isize },
BlockStartingAt { bo: BlockOffset },
}
impl Area {
pub const fn to_rect(
self,
xdec: usize,
ydec: usize,
parent_width: usize,
parent_height: usize,
) -> Rect {
match self {
Area::Rect(rect) => rect,
Area::StartingAt { x, y } => Rect {
x,
y,
width: (parent_width as isize - x) as usize,
height: (parent_height as isize - y) as usize,
},
Area::BlockStartingAt { bo } => {
let x = (bo.x >> xdec << BLOCK_TO_PLANE_SHIFT) as isize;
let y = (bo.y >> ydec << BLOCK_TO_PLANE_SHIFT) as isize;
Rect {
x,
y,
width: (parent_width as isize - x) as usize,
height: (parent_height as isize - y) as usize,
}
}
}
}
}
pub trait AsRegion<T: Pixel> {
fn as_region(&self) -> PlaneRegion<'_, T>;
fn region(&self, area: Area) -> PlaneRegion<'_, T>;
fn region_mut(&mut self, area: Area) -> PlaneRegionMut<'_, T>;
}
impl<T: Pixel> AsRegion<T> for Plane<T> {
fn as_region(&self) -> PlaneRegion<'_, T> {
PlaneRegion::new_from_plane(self)
}
fn region(&self, area: Area) -> PlaneRegion<'_, T> {
let geometry = self.geometry();
let rect = area.to_rect(
geometry.subsampling_x.get() as usize >> 1,
geometry.subsampling_y.get() as usize >> 1,
geometry.stride.get() - geometry.pad_left,
geometry.alloc_height().get() - geometry.pad_top,
);
PlaneRegion::new(self, rect)
}
fn region_mut(&mut self, area: Area) -> PlaneRegionMut<'_, T> {
let geometry = self.geometry();
let rect = area.to_rect(
geometry.subsampling_x.get() as usize >> 1,
geometry.subsampling_y.get() as usize >> 1,
geometry.stride.get() - geometry.pad_left,
geometry.alloc_height().get() - geometry.pad_top,
);
PlaneRegionMut::new(self, rect)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PlaneBlockOffset(pub BlockOffset);
impl PlaneBlockOffset {
pub const fn to_luma_plane_offset(self) -> PlaneOffset {
self.0.to_luma_plane_offset()
}
}
#[derive(Clone, Copy)]
pub struct PlaneSlice<'a, T: Pixel> {
pub plane: &'a Plane<T>,
pub x: isize,
pub y: isize,
}
impl<'a, T: Pixel> PlaneSlice<'a, T> {
pub fn as_ptr(&self) -> *const T {
self[0].as_ptr()
}
pub fn clamp(&self) -> PlaneSlice<'a, T> {
PlaneSlice {
plane: self.plane,
x: self.x.clamp(
-(self.plane.geometry().pad_left as isize),
self.plane.geometry().width.get() as isize,
),
y: self.y.clamp(
-(self.plane.geometry().pad_top as isize),
self.plane.geometry().height.get() as isize,
),
}
}
pub fn subslice(&self, xo: usize, yo: usize) -> PlaneSlice<'a, T> {
PlaneSlice {
plane: self.plane,
x: self.x + xo as isize,
y: self.y + yo as isize,
}
}
#[allow(dead_code)]
pub fn go_up(&self, i: usize) -> PlaneSlice<'a, T> {
PlaneSlice {
plane: self.plane,
x: self.x,
y: self.y - i as isize,
}
}
#[allow(dead_code)]
pub fn go_left(&self, i: usize) -> PlaneSlice<'a, T> {
PlaneSlice {
plane: self.plane,
x: self.x - i as isize,
y: self.y,
}
}
pub fn accessible(&self, add_x: usize, add_y: usize) -> bool {
let y = (self.y + add_y as isize + self.plane.geometry().pad_top as isize) as usize;
let x = (self.x + add_x as isize + self.plane.geometry().pad_left as isize) as usize;
y < self.plane.geometry().alloc_height().get() && x < self.plane.geometry().stride.get()
}
pub fn accessible_neg(&self, sub_x: usize, sub_y: usize) -> bool {
let y = self.y - sub_y as isize + self.plane.geometry().pad_top as isize;
let x = self.x - sub_x as isize + self.plane.geometry().pad_left as isize;
y >= 0 && x >= 0
}
}
impl<T: Pixel> Index<usize> for PlaneSlice<'_, T> {
type Output = [T];
fn index(&self, index: usize) -> &Self::Output {
let range = row_range(self.plane.geometry(), self.x, self.y + index as isize);
&self.plane.data()[range]
}
}
pub(crate) fn plane_to_plane_slice<T: Pixel>(
plane: &Plane<T>,
po: PlaneOffset,
) -> PlaneSlice<'_, T> {
PlaneSlice {
plane,
x: po.x,
y: po.y,
}
}
fn row_range(geometry: PlaneGeometry, x: isize, y: isize) -> Range<usize> {
debug_assert!(geometry.pad_top as isize + y >= 0);
debug_assert!(geometry.pad_left as isize + x >= 0);
let base_y = (geometry.pad_top as isize + y) as usize;
let base_x = (geometry.pad_left as isize + x) as usize;
let base = base_y * geometry.stride.get() + base_x;
let width = geometry.stride.get() - base_x;
base..base + width
}
pub(crate) fn downscale<T: Pixel, const SCALE: usize>(
plane: &Plane<T>,
bit_depth: NonZeroU8,
) -> Plane<T> {
let new_frame = FrameBuilder::new(
NonZeroUsize::new(plane.width().get() / SCALE)
.expect("cannot downscale a plane with width < SCALE"),
NonZeroUsize::new(plane.height().get() / SCALE)
.expect("cannot downscale a plane with height < SCALE"),
v_frame::chroma::ChromaSubsampling::Monochrome,
bit_depth,
)
.build()
.expect("should be able to build new frame");
let mut new_plane = new_frame.y_plane;
downscale_in_place::<T, SCALE>(plane, &mut new_plane);
new_plane
}
pub(crate) fn downscale_in_place<T: Pixel, const SCALE: usize>(
plane: &Plane<T>,
in_plane: &mut Plane<T>,
) {
let stride = in_plane.geometry().stride.get();
let width = in_plane.width().get();
let height = in_plane.height().get();
assert!(width * SCALE <= plane.geometry().stride.get() - plane.geometry().pad_left);
assert!(height * SCALE <= plane.geometry().alloc_height().get() - plane.geometry().pad_top);
unsafe {
let src = plane;
let box_pixels = SCALE * SCALE;
let half_box_pixels = box_pixels as u32 / 2;
let data_origin = &src.data()[src.data_origin()..];
let plane_data_mut_slice = in_plane.data_mut();
let src_stride = src.geometry().stride.get();
for row_idx in 0..height {
let dst_row = plane_data_mut_slice.get_unchecked_mut(row_idx * stride..);
for (col_idx, dst) in dst_row.get_unchecked_mut(..width).iter_mut().enumerate() {
macro_rules! generate_inner_loop {
($x:ty) => {
let mut sum = half_box_pixels as $x;
for y in 0..SCALE {
let src_row_idx = row_idx * SCALE + y;
let src_row = data_origin.get_unchecked((src_row_idx * src_stride)..);
for x in 0..SCALE {
let src_col_idx = col_idx * SCALE + x;
pastey::paste! {
sum += src_row.get_unchecked(src_col_idx).[<to_ $x>]().expect("value should fit into integer");
}
}
}
let avg = sum as usize / box_pixels;
*dst = T::from(avg).expect("value should fit into Pixel");
};
}
if size_of::<T>() == 1
&& SCALE as u128 * SCALE as u128 * (u8::MAX as u128) + half_box_pixels as u128
<= u16::MAX as u128
{
generate_inner_loop!(u16);
} else {
generate_inner_loop!(u32);
}
}
}
}
}