#[cfg(test)]
mod tests;
use std::{
iter,
num::{NonZeroU8, NonZeroUsize},
};
use aligned_vec::{ABox, AVec, ConstAlign};
use crate::{error::Error, pixel::Pixel};
#[cfg(target_arch = "wasm32")]
const DATA_ALIGNMENT: usize = 1 << 3;
#[cfg(not(target_arch = "wasm32"))]
const DATA_ALIGNMENT: usize = 1 << 6;
#[derive(Clone)]
pub struct Plane<T: Pixel> {
pub(crate) data: ABox<[T], ConstAlign<DATA_ALIGNMENT>>,
pub(crate) geometry: PlaneGeometry,
}
impl<T> Plane<T>
where
T: Pixel,
{
pub(crate) fn new(geometry: PlaneGeometry) -> Self {
let rows = geometry
.height
.saturating_add(geometry.pad_top)
.saturating_add(geometry.pad_bottom);
Self {
data: AVec::from_iter(
DATA_ALIGNMENT,
iter::repeat_n(T::zero(), geometry.stride.get() * rows.get()),
)
.into_boxed_slice(),
geometry,
}
}
#[inline]
#[must_use]
pub fn width(&self) -> NonZeroUsize {
self.geometry.width
}
#[inline]
#[must_use]
pub fn height(&self) -> NonZeroUsize {
self.geometry.height
}
#[inline]
#[must_use]
pub fn row(&self, y: usize) -> Option<&[T]> {
self.rows().nth(y)
}
#[inline]
#[must_use]
pub fn row_mut(&mut self, y: usize) -> Option<&mut [T]> {
self.rows_mut().nth(y)
}
#[inline]
#[must_use]
pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> + ExactSizeIterator {
let origin = self.geometry.stride.get() * self.geometry.pad_top;
let visible_data = unsafe { self.data.get_unchecked(origin..) };
visible_data
.chunks_exact(self.geometry.stride.get())
.take(self.geometry.height.get())
.map(|row| {
let start_idx = self.geometry.pad_left;
let end_idx = start_idx + self.geometry.width.get();
unsafe { row.get_unchecked(start_idx..end_idx) }
})
}
#[inline]
pub fn rows_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut [T]> + ExactSizeIterator {
let origin = self.geometry.stride.get() * self.geometry.pad_top;
let visible_data = unsafe { self.data.get_unchecked_mut(origin..) };
visible_data
.chunks_exact_mut(self.geometry.stride.get())
.take(self.geometry.height.get())
.map(|row| {
let start_idx = self.geometry.pad_left;
let end_idx = start_idx + self.geometry.width.get();
unsafe { row.get_unchecked_mut(start_idx..end_idx) }
})
}
#[inline]
#[must_use]
pub fn pixel(&self, x: usize, y: usize) -> Option<T> {
let index = self.data_origin() + self.geometry.stride.get() * y + x;
self.data.get(index).copied()
}
#[inline]
pub fn pixel_mut(&mut self, x: usize, y: usize) -> Option<&mut T> {
let index = self.data_origin() + self.geometry.stride.get() * y + x;
self.data.get_mut(index)
}
#[inline]
#[must_use]
pub fn pixels(&self) -> impl DoubleEndedIterator<Item = T> + ExactSizeIterator {
let total = self.width().get() * self.height().get();
ExactSizeWrapper {
iter: self.rows().flatten().copied(),
len: total,
}
}
#[inline]
pub fn pixels_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> + ExactSizeIterator {
let total = self.width().get() * self.height().get();
ExactSizeWrapper {
iter: self.rows_mut().flatten(),
len: total,
}
}
#[inline]
#[must_use]
pub fn byte_data(&self) -> impl DoubleEndedIterator<Item = u8> + ExactSizeIterator {
let byte_width = size_of::<T>();
assert!(
byte_width <= 2,
"unsupported pixel byte width: {byte_width}"
);
let total = self.width().get() * self.height().get() * byte_width;
ExactSizeWrapper {
iter: self.pixels().flat_map(move |pix| {
let bytes: [u8; 2] = if byte_width == 1 {
[
pix.to_u8()
.expect("Pixel::byte_data only supports u8 and u16 pixels"),
0,
]
} else {
pix.to_u16()
.expect("Pixel::byte_data only supports u8 and u16 pixels")
.to_le_bytes()
};
bytes.into_iter().take(byte_width)
}),
len: total,
}
}
#[inline]
pub fn copy_from_slice(&mut self, src: &[T]) -> Result<(), Error> {
let pixel_count = self.width().get() * self.height().get();
if pixel_count != src.len() {
return Err(Error::DataLength {
expected: pixel_count,
found: src.len(),
});
}
for (dest, src) in self.pixels_mut().zip(src.iter()) {
*dest = *src;
}
Ok(())
}
#[inline]
pub fn copy_from_u8_slice(&mut self, src: &[u8]) -> Result<(), Error> {
self.copy_from_u8_slice_with_stride(
src,
self.width()
.saturating_mul(NonZeroUsize::new(size_of::<T>()).expect("size can't be zero")),
)
}
#[inline]
pub fn copy_from_u8_slice_with_stride(
&mut self,
src: &[u8],
input_stride: NonZeroUsize,
) -> Result<(), Error> {
let byte_width = size_of::<T>();
assert!(
byte_width <= 2,
"unsupported pixel byte width: {byte_width}"
);
if input_stride < self.width() {
return Err(Error::InvalidStride {
stride: input_stride.get(),
width: self.width().get(),
});
}
let byte_count = input_stride.get() * self.height().get();
if byte_count != src.len() {
return Err(Error::DataLength {
expected: byte_count,
found: src.len(),
});
}
let width = self.width().get();
let stride = input_stride.get();
if byte_width == 1 {
for (row_idx, dest_row) in self.rows_mut().enumerate() {
let src_offset = row_idx * stride;
let src_row = &src[src_offset..src_offset + width];
let src_row_typed = unsafe { &*(src_row as *const [u8] as *const [T]) };
dest_row.copy_from_slice(src_row_typed);
}
} else {
let row_byte_width = width * byte_width;
for (row_idx, dest_row) in self.rows_mut().enumerate() {
let src_offset = row_idx * stride;
let src_row = &src[src_offset..src_offset + row_byte_width];
for (dest_pixel, src_chunk) in dest_row.iter_mut().zip(src_row.chunks_exact(2)) {
let bytes =
unsafe { [*src_chunk.get_unchecked(0), *src_chunk.get_unchecked(1)] };
let dest = unsafe { &mut *(dest_pixel as *mut T as *mut u16) };
*dest = u16::from_le_bytes(bytes);
}
}
}
Ok(())
}
#[inline]
#[must_use]
#[cfg(feature = "padding_api")]
pub fn geometry(&self) -> PlaneGeometry {
self.geometry
}
#[inline]
#[must_use]
#[cfg(feature = "padding_api")]
pub fn data(&self) -> &[T] {
&self.data
}
#[inline]
#[must_use]
#[cfg(feature = "padding_api")]
pub fn data_mut(&mut self) -> &mut [T] {
&mut self.data
}
#[inline]
#[must_use]
#[cfg_attr(not(feature = "padding_api"), doc(hidden))]
pub fn data_origin(&self) -> usize {
self.geometry.stride.get() * self.geometry.pad_top + self.geometry.pad_left
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "padding_api"), doc(hidden))]
pub struct PlaneGeometry {
pub width: NonZeroUsize,
pub height: NonZeroUsize,
pub stride: NonZeroUsize,
pub pad_left: usize,
pub pad_right: usize,
pub pad_top: usize,
pub pad_bottom: usize,
pub subsampling_x: NonZeroU8,
pub subsampling_y: NonZeroU8,
}
impl PlaneGeometry {
#[inline]
#[must_use]
#[cfg_attr(not(feature = "padding_api"), doc(hidden))]
pub fn alloc_height(&self) -> NonZeroUsize {
self.height
.saturating_add(self.pad_top)
.saturating_add(self.pad_bottom)
}
}
struct ExactSizeWrapper<I> {
iter: I,
len: usize,
}
impl<I: Iterator> Iterator for ExactSizeWrapper<I> {
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.iter.next() {
self.len = self.len.saturating_sub(1);
Some(item)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<I: DoubleEndedIterator> DoubleEndedIterator for ExactSizeWrapper<I> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if let Some(item) = self.iter.next_back() {
self.len = self.len.saturating_sub(1);
Some(item)
} else {
None
}
}
}
impl<I: Iterator> ExactSizeIterator for ExactSizeWrapper<I> {
#[inline]
fn len(&self) -> usize {
self.len
}
}