use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use whereat::At;
#[cfg(feature = "rgb")]
use whereat::ResultAtExt;
#[cfg(feature = "imgref")]
use imgref::ImgRef;
#[cfg(feature = "imgref")]
use imgref::ImgVec;
#[cfg(feature = "rgb")]
use rgb::alt::BGRA;
#[cfg(feature = "rgb")]
use rgb::{Gray, Rgb, Rgba};
use crate::color::ColorContext;
use crate::descriptor::{
AlphaMode, ColorPrimaries, PixelDescriptor, SignalRange, TransferFunction,
};
#[cfg(feature = "rgb")]
use crate::pixel_types::{GrayAlpha8, GrayAlpha16, GrayAlphaF32};
#[derive(bytemuck::Zeroable, bytemuck::Pod, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Rgbx {
pub r: u8,
pub g: u8,
pub b: u8,
pub x: u8,
}
#[derive(bytemuck::Zeroable, bytemuck::Pod, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct Bgrx {
pub b: u8,
pub g: u8,
pub r: u8,
pub x: u8,
}
pub trait Pixel: bytemuck::Pod {
const DESCRIPTOR: PixelDescriptor;
}
impl Pixel for Rgbx {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBX8;
}
impl Pixel for Bgrx {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::BGRX8;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgb<u8> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGB8;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgba<u8> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBA8;
}
#[cfg(feature = "rgb")]
impl Pixel for Gray<u8> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAY8;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgb<u16> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGB16;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgba<u16> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBA16;
}
#[cfg(feature = "rgb")]
impl Pixel for Gray<u16> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAY16;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgb<f32> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBF32;
}
#[cfg(feature = "rgb")]
impl Pixel for Rgba<f32> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBAF32;
}
#[cfg(feature = "rgb")]
impl Pixel for Gray<f32> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYF32;
}
#[cfg(feature = "rgb")]
impl Pixel for BGRA<u8> {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::BGRA8;
}
#[cfg(feature = "rgb")]
impl Pixel for GrayAlpha8 {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYA8;
}
#[cfg(feature = "rgb")]
impl Pixel for GrayAlpha16 {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYA16;
}
#[cfg(feature = "rgb")]
impl Pixel for GrayAlphaF32 {
const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYAF32;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BufferError {
AlignmentViolation,
InsufficientData,
StrideTooSmall,
StrideNotPixelAligned,
InvalidDimensions,
IncompatibleDescriptor,
AllocationFailed,
}
impl fmt::Display for BufferError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AlignmentViolation => write!(f, "data is not aligned for the channel type"),
Self::InsufficientData => {
write!(f, "data slice is too small for the given dimensions")
}
Self::StrideTooSmall => write!(f, "stride is smaller than width * bytes_per_pixel"),
Self::StrideNotPixelAligned => {
write!(f, "stride is not a multiple of bytes_per_pixel")
}
Self::InvalidDimensions => write!(f, "width or height is zero or causes overflow"),
Self::IncompatibleDescriptor => {
write!(f, "new descriptor has different bytes_per_pixel")
}
Self::AllocationFailed => write!(f, "buffer allocation failed"),
}
}
}
impl core::error::Error for BufferError {}
const fn align_up(val: usize, align: usize) -> usize {
(val + align - 1) & !(align - 1)
}
fn align_offset(ptr: *const u8, align: usize) -> usize {
let addr = ptr as usize;
align_up(addr, align) - addr
}
fn try_alloc_zeroed(size: usize) -> Result<Vec<u8>, BufferError> {
let mut data = Vec::new();
data.try_reserve_exact(size)
.map_err(|_| BufferError::AllocationFailed)?;
data.resize(size, 0);
Ok(data)
}
fn validate_slice(
data_len: usize,
data_ptr: *const u8,
width: u32,
rows: u32,
stride_bytes: usize,
descriptor: &PixelDescriptor,
) -> Result<(), BufferError> {
let bpp = descriptor.bytes_per_pixel();
let min_stride = (width as usize)
.checked_mul(bpp)
.ok_or(BufferError::InvalidDimensions)?;
if stride_bytes < min_stride {
return Err(BufferError::StrideTooSmall);
}
#[allow(clippy::manual_is_multiple_of)] if bpp > 0 && stride_bytes % bpp != 0 {
return Err(BufferError::StrideNotPixelAligned);
}
if rows > 0 {
let required = required_bytes(rows, stride_bytes, min_stride)?;
if data_len < required {
return Err(BufferError::InsufficientData);
}
}
let align = descriptor.min_alignment();
#[allow(clippy::manual_is_multiple_of)] if (data_ptr as usize) % align != 0 {
return Err(BufferError::AlignmentViolation);
}
Ok(())
}
fn required_bytes(rows: u32, stride: usize, min_stride: usize) -> Result<usize, BufferError> {
let preceding = (rows as usize - 1)
.checked_mul(stride)
.ok_or(BufferError::InvalidDimensions)?;
preceding
.checked_add(min_stride)
.ok_or(BufferError::InvalidDimensions)
}
#[cfg(feature = "rgb")]
fn pixels_to_bytes<P: bytemuck::Pod>(pixels: Vec<P>) -> Vec<u8> {
match bytemuck::try_cast_vec(pixels) {
Ok(bytes) => bytes,
Err((_err, pixels)) => bytemuck::cast_slice::<P, u8>(&pixels).to_vec(),
}
}
#[non_exhaustive]
pub struct PixelSlice<'a, P = ()> {
data: &'a [u8],
width: u32,
rows: u32,
stride: usize,
descriptor: PixelDescriptor,
color: Option<Arc<ColorContext>>,
_pixel: PhantomData<P>,
}
impl<'a> PixelSlice<'a> {
#[track_caller]
pub fn new(
data: &'a [u8],
width: u32,
rows: u32,
stride_bytes: usize,
descriptor: PixelDescriptor,
) -> Result<Self, At<BufferError>> {
validate_slice(
data.len(),
data.as_ptr(),
width,
rows,
stride_bytes,
&descriptor,
)
.map_err(|e| whereat::at!(e))?;
Ok(Self {
data,
width,
rows,
stride: stride_bytes,
descriptor,
color: None,
_pixel: PhantomData,
})
}
}
impl<'a, P> PixelSlice<'a, P> {
pub fn erase(self) -> PixelSlice<'a> {
PixelSlice {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
}
}
pub fn try_typed<Q: Pixel>(self) -> Option<PixelSlice<'a, Q>> {
if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
Some(PixelSlice {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
})
} else {
None
}
}
#[inline]
#[must_use]
pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
assert!(
self.descriptor.layout_compatible(descriptor),
"with_descriptor() cannot change physical layout ({} -> {}); \
use reinterpret() for layout changes",
self.descriptor,
descriptor
);
self.descriptor = descriptor;
self
}
#[track_caller]
pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
return Err(whereat::at!(BufferError::IncompatibleDescriptor));
}
self.descriptor = descriptor;
Ok(self)
}
#[inline]
#[must_use]
pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
self.descriptor.transfer = tf;
self
}
#[inline]
#[must_use]
pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
self.descriptor.primaries = cp;
self
}
#[inline]
#[must_use]
pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
self.descriptor.signal_range = sr;
self
}
#[inline]
#[must_use]
pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
self.descriptor.alpha = am;
self
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn rows(&self) -> u32 {
self.rows
}
#[inline]
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn descriptor(&self) -> PixelDescriptor {
self.descriptor
}
#[inline]
pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
self.color.as_ref()
}
#[inline]
#[must_use]
pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
self.color = Some(ctx);
self
}
#[inline]
pub fn is_contiguous(&self) -> bool {
self.stride == self.width as usize * self.descriptor.bytes_per_pixel()
}
#[inline]
pub fn as_contiguous_bytes(&self) -> Option<&'a [u8]> {
if self.is_contiguous() {
let total = self.rows as usize * self.stride;
Some(&self.data[..total])
} else {
None
}
}
#[inline]
pub fn as_strided_bytes(&self) -> &'a [u8] {
if self.rows == 0 {
return &[];
}
let full = self.rows as usize * self.stride;
if full <= self.data.len() {
&self.data[..full]
} else {
let bpp = self.descriptor.bytes_per_pixel();
let trimmed = (self.rows as usize - 1) * self.stride + self.width as usize * bpp;
&self.data[..trimmed]
}
}
pub fn contiguous_bytes(&self) -> alloc::borrow::Cow<'a, [u8]> {
if let Some(bytes) = self.as_contiguous_bytes() {
alloc::borrow::Cow::Borrowed(bytes)
} else {
let bpp = self.descriptor.bytes_per_pixel();
let row_bytes = self.width as usize * bpp;
let mut buf = Vec::with_capacity(row_bytes * self.rows as usize);
for y in 0..self.rows {
buf.extend_from_slice(self.row(y));
}
alloc::borrow::Cow::Owned(buf)
}
}
#[inline]
pub fn row(&self, y: u32) -> &[u8] {
assert!(
y < self.rows,
"row index {y} out of bounds (rows: {})",
self.rows
);
let start = y as usize * self.stride;
let len = self.width as usize * self.descriptor.bytes_per_pixel();
&self.data[start..start + len]
}
#[inline]
pub fn row_with_stride(&self, y: u32) -> &[u8] {
assert!(
y < self.rows,
"row index {y} out of bounds (rows: {})",
self.rows
);
let start = y as usize * self.stride;
&self.data[start..start + self.stride]
}
pub fn sub_rows(&self, y: u32, count: u32) -> PixelSlice<'_, P> {
assert!(
y.checked_add(count).is_some_and(|end| end <= self.rows),
"sub_rows({y}, {count}) out of bounds (rows: {})",
self.rows
);
if count == 0 {
return PixelSlice {
data: &[],
width: self.width,
rows: 0,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = y as usize * self.stride;
let end = (y as usize + count as usize - 1) * self.stride + self.width as usize * bpp;
PixelSlice {
data: &self.data[start..end],
width: self.width,
rows: count,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn crop_view(&self, x: u32, y: u32, w: u32, h: u32) -> PixelSlice<'_, P> {
assert!(
x.checked_add(w).is_some_and(|end| end <= self.width),
"crop x={x} w={w} exceeds width {}",
self.width
);
assert!(
y.checked_add(h).is_some_and(|end| end <= self.rows),
"crop y={y} h={h} exceeds rows {}",
self.rows
);
if h == 0 || w == 0 {
return PixelSlice {
data: &[],
width: w,
rows: h,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = y as usize * self.stride + x as usize * bpp;
let end = (y as usize + h as usize - 1) * self.stride + (x as usize + w as usize) * bpp;
PixelSlice {
data: &self.data[start..end],
width: w,
rows: h,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
}
impl<'a, P: Pixel> PixelSlice<'a, P> {
#[track_caller]
pub fn new_typed(
data: &'a [u8],
width: u32,
rows: u32,
stride_pixels: u32,
) -> Result<Self, At<BufferError>> {
const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
let stride_bytes = stride_pixels as usize * core::mem::size_of::<P>();
validate_slice(
data.len(),
data.as_ptr(),
width,
rows,
stride_bytes,
&P::DESCRIPTOR,
)
.map_err(|e| whereat::at!(e))?;
Ok(Self {
data,
width,
rows,
stride: stride_bytes,
descriptor: P::DESCRIPTOR,
color: None,
_pixel: PhantomData,
})
}
}
impl<P> fmt::Debug for PixelSlice<'_, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PixelSlice({}x{}, {:?} {:?})",
self.width,
self.rows,
self.descriptor.layout(),
self.descriptor.channel_type()
)
}
}
#[non_exhaustive]
pub struct PixelSliceMut<'a, P = ()> {
data: &'a mut [u8],
width: u32,
rows: u32,
stride: usize,
descriptor: PixelDescriptor,
color: Option<Arc<ColorContext>>,
_pixel: PhantomData<P>,
}
impl<'a> PixelSliceMut<'a> {
#[track_caller]
pub fn new(
data: &'a mut [u8],
width: u32,
rows: u32,
stride_bytes: usize,
descriptor: PixelDescriptor,
) -> Result<Self, At<BufferError>> {
validate_slice(
data.len(),
data.as_ptr(),
width,
rows,
stride_bytes,
&descriptor,
)
.map_err(|e| whereat::at!(e))?;
Ok(Self {
data,
width,
rows,
stride: stride_bytes,
descriptor,
color: None,
_pixel: PhantomData,
})
}
}
impl<'a, P> PixelSliceMut<'a, P> {
pub fn erase(self) -> PixelSliceMut<'a> {
PixelSliceMut {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
}
}
pub fn try_typed<Q: Pixel>(self) -> Option<PixelSliceMut<'a, Q>> {
if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
Some(PixelSliceMut {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
})
} else {
None
}
}
#[inline]
#[must_use]
pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
assert!(
self.descriptor.layout_compatible(descriptor),
"with_descriptor() cannot change physical layout ({} -> {}); \
use reinterpret() for layout changes",
self.descriptor,
descriptor
);
self.descriptor = descriptor;
self
}
#[track_caller]
pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
return Err(whereat::at!(BufferError::IncompatibleDescriptor));
}
self.descriptor = descriptor;
Ok(self)
}
#[inline]
#[must_use]
pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
self.descriptor.transfer = tf;
self
}
#[inline]
#[must_use]
pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
self.descriptor.primaries = cp;
self
}
#[inline]
#[must_use]
pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
self.descriptor.signal_range = sr;
self
}
#[inline]
#[must_use]
pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
self.descriptor.alpha = am;
self
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn rows(&self) -> u32 {
self.rows
}
#[inline]
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn descriptor(&self) -> PixelDescriptor {
self.descriptor
}
#[inline]
pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
self.color.as_ref()
}
#[inline]
#[must_use]
pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
self.color = Some(ctx);
self
}
#[inline]
pub fn as_pixel_slice(&self) -> PixelSlice<'_, P> {
PixelSlice {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
#[inline]
pub fn as_strided_bytes(&self) -> &[u8] {
self.data
}
#[inline]
pub fn as_strided_bytes_mut(&mut self) -> &mut [u8] {
self.data
}
#[inline]
pub fn row(&self, y: u32) -> &[u8] {
assert!(
y < self.rows,
"row index {y} out of bounds (rows: {})",
self.rows
);
let start = y as usize * self.stride;
let len = self.width as usize * self.descriptor.bytes_per_pixel();
&self.data[start..start + len]
}
#[inline]
pub fn row_mut(&mut self, y: u32) -> &mut [u8] {
assert!(
y < self.rows,
"row index {y} out of bounds (rows: {})",
self.rows
);
let start = y as usize * self.stride;
let len = self.width as usize * self.descriptor.bytes_per_pixel();
&mut self.data[start..start + len]
}
pub fn sub_rows_mut(&mut self, y: u32, count: u32) -> PixelSliceMut<'_, P> {
assert!(
y.checked_add(count).is_some_and(|end| end <= self.rows),
"sub_rows_mut({y}, {count}) out of bounds (rows: {})",
self.rows
);
if count == 0 {
return PixelSliceMut {
data: &mut [],
width: self.width,
rows: 0,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = y as usize * self.stride;
let end = (y as usize + count as usize - 1) * self.stride + self.width as usize * bpp;
PixelSliceMut {
data: &mut self.data[start..end],
width: self.width,
rows: count,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
}
impl<'a, P: Pixel> PixelSliceMut<'a, P> {
#[track_caller]
pub fn new_typed(
data: &'a mut [u8],
width: u32,
rows: u32,
stride_pixels: u32,
) -> Result<Self, At<BufferError>> {
const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
let stride_bytes = stride_pixels as usize * core::mem::size_of::<P>();
validate_slice(
data.len(),
data.as_ptr(),
width,
rows,
stride_bytes,
&P::DESCRIPTOR,
)
.map_err(|e| whereat::at!(e))?;
Ok(Self {
data,
width,
rows,
stride: stride_bytes,
descriptor: P::DESCRIPTOR,
color: None,
_pixel: PhantomData,
})
}
}
impl<P> fmt::Debug for PixelSliceMut<'_, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PixelSliceMut({}x{}, {:?} {:?})",
self.width,
self.rows,
self.descriptor.layout(),
self.descriptor.channel_type()
)
}
}
fn for_each_pixel_4bpp(
data: &mut [u8],
width: u32,
rows: u32,
stride: usize,
mut f: impl FnMut(&mut [u8; 4]),
) {
let row_bytes = width as usize * 4;
for y in 0..rows as usize {
let row_start = y * stride;
let row = &mut data[row_start..row_start + row_bytes];
for chunk in row.chunks_exact_mut(4) {
let px: &mut [u8; 4] = chunk.try_into().unwrap();
f(px);
}
}
}
impl<'a> PixelSliceMut<'a, Rgbx> {
#[must_use]
pub fn swap_to_bgrx(self) -> PixelSliceMut<'a, Bgrx> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px.swap(0, 2);
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::BGRX8_SRGB,
color,
_pixel: PhantomData,
}
}
}
#[cfg(feature = "rgb")]
impl<'a> PixelSliceMut<'a, Rgbx> {
#[must_use]
pub fn upgrade_to_rgba(self) -> PixelSliceMut<'a, Rgba<u8>> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px[3] = 255;
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::RGBA8_SRGB,
color,
_pixel: PhantomData,
}
}
}
impl<'a> PixelSliceMut<'a, Bgrx> {
#[must_use]
pub fn swap_to_rgbx(self) -> PixelSliceMut<'a, Rgbx> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px.swap(0, 2);
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::RGBX8_SRGB,
color,
_pixel: PhantomData,
}
}
}
#[cfg(feature = "rgb")]
impl<'a> PixelSliceMut<'a, Bgrx> {
#[must_use]
pub fn upgrade_to_bgra(self) -> PixelSliceMut<'a, BGRA<u8>> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px[3] = 255;
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::BGRA8_SRGB,
color,
_pixel: PhantomData,
}
}
}
#[cfg(feature = "rgb")]
impl<'a> PixelSliceMut<'a, Rgba<u8>> {
#[must_use]
pub fn matte_to_rgbx(self, bg: Rgb<u8>) -> PixelSliceMut<'a, Rgbx> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
let a = px[3] as u16;
let inv_a = 255 - a;
px[0] = ((px[0] as u16 * a + bg.r as u16 * inv_a + 127) / 255) as u8;
px[1] = ((px[1] as u16 * a + bg.g as u16 * inv_a + 127) / 255) as u8;
px[2] = ((px[2] as u16 * a + bg.b as u16 * inv_a + 127) / 255) as u8;
px[3] = 0;
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::RGBX8_SRGB,
color,
_pixel: PhantomData,
}
}
#[must_use]
pub fn strip_alpha_to_rgbx(self) -> PixelSliceMut<'a, Rgbx> {
PixelSliceMut {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: PixelDescriptor::RGBX8_SRGB,
color: self.color,
_pixel: PhantomData,
}
}
#[must_use]
pub fn swap_to_bgra(self) -> PixelSliceMut<'a, BGRA<u8>> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px.swap(0, 2);
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::BGRA8_SRGB,
color,
_pixel: PhantomData,
}
}
}
#[cfg(feature = "rgb")]
impl<'a> PixelSliceMut<'a, BGRA<u8>> {
#[must_use]
pub fn matte_to_bgrx(self, bg: Rgb<u8>) -> PixelSliceMut<'a, Bgrx> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
let a = px[3] as u16;
let inv_a = 255 - a;
px[0] = ((px[0] as u16 * a + bg.b as u16 * inv_a + 127) / 255) as u8;
px[1] = ((px[1] as u16 * a + bg.g as u16 * inv_a + 127) / 255) as u8;
px[2] = ((px[2] as u16 * a + bg.r as u16 * inv_a + 127) / 255) as u8;
px[3] = 0;
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::BGRX8_SRGB,
color,
_pixel: PhantomData,
}
}
#[must_use]
pub fn strip_alpha_to_bgrx(self) -> PixelSliceMut<'a, Bgrx> {
PixelSliceMut {
data: self.data,
width: self.width,
rows: self.rows,
stride: self.stride,
descriptor: PixelDescriptor::BGRX8_SRGB,
color: self.color,
_pixel: PhantomData,
}
}
#[must_use]
pub fn swap_to_rgba(self) -> PixelSliceMut<'a, Rgba<u8>> {
let width = self.width;
let rows = self.rows;
let stride = self.stride;
let color = self.color;
let data = self.data;
for_each_pixel_4bpp(data, width, rows, stride, |px| {
px.swap(0, 2);
});
PixelSliceMut {
data,
width,
rows,
stride,
descriptor: PixelDescriptor::RGBA8_SRGB,
color,
_pixel: PhantomData,
}
}
}
#[non_exhaustive]
pub struct PixelBuffer<P = ()> {
data: Vec<u8>,
offset: usize,
width: u32,
height: u32,
stride: usize,
descriptor: PixelDescriptor,
color: Option<Arc<ColorContext>>,
_pixel: PhantomData<P>,
}
impl PixelBuffer {
pub fn new(width: u32, height: u32, descriptor: PixelDescriptor) -> Self {
Self::try_new(width, height, descriptor).expect("pixel buffer allocation failed")
}
#[track_caller]
pub fn try_new(
width: u32,
height: u32,
descriptor: PixelDescriptor,
) -> Result<Self, At<BufferError>> {
let stride = descriptor.aligned_stride(width);
let total = stride
.checked_mul(height as usize)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let align = descriptor.min_alignment();
let alloc_size = total
.checked_add(align - 1)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
let offset = align_offset(data.as_ptr(), align);
Ok(Self {
data,
offset,
width,
height,
stride,
descriptor,
color: None,
_pixel: PhantomData,
})
}
pub fn new_simd_aligned(
width: u32,
height: u32,
descriptor: PixelDescriptor,
simd_align: usize,
) -> Self {
Self::try_new_simd_aligned(width, height, descriptor, simd_align)
.expect("pixel buffer SIMD-aligned allocation failed")
}
#[track_caller]
pub fn try_new_simd_aligned(
width: u32,
height: u32,
descriptor: PixelDescriptor,
simd_align: usize,
) -> Result<Self, At<BufferError>> {
let stride = descriptor.simd_aligned_stride(width, simd_align);
let total = stride
.checked_mul(height as usize)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let alloc_size = total
.checked_add(simd_align - 1)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
let offset = align_offset(data.as_ptr(), simd_align);
Ok(Self {
data,
offset,
width,
height,
stride,
descriptor,
color: None,
_pixel: PhantomData,
})
}
#[track_caller]
pub fn from_vec(
data: Vec<u8>,
width: u32,
height: u32,
descriptor: PixelDescriptor,
) -> Result<Self, At<BufferError>> {
let stride = descriptor.aligned_stride(width);
let total = stride
.checked_mul(height as usize)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let align = descriptor.min_alignment();
let offset = align_offset(data.as_ptr(), align);
if data.len() < offset + total {
return Err(whereat::at!(BufferError::InsufficientData));
}
Ok(Self {
data,
offset,
width,
height,
stride,
descriptor,
color: None,
_pixel: PhantomData,
})
}
}
impl<P: Pixel> PixelBuffer<P> {
pub fn new_typed(width: u32, height: u32) -> Self {
Self::try_new_typed(width, height).expect("typed pixel buffer allocation failed")
}
#[track_caller]
pub fn try_new_typed(width: u32, height: u32) -> Result<Self, At<BufferError>> {
const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
let descriptor = P::DESCRIPTOR;
let stride = descriptor.aligned_stride(width);
let total = stride
.checked_mul(height as usize)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let align = descriptor.min_alignment();
let alloc_size = total
.checked_add(align - 1)
.ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
let offset = align_offset(data.as_ptr(), align);
Ok(Self {
data,
offset,
width,
height,
stride,
descriptor,
color: None,
_pixel: PhantomData,
})
}
}
#[cfg(feature = "rgb")]
impl<P: Pixel> PixelBuffer<P> {
#[track_caller]
pub fn from_pixels(pixels: Vec<P>, width: u32, height: u32) -> Result<Self, At<BufferError>> {
const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
let expected = width as usize * height as usize;
if pixels.len() != expected {
return Err(whereat::at!(BufferError::InvalidDimensions));
}
let descriptor = P::DESCRIPTOR;
let stride = descriptor.aligned_stride(width);
let data: Vec<u8> = pixels_to_bytes(pixels);
Ok(Self {
data,
offset: 0,
width,
height,
stride,
descriptor,
color: None,
_pixel: PhantomData,
})
}
}
#[cfg(feature = "imgref")]
impl<P: Pixel> PixelBuffer<P> {
pub fn from_imgvec(img: ImgVec<P>) -> Self {
const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
let width = img.width() as u32;
let height = img.height() as u32;
let stride_pixels = img.stride();
let descriptor = P::DESCRIPTOR;
let stride_bytes = stride_pixels * core::mem::size_of::<P>();
let (buf, ..) = img.into_contiguous_buf();
let data: Vec<u8> = pixels_to_bytes(buf);
Self {
data,
offset: 0,
width,
height,
stride: stride_bytes,
descriptor,
color: None,
_pixel: PhantomData,
}
}
}
#[cfg(feature = "imgref")]
impl<P: Pixel> PixelBuffer<P> {
pub fn as_imgref(&self) -> ImgRef<'_, P> {
let total_bytes = if self.height == 0 {
0
} else {
(self.height as usize - 1) * self.stride
+ self.width as usize * core::mem::size_of::<P>()
};
let data = &self.data[self.offset..self.offset + total_bytes];
let pixels: &[P] = bytemuck::cast_slice(data);
let stride_px = self.stride / core::mem::size_of::<P>();
imgref::Img::new_stride(pixels, self.width as usize, self.height as usize, stride_px)
}
pub fn as_imgref_mut(&mut self) -> imgref::ImgRefMut<'_, P> {
let total_bytes = if self.height == 0 {
0
} else {
(self.height as usize - 1) * self.stride
+ self.width as usize * core::mem::size_of::<P>()
};
let offset = self.offset;
let data = &mut self.data[offset..offset + total_bytes];
let pixels: &mut [P] = bytemuck::cast_slice_mut(data);
let stride_px = self.stride / core::mem::size_of::<P>();
imgref::Img::new_stride(pixels, self.width as usize, self.height as usize, stride_px)
}
}
#[cfg(feature = "rgb")]
impl PixelBuffer {
#[track_caller]
pub fn from_pixels_erased<P: Pixel>(
pixels: Vec<P>,
width: u32,
height: u32,
) -> Result<Self, At<BufferError>> {
PixelBuffer::<P>::from_pixels(pixels, width, height)
.at()
.map(PixelBuffer::from)
}
pub fn as_contiguous_pixels<P: Pixel>(&self) -> Option<&[P]> {
if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
return None;
}
let pixel_size = core::mem::size_of::<P>();
let row_bytes = self.width as usize * pixel_size;
if pixel_size == 0 || self.stride != row_bytes {
return None;
}
let total = row_bytes * self.height as usize;
let data = &self.data[self.offset..self.offset + total];
Some(bytemuck::cast_slice(data))
}
pub fn into_contiguous_pixels<P: Pixel>(self) -> Option<Vec<P>> {
if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
return None;
}
let pixel_size = core::mem::size_of::<P>();
if pixel_size == 0 {
return None;
}
let row_bytes = self.width as usize * pixel_size;
let total_pixels = self.width as usize * self.height as usize;
if self.stride == row_bytes && self.offset == 0 {
let mut data = self.data;
data.truncate(total_pixels * pixel_size);
match bytemuck::try_cast_vec(data) {
Ok(pixels) => return Some(pixels),
Err((_err, data)) => {
return Some(
bytemuck::cast_slice::<u8, P>(&data[..total_pixels * pixel_size]).to_vec(),
);
}
}
}
let mut out = Vec::with_capacity(total_pixels);
for y in 0..self.height as usize {
let row_start = self.offset + y * self.stride;
let row_data = &self.data[row_start..row_start + row_bytes];
out.extend_from_slice(bytemuck::cast_slice(row_data));
}
Some(out)
}
}
#[cfg(feature = "imgref")]
impl PixelBuffer {
pub fn try_as_imgref<P: Pixel>(&self) -> Option<ImgRef<'_, P>> {
if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
return None;
}
let pixel_size = core::mem::size_of::<P>();
#[allow(clippy::manual_is_multiple_of)] if pixel_size == 0 || self.stride % pixel_size != 0 {
return None;
}
let total_bytes = if self.height == 0 {
0
} else {
(self.height as usize - 1) * self.stride + self.width as usize * pixel_size
};
let data = &self.data[self.offset..self.offset + total_bytes];
let pixels: &[P] = bytemuck::cast_slice(data);
let stride_px = self.stride / pixel_size;
Some(imgref::Img::new_stride(
pixels,
self.width as usize,
self.height as usize,
stride_px,
))
}
pub fn try_as_imgref_mut<P: Pixel>(&mut self) -> Option<imgref::ImgRefMut<'_, P>> {
if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
return None;
}
let pixel_size = core::mem::size_of::<P>();
#[allow(clippy::manual_is_multiple_of)] if pixel_size == 0 || self.stride % pixel_size != 0 {
return None;
}
let total_bytes = if self.height == 0 {
0
} else {
(self.height as usize - 1) * self.stride + self.width as usize * pixel_size
};
let offset = self.offset;
let data = &mut self.data[offset..offset + total_bytes];
let pixels: &mut [P] = bytemuck::cast_slice_mut(data);
let stride_px = self.stride / pixel_size;
Some(imgref::Img::new_stride(
pixels,
self.width as usize,
self.height as usize,
stride_px,
))
}
}
impl<P> PixelBuffer<P> {
pub fn erase(self) -> PixelBuffer {
PixelBuffer {
data: self.data,
offset: self.offset,
width: self.width,
height: self.height,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
}
}
pub fn try_typed<Q: Pixel>(self) -> Option<PixelBuffer<Q>> {
if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
Some(PixelBuffer {
data: self.data,
offset: self.offset,
width: self.width,
height: self.height,
stride: self.stride,
descriptor: self.descriptor,
color: self.color,
_pixel: PhantomData,
})
} else {
None
}
}
#[inline]
#[must_use]
pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
assert!(
self.descriptor.layout_compatible(descriptor),
"with_descriptor() cannot change physical layout ({} -> {}); \
use reinterpret() for layout changes",
self.descriptor,
descriptor
);
self.descriptor = descriptor;
self
}
#[track_caller]
pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
return Err(whereat::at!(BufferError::IncompatibleDescriptor));
}
self.descriptor = descriptor;
Ok(self)
}
#[inline]
#[must_use]
pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
self.descriptor.transfer = tf;
self
}
#[inline]
#[must_use]
pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
self.descriptor.primaries = cp;
self
}
#[inline]
#[must_use]
pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
self.descriptor.signal_range = sr;
self
}
#[inline]
#[must_use]
pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
self.descriptor.alpha = am;
self
}
#[inline]
pub fn has_alpha(&self) -> bool {
self.descriptor.has_alpha()
}
#[inline]
pub fn is_grayscale(&self) -> bool {
self.descriptor.is_grayscale()
}
pub fn into_vec(self) -> Vec<u8> {
self.data
}
#[inline]
pub fn as_contiguous_bytes(&self) -> Option<&[u8]> {
let bpp = self.descriptor.bytes_per_pixel();
let row_bytes = self.width as usize * bpp;
if self.stride == row_bytes {
let total = row_bytes * self.height as usize;
Some(&self.data[self.offset..self.offset + total])
} else {
None
}
}
pub fn copy_to_contiguous_bytes(&self) -> Vec<u8> {
let bpp = self.descriptor.bytes_per_pixel();
let row_bytes = self.width as usize * bpp;
let total = row_bytes * self.height as usize;
if self.stride == row_bytes {
let start = self.offset;
return self.data[start..start + total].to_vec();
}
let mut out = Vec::with_capacity(total);
let slice = self.as_slice();
for y in 0..self.height {
out.extend_from_slice(slice.row(y));
}
out
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn descriptor(&self) -> PixelDescriptor {
self.descriptor
}
#[inline]
pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
self.color.as_ref()
}
#[inline]
#[must_use]
pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
self.color = Some(ctx);
self
}
pub fn as_slice(&self) -> PixelSlice<'_, P> {
let total = self.stride * self.height as usize;
PixelSlice {
data: &self.data[self.offset..self.offset + total],
width: self.width,
rows: self.height,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn as_slice_mut(&mut self) -> PixelSliceMut<'_, P> {
let total = self.stride * self.height as usize;
let offset = self.offset;
PixelSliceMut {
data: &mut self.data[offset..offset + total],
width: self.width,
rows: self.height,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn rows(&self, y: u32, count: u32) -> PixelSlice<'_, P> {
assert!(
y.checked_add(count).is_some_and(|end| end <= self.height),
"rows({y}, {count}) out of bounds (height: {})",
self.height
);
if count == 0 {
return PixelSlice {
data: &[],
width: self.width,
rows: 0,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = self.offset + y as usize * self.stride;
let end = self.offset
+ (y as usize + count as usize - 1) * self.stride
+ self.width as usize * bpp;
PixelSlice {
data: &self.data[start..end],
width: self.width,
rows: count,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn rows_mut(&mut self, y: u32, count: u32) -> PixelSliceMut<'_, P> {
assert!(
y.checked_add(count).is_some_and(|end| end <= self.height),
"rows_mut({y}, {count}) out of bounds (height: {})",
self.height
);
if count == 0 {
return PixelSliceMut {
data: &mut [],
width: self.width,
rows: 0,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = self.offset + y as usize * self.stride;
let end = self.offset
+ (y as usize + count as usize - 1) * self.stride
+ self.width as usize * bpp;
PixelSliceMut {
data: &mut self.data[start..end],
width: self.width,
rows: count,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn crop_view(&self, x: u32, y: u32, w: u32, h: u32) -> PixelSlice<'_, P> {
assert!(
x.checked_add(w).is_some_and(|end| end <= self.width),
"crop x={x} w={w} exceeds width {}",
self.width
);
assert!(
y.checked_add(h).is_some_and(|end| end <= self.height),
"crop y={y} h={h} exceeds height {}",
self.height
);
if h == 0 || w == 0 {
return PixelSlice {
data: &[],
width: w,
rows: h,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
}
let bpp = self.descriptor.bytes_per_pixel();
let start = self.offset + y as usize * self.stride + x as usize * bpp;
let end = self.offset
+ (y as usize + h as usize - 1) * self.stride
+ (x as usize + w as usize) * bpp;
PixelSlice {
data: &self.data[start..end],
width: w,
rows: h,
stride: self.stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
}
}
pub fn crop_copy(&self, x: u32, y: u32, w: u32, h: u32) -> PixelBuffer<P> {
let src = self.crop_view(x, y, w, h);
let stride = self.descriptor.aligned_stride(w);
let total = stride * h as usize;
let align = self.descriptor.min_alignment();
let alloc_size = total + align - 1;
let data = vec![0u8; alloc_size];
let offset = align_offset(data.as_ptr(), align);
let mut dst = PixelBuffer {
data,
offset,
width: w,
height: h,
stride,
descriptor: self.descriptor,
color: self.color.clone(),
_pixel: PhantomData,
};
let bpp = self.descriptor.bytes_per_pixel();
let row_bytes = w as usize * bpp;
for row_y in 0..h {
let src_row = src.row(row_y);
let dst_start = dst.offset + row_y as usize * dst.stride;
dst.data[dst_start..dst_start + row_bytes].copy_from_slice(&src_row[..row_bytes]);
}
dst
}
}
impl<P> fmt::Debug for PixelBuffer<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"PixelBuffer({}x{}, {:?} {:?})",
self.width,
self.height,
self.descriptor.layout(),
self.descriptor.channel_type()
)
}
}
#[cfg(feature = "imgref")]
macro_rules! impl_from_imgref {
($pixel:ty, $descriptor:expr) => {
impl<'a> From<ImgRef<'a, $pixel>> for PixelSlice<'a, $pixel> {
fn from(img: ImgRef<'a, $pixel>) -> Self {
let bytes: &[u8] = bytemuck::cast_slice(img.buf());
let byte_stride = img.stride() * core::mem::size_of::<$pixel>();
PixelSlice {
data: bytes,
width: img.width() as u32,
rows: img.height() as u32,
stride: byte_stride,
descriptor: $descriptor,
color: None,
_pixel: PhantomData,
}
}
}
};
}
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgb<u8>, PixelDescriptor::RGB8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgba<u8>, PixelDescriptor::RGBA8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgb<u16>, PixelDescriptor::RGB16);
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgba<u16>, PixelDescriptor::RGBA16);
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgb<f32>, PixelDescriptor::RGBF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref!(Rgba<f32>, PixelDescriptor::RGBAF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref!(Gray<u8>, PixelDescriptor::GRAY8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref!(Gray<u16>, PixelDescriptor::GRAY16);
#[cfg(feature = "imgref")]
impl_from_imgref!(Gray<f32>, PixelDescriptor::GRAYF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref!(BGRA<u8>, PixelDescriptor::BGRA8_SRGB);
#[cfg(feature = "imgref")]
macro_rules! impl_from_imgref_mut {
($pixel:ty, $descriptor:expr) => {
impl<'a> From<imgref::ImgRefMut<'a, $pixel>> for PixelSliceMut<'a, $pixel> {
fn from(img: imgref::ImgRefMut<'a, $pixel>) -> Self {
let width = img.width() as u32;
let rows = img.height() as u32;
let byte_stride = img.stride() * core::mem::size_of::<$pixel>();
let buf = img.into_buf();
let bytes: &mut [u8] = bytemuck::cast_slice_mut(buf);
PixelSliceMut {
data: bytes,
width,
rows,
stride: byte_stride,
descriptor: $descriptor,
color: None,
_pixel: PhantomData,
}
}
}
};
}
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgb<u8>, PixelDescriptor::RGB8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgba<u8>, PixelDescriptor::RGBA8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgb<u16>, PixelDescriptor::RGB16);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgba<u16>, PixelDescriptor::RGBA16);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgb<f32>, PixelDescriptor::RGBF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Rgba<f32>, PixelDescriptor::RGBAF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Gray<u8>, PixelDescriptor::GRAY8_SRGB);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Gray<u16>, PixelDescriptor::GRAY16);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(Gray<f32>, PixelDescriptor::GRAYF32_LINEAR);
#[cfg(feature = "imgref")]
impl_from_imgref_mut!(BGRA<u8>, PixelDescriptor::BGRA8_SRGB);
impl<'a, P: Pixel> From<PixelSlice<'a, P>> for PixelSlice<'a> {
fn from(typed: PixelSlice<'a, P>) -> Self {
typed.erase()
}
}
impl<'a, P: Pixel> From<PixelSliceMut<'a, P>> for PixelSliceMut<'a> {
fn from(typed: PixelSliceMut<'a, P>) -> Self {
typed.erase()
}
}
impl<P: Pixel> From<PixelBuffer<P>> for PixelBuffer {
fn from(typed: PixelBuffer<P>) -> Self {
typed.erase()
}
}
impl<'a, P> From<PixelSliceMut<'a, P>> for PixelSlice<'a, P> {
fn from(mut_slice: PixelSliceMut<'a, P>) -> Self {
PixelSlice {
data: mut_slice.data,
width: mut_slice.width,
rows: mut_slice.rows,
stride: mut_slice.stride,
descriptor: mut_slice.descriptor,
color: mut_slice.color,
_pixel: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::descriptor::{ChannelLayout, ChannelType};
use alloc::format;
use alloc::vec;
#[test]
fn pixel_buffer_new_rgb8() {
let buf = PixelBuffer::new(10, 5, PixelDescriptor::RGB8_SRGB);
assert_eq!(buf.width(), 10);
assert_eq!(buf.height(), 5);
assert_eq!(buf.stride(), 30);
assert_eq!(buf.descriptor(), PixelDescriptor::RGB8_SRGB);
let slice = buf.as_slice();
assert_eq!(slice.row(0), &[0u8; 30]);
assert_eq!(slice.row(4), &[0u8; 30]);
}
#[test]
fn pixel_buffer_from_vec() {
let data = vec![0u8; 30 * 5];
let buf = PixelBuffer::from_vec(data, 10, 5, PixelDescriptor::RGB8_SRGB).unwrap();
assert_eq!(buf.width(), 10);
assert_eq!(buf.height(), 5);
}
#[test]
fn pixel_buffer_from_vec_too_small() {
let data = vec![0u8; 10];
let err = PixelBuffer::from_vec(data, 10, 5, PixelDescriptor::RGB8_SRGB);
assert_eq!(*err.unwrap_err().error(), BufferError::InsufficientData);
}
#[test]
fn pixel_buffer_into_vec_roundtrip() {
let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGBA8_SRGB);
let v = buf.into_vec();
let buf2 = PixelBuffer::from_vec(v, 4, 4, PixelDescriptor::RGBA8_SRGB).unwrap();
assert_eq!(buf2.width(), 4);
}
#[test]
fn pixel_buffer_write_and_read() {
let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
{
let mut slice = buf.as_slice_mut();
let row = slice.row_mut(0);
row[0] = 255;
row[1] = 128;
row[2] = 64;
}
let slice = buf.as_slice();
assert_eq!(&slice.row(0)[..3], &[255, 128, 64]);
assert_eq!(&slice.row(1)[..3], &[0, 0, 0]);
}
#[test]
fn pixel_buffer_simd_aligned() {
let buf = PixelBuffer::new_simd_aligned(10, 5, PixelDescriptor::RGBA8_SRGB, 64);
assert_eq!(buf.width(), 10);
assert_eq!(buf.height(), 5);
assert_eq!(buf.stride(), 64);
let slice = buf.as_slice();
assert_eq!(slice.data.as_ptr() as usize % 64, 0);
}
#[test]
fn pixel_slice_crop_view() {
let mut buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
{
let mut slice = buf.as_slice_mut();
for y in 0..4u32 {
let row = slice.row_mut(y);
for byte in row.iter_mut() {
*byte = y as u8;
}
}
}
let crop = buf.crop_view(1, 1, 2, 2);
assert_eq!(crop.width(), 2);
assert_eq!(crop.rows(), 2);
assert_eq!(crop.row(0), &[1, 1, 1, 1, 1, 1]);
assert_eq!(crop.row(1), &[2, 2, 2, 2, 2, 2]);
}
#[test]
fn pixel_slice_crop_copy() {
let mut buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
{
let mut slice = buf.as_slice_mut();
for y in 0..4u32 {
let row = slice.row_mut(y);
for (i, byte) in row.iter_mut().enumerate() {
*byte = (y * 100 + i as u32) as u8;
}
}
}
let cropped = buf.crop_copy(1, 1, 2, 2);
assert_eq!(cropped.width(), 2);
assert_eq!(cropped.height(), 2);
assert_eq!(cropped.as_slice().row(0), &[103, 104, 105, 106, 107, 108]);
}
#[test]
fn pixel_slice_sub_rows() {
let mut buf = PixelBuffer::new(2, 4, PixelDescriptor::GRAY8_SRGB);
{
let mut slice = buf.as_slice_mut();
for y in 0..4u32 {
let row = slice.row_mut(y);
row[0] = y as u8 * 10;
row[1] = y as u8 * 10 + 1;
}
}
let sub = buf.rows(1, 2);
assert_eq!(sub.rows(), 2);
assert_eq!(sub.row(0), &[10, 11]);
assert_eq!(sub.row(1), &[20, 21]);
}
#[test]
fn pixel_slice_stride_too_small() {
let data = [0u8; 100];
let err = PixelSlice::new(&data, 10, 1, 2, PixelDescriptor::RGB8_SRGB);
assert_eq!(*err.unwrap_err().error(), BufferError::StrideTooSmall);
}
#[test]
fn pixel_slice_insufficient_data() {
let data = [0u8; 10];
let err = PixelSlice::new(&data, 10, 1, 30, PixelDescriptor::RGB8_SRGB);
assert_eq!(*err.unwrap_err().error(), BufferError::InsufficientData);
}
#[test]
fn pixel_slice_zero_rows() {
let data = [0u8; 0];
let slice = PixelSlice::new(&data, 10, 0, 30, PixelDescriptor::RGB8_SRGB).unwrap();
assert_eq!(slice.rows(), 0);
}
#[test]
fn stride_not_pixel_aligned_rejected() {
let data = [0u8; 128];
let err = PixelSlice::new(&data, 10, 1, 32, PixelDescriptor::RGB8_SRGB);
assert_eq!(
*err.unwrap_err().error(),
BufferError::StrideNotPixelAligned
);
let ok = PixelSlice::new(&data, 10, 1, 33, PixelDescriptor::RGB8_SRGB);
assert!(ok.is_ok());
}
#[test]
fn stride_pixel_aligned_accepted() {
let data = [0u8; 256];
let ok = PixelSlice::new(&data, 10, 2, 48, PixelDescriptor::RGBA8_SRGB);
assert!(ok.is_ok());
let s = ok.unwrap();
assert_eq!(s.stride(), 48);
}
#[test]
fn debug_formats() {
let buf = PixelBuffer::new(10, 5, PixelDescriptor::RGB8_SRGB);
assert_eq!(format!("{buf:?}"), "PixelBuffer(10x5, Rgb U8)");
let slice = buf.as_slice();
assert_eq!(format!("{slice:?}"), "PixelSlice(10x5, Rgb U8)");
let mut buf = PixelBuffer::new(3, 3, PixelDescriptor::RGBA16_SRGB);
let slice_mut = buf.as_slice_mut();
assert_eq!(format!("{slice_mut:?}"), "PixelSliceMut(3x3, Rgba U16)");
}
#[test]
fn buffer_error_display() {
let msg = format!("{}", BufferError::StrideTooSmall);
assert!(msg.contains("stride"));
}
#[test]
fn bgrx8_srgb_properties() {
let d = PixelDescriptor::BGRX8_SRGB;
assert_eq!(d.channel_type(), ChannelType::U8);
assert_eq!(d.layout(), ChannelLayout::Bgra);
assert_eq!(d.alpha(), Some(AlphaMode::Undefined));
assert_eq!(d.transfer(), TransferFunction::Srgb);
assert_eq!(d.bytes_per_pixel(), 4);
assert_eq!(d.min_alignment(), 1);
assert!(d.layout_compatible(PixelDescriptor::BGRA8_SRGB));
assert!(!d.has_alpha());
assert!(PixelDescriptor::BGRA8_SRGB.has_alpha());
assert!(d.layout().has_alpha());
}
#[test]
fn zero_size_buffer() {
let buf = PixelBuffer::new(0, 0, PixelDescriptor::RGB8_SRGB);
assert_eq!(buf.width(), 0);
assert_eq!(buf.height(), 0);
let slice = buf.as_slice();
assert_eq!(slice.rows(), 0);
}
#[test]
fn crop_empty() {
let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
let crop = buf.crop_view(0, 0, 0, 0);
assert_eq!(crop.width(), 0);
assert_eq!(crop.rows(), 0);
}
#[test]
fn sub_rows_empty() {
let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
let sub = buf.rows(2, 0);
assert_eq!(sub.rows(), 0);
}
#[test]
fn with_descriptor_metadata_change_succeeds() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
let buf2 = buf.with_descriptor(PixelDescriptor::RGB8);
assert_eq!(buf2.descriptor(), PixelDescriptor::RGB8);
}
#[test]
#[should_panic(expected = "with_descriptor() cannot change physical layout")]
fn with_descriptor_layout_change_panics() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
let _ = buf.with_descriptor(PixelDescriptor::RGBA8);
}
#[test]
fn with_descriptor_slice_assertion() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
let slice = buf.as_slice();
let s2 = slice.with_descriptor(PixelDescriptor::RGB8);
assert_eq!(s2.descriptor(), PixelDescriptor::RGB8);
}
#[test]
#[should_panic(expected = "with_descriptor() cannot change physical layout")]
fn with_descriptor_slice_layout_change_panics() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
let slice = buf.as_slice();
let _ = slice.with_descriptor(PixelDescriptor::RGBA8);
}
#[test]
fn reinterpret_same_bpp_succeeds() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8);
let buf2 = buf.reinterpret(PixelDescriptor::BGRA8).unwrap();
assert_eq!(buf2.descriptor().layout(), ChannelLayout::Bgra);
}
#[test]
fn reinterpret_different_bpp_fails() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
let err = buf.reinterpret(PixelDescriptor::RGBA8);
assert_eq!(
*err.unwrap_err().error(),
BufferError::IncompatibleDescriptor
);
}
#[test]
fn reinterpret_rgbx_to_rgba() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBX8);
let buf2 = buf.reinterpret(PixelDescriptor::RGBA8).unwrap();
assert!(buf2.descriptor().has_alpha());
}
#[test]
fn per_field_setters() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
let buf = buf.with_transfer(TransferFunction::Srgb);
assert_eq!(buf.descriptor().transfer(), TransferFunction::Srgb);
let buf = buf.with_primaries(ColorPrimaries::DisplayP3);
assert_eq!(buf.descriptor().primaries, ColorPrimaries::DisplayP3);
let buf = buf.with_signal_range(SignalRange::Narrow);
assert!(matches!(buf.descriptor().signal_range, SignalRange::Narrow));
let buf = buf.with_alpha_mode(Some(AlphaMode::Premultiplied));
assert_eq!(buf.descriptor().alpha(), Some(AlphaMode::Premultiplied));
}
#[test]
fn copy_to_contiguous_bytes_tight() {
let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
{
let mut s = buf.as_slice_mut();
s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6]);
s.row_mut(1).copy_from_slice(&[7, 8, 9, 10, 11, 12]);
}
let bytes = buf.copy_to_contiguous_bytes();
assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn copy_to_contiguous_bytes_padded() {
let mut buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
let stride = buf.stride();
assert!(stride >= 6);
{
let mut s = buf.as_slice_mut();
s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6]);
s.row_mut(1).copy_from_slice(&[7, 8, 9, 10, 11, 12]);
}
let bytes = buf.copy_to_contiguous_bytes();
assert_eq!(bytes.len(), 12);
assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
}
#[test]
fn buffer_error_display_alignment_violation() {
let msg = format!("{}", BufferError::AlignmentViolation);
assert_eq!(msg, "data is not aligned for the channel type");
}
#[test]
fn buffer_error_display_insufficient_data() {
let msg = format!("{}", BufferError::InsufficientData);
assert_eq!(msg, "data slice is too small for the given dimensions");
}
#[test]
fn buffer_error_display_invalid_dimensions() {
let msg = format!("{}", BufferError::InvalidDimensions);
assert_eq!(msg, "width or height is zero or causes overflow");
}
#[test]
fn buffer_error_display_incompatible_descriptor() {
let msg = format!("{}", BufferError::IncompatibleDescriptor);
assert_eq!(msg, "new descriptor has different bytes_per_pixel");
}
#[test]
fn buffer_error_display_allocation_failed() {
let msg = format!("{}", BufferError::AllocationFailed);
assert_eq!(msg, "buffer allocation failed");
}
#[test]
fn pixel_slice_is_contiguous_tight() {
let buf = PixelBuffer::new(4, 3, PixelDescriptor::RGBA8_SRGB);
let slice = buf.as_slice();
assert_eq!(slice.stride(), 16);
assert!(slice.is_contiguous());
}
#[test]
fn pixel_slice_as_contiguous_bytes_tight() {
let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
{
let mut s = buf.as_slice_mut();
s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
s.row_mut(1)
.copy_from_slice(&[9, 10, 11, 12, 13, 14, 15, 16]);
}
let slice = buf.as_slice();
let bytes = slice.as_contiguous_bytes();
assert!(bytes.is_some());
assert_eq!(
bytes.unwrap(),
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
);
}
#[test]
fn pixel_slice_as_contiguous_bytes_padded_returns_none() {
let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
let slice = buf.as_slice();
assert!(slice.stride() > 6);
assert!(!slice.is_contiguous());
assert!(slice.as_contiguous_bytes().is_none());
}
#[test]
fn pixel_slice_as_strided_bytes_tight() {
let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
{
let mut s = buf.as_slice_mut();
s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
s.row_mut(1)
.copy_from_slice(&[9, 10, 11, 12, 13, 14, 15, 16]);
}
let slice = buf.as_slice();
let bytes = slice.as_strided_bytes();
assert_eq!(
bytes,
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
);
}
#[test]
fn pixel_slice_as_strided_bytes_padded() {
let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
let slice = buf.as_slice();
let stride = slice.stride();
assert!(stride > 6, "expected padding for SIMD alignment");
let bytes = slice.as_strided_bytes();
assert_eq!(bytes.len(), stride * 2);
}
#[test]
fn pixel_slice_as_strided_bytes_sub_rows() {
let buf = PixelBuffer::new_simd_aligned(2, 4, PixelDescriptor::RGB8_SRGB, 16);
let slice = buf.as_slice();
let stride = slice.stride();
let sub = slice.sub_rows(1, 2);
let bytes = sub.as_strided_bytes();
let expected_len = stride + 2 * 3; assert_eq!(bytes.len(), expected_len);
}
#[test]
fn pixel_slice_mut_as_strided_bytes() {
let mut buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
let mut slice = buf.as_slice_mut();
let stride = slice.stride();
let bytes = slice.as_strided_bytes_mut();
assert_eq!(bytes.len(), stride * 2);
bytes[0] = 42;
assert_eq!(slice.row(0)[0], 42);
}
#[test]
fn pixel_slice_row_with_stride_padded() {
let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGBA8_SRGB, 64);
let slice = buf.as_slice();
let stride = slice.stride();
assert!(stride >= 8);
let full_row = slice.row_with_stride(0);
assert_eq!(full_row.len(), stride);
let pixel_row = slice.row(0);
assert_eq!(pixel_row.len(), 8); }
#[test]
fn pixel_buffer_has_alpha_rgba8() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
assert!(buf.has_alpha());
}
#[test]
fn pixel_buffer_has_alpha_rgb8_false() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
assert!(!buf.has_alpha());
}
#[test]
fn pixel_buffer_is_grayscale_gray8() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::GRAY8_SRGB);
assert!(buf.is_grayscale());
}
#[test]
fn pixel_buffer_is_grayscale_rgb8_false() {
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
assert!(!buf.is_grayscale());
}
#[cfg(feature = "rgb")]
#[test]
fn pixel_slice_try_typed_success() {
use rgb::Rgba;
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
let slice = buf.as_slice();
let typed: Option<PixelSlice<'_, Rgba<u8>>> = slice.try_typed();
assert!(typed.is_some());
let typed = typed.unwrap();
assert_eq!(typed.width(), 2);
assert_eq!(typed.rows(), 2);
}
#[cfg(feature = "rgb")]
#[test]
fn pixel_buffer_try_typed_success() {
use rgb::Rgba;
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
let typed: Option<PixelBuffer<Rgba<u8>>> = buf.try_typed();
assert!(typed.is_some());
let typed = typed.unwrap();
assert_eq!(typed.width(), 2);
assert_eq!(typed.height(), 2);
}
#[cfg(feature = "rgb")]
#[test]
fn pixel_buffer_try_typed_failure_wrong_layout() {
use rgb::Rgba;
let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
let typed: Option<PixelBuffer<Rgba<u8>>> = buf.try_typed();
assert!(typed.is_none());
}
}
#[cfg(all(test, feature = "imgref"))]
mod buffer_tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
use rgb::{Gray, Rgb};
#[test]
fn imgref_to_pixel_slice_rgb8() {
let pixels: Vec<Rgb<u8>> = vec![
Rgb {
r: 10,
g: 20,
b: 30,
},
Rgb {
r: 40,
g: 50,
b: 60,
},
Rgb {
r: 70,
g: 80,
b: 90,
},
Rgb {
r: 100,
g: 110,
b: 120,
},
];
let img = imgref::Img::new(pixels.as_slice(), 2, 2);
let slice: PixelSlice<'_, Rgb<u8>> = img.into();
assert_eq!(slice.width(), 2);
assert_eq!(slice.rows(), 2);
assert_eq!(slice.row(0), &[10, 20, 30, 40, 50, 60]);
assert_eq!(slice.row(1), &[70, 80, 90, 100, 110, 120]);
}
#[test]
fn imgref_to_pixel_slice_gray16() {
let pixels = vec![Gray::new(1000u16), Gray::new(2000u16)];
let img = imgref::Img::new(pixels.as_slice(), 2, 1);
let slice: PixelSlice<'_, Gray<u16>> = img.into();
assert_eq!(slice.width(), 2);
assert_eq!(slice.rows(), 1);
assert_eq!(slice.descriptor(), PixelDescriptor::GRAY16);
let row = slice.row(0);
assert_eq!(row.len(), 4);
let v0 = u16::from_ne_bytes([row[0], row[1]]);
let v1 = u16::from_ne_bytes([row[2], row[3]]);
assert_eq!(v0, 1000);
assert_eq!(v1, 2000);
}
#[test]
fn from_pixels_erased_matches_manual() {
let pixels1: Vec<Rgb<u8>> = vec![
Rgb {
r: 10,
g: 20,
b: 30,
},
Rgb {
r: 40,
g: 50,
b: 60,
},
];
let pixels2 = pixels1.clone();
let manual: PixelBuffer = PixelBuffer::from_pixels(pixels1, 2, 1).unwrap().into();
let erased = PixelBuffer::from_pixels_erased(pixels2, 2, 1).unwrap();
assert_eq!(manual.width(), erased.width());
assert_eq!(manual.height(), erased.height());
assert_eq!(manual.descriptor(), erased.descriptor());
assert_eq!(manual.as_slice().row(0), erased.as_slice().row(0));
}
#[test]
fn from_pixels_erased_dimension_mismatch() {
let pixels: Vec<Rgb<u8>> = vec![Rgb { r: 1, g: 2, b: 3 }];
let err = PixelBuffer::from_pixels_erased(pixels, 2, 1);
assert_eq!(*err.unwrap_err().error(), BufferError::InvalidDimensions);
}
}