use std::{
borrow::Borrow,
fmt::{self, Debug, Formatter},
num::{NonZeroU8, NonZeroU32},
sync::Arc,
};
use crate::{
dynamic::DynamicImageChannel,
pixel::{DynamicSize, PixelType, PixelTypePrimitive, RuntimePixelType},
pixel_elements::PixelSize,
};
#[repr(transparent)]
pub struct ImageChannel<TP: RuntimePixelType>(UnsafeImageChannel<TP::Primitive>);
pub trait BorrowableImageChannel:
Borrow<ImageChannel<Self::Pixel>> + crate::seal::SealedImageChannel
{
type Pixel: PixelType;
}
impl<T: PixelType> BorrowableImageChannel for ImageChannel<T> {
type Pixel = T;
}
impl<T: PixelType> BorrowableImageChannel for &ImageChannel<T> {
type Pixel = T;
}
impl<T: PixelType> BorrowableImageChannel for &mut ImageChannel<T> {
type Pixel = T;
}
impl<TP: RuntimePixelType> Clone for ImageChannel<TP> {
fn clone(&self) -> Self {
Self(unsafe { (self.0.vtable.clone)(&self.0) })
}
}
impl<TP> PartialEq for ImageChannel<TP>
where
TP: RuntimePixelType,
TP::Primitive: std::cmp::PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.0.width == other.0.width
&& self.0.height == other.0.height
&& self.0.pixel_elements == other.0.pixel_elements
&& self.buffer_flat() == other.buffer_flat()
}
}
impl<TP> ImageChannel<TP>
where
TP: PixelType,
TP::Primitive: Clone,
{
#[must_use]
pub fn new_vec(mut input: Vec<TP>, width: NonZeroU32, height: NonZeroU32) -> Self {
assert_eq!(
input.len(),
calc_pixel_len_packed(width, height),
"Incompatible Buffer-Size"
);
let len = input.len();
let cap = input.capacity();
let ptr = input.as_mut_ptr().cast::<TP::Primitive>();
let len = len * TP::ELEMENTS.get() as usize;
let cap = cap * TP::ELEMENTS.get() as usize;
std::mem::forget(input);
let cast_input = unsafe { Vec::from_raw_parts(ptr, len, cap) };
Self(UnsafeImageChannel::new_vec(
cast_input,
width,
height,
TP::PixelSize::default().get(),
))
}
pub fn new_arc(input: Arc<[TP]>, width: NonZeroU32, height: NonZeroU32) -> Self {
let len = input.len();
let ptr = Arc::into_raw(input).cast::<TP::Primitive>();
let len = len * TP::ELEMENTS.get() as usize;
let cast_input = unsafe { Arc::from_raw(std::ptr::slice_from_raw_parts(ptr, len)) };
Self(UnsafeImageChannel::new_arc(
cast_input,
width,
height,
TP::PixelSize::default().get(),
))
}
#[must_use]
pub const fn buffer(&self) -> &[TP] {
unsafe { std::slice::from_raw_parts(self.0.ptr.cast::<TP>(), self.len()) }
}
#[must_use]
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
self.0.calc_len_packed()
}
pub fn make_mut(&mut self) -> &mut [TP] {
unsafe {
(self.0.vtable.make_mut)(&mut self.0);
let len = self.len_flat();
let len = len / TP::ELEMENTS.get() as usize;
std::slice::from_raw_parts_mut(self.0.ptr as *mut TP, len)
}
}
#[must_use]
pub fn into_vec(self) -> Vec<TP>
where
TP: Clone,
{
let vec_drop: unsafe extern "C" fn(&mut UnsafeImageChannel<TP::Primitive>) =
crate::vec::clear_vec_channel::<TP::Primitive>;
let mut vec = if std::ptr::fn_addr_eq(self.0.vtable.drop, vec_drop) {
let size = self.len_flat();
let result =
unsafe { Vec::from_raw_parts(self.0.ptr.cast_mut(), size, self.0.data as usize) };
std::mem::forget(self);
result
} else {
let len = self.len_flat();
let buf = unsafe { std::slice::from_raw_parts(self.0.ptr, len) };
buf.to_vec()
};
let ptr = vec.as_mut_ptr().cast::<TP>();
let len = vec.len() / TP::ELEMENTS.get() as usize;
let cap = vec.capacity() / TP::ELEMENTS.get() as usize;
std::mem::forget(vec);
unsafe { Vec::from_raw_parts(ptr, len, cap) }
}
pub unsafe fn new_with_vtable(
ptr: *const TP::Primitive,
width: NonZeroU32,
height: NonZeroU32,
vtable: &'static ImageChannelVTable<TP::Primitive>,
data: *mut (),
) -> Self
where
TP::Primitive: Send + Sync,
{
unsafe {
Self(UnsafeImageChannel::new_with_vtable(
ptr,
width,
height,
TP::PixelSize::default().get(),
vtable,
data,
))
}
}
}
impl<TP: PixelType> TryFrom<DynamicImageChannel> for ImageChannel<TP> {
type Error = DynamicImageChannel;
fn try_from(value: DynamicImageChannel) -> Result<Self, Self::Error> {
let typed = <TP::Primitive as PixelTypePrimitive>::try_from_dynamic_image(value)?;
if typed.0.pixel_elements == TP::ELEMENTS {
Ok(ImageChannel(typed.0))
} else {
Err(<TP::Primitive as PixelTypePrimitive>::into_runtime_channel(
typed,
))
}
}
}
impl<TP: PixelType> TryFrom<ImageChannel<DynamicSize<TP::Primitive>>> for ImageChannel<TP> {
type Error = DynamicImageChannel;
fn try_from(value: ImageChannel<DynamicSize<TP::Primitive>>) -> Result<Self, Self::Error> {
if value.pixel_elements() == TP::ELEMENTS {
Ok(ImageChannel(value.0))
} else {
Err(<TP::Primitive as PixelTypePrimitive>::into_runtime_channel(
value.into(),
))
}
}
}
impl<TP: PixelType> TryFrom<&DynamicImageChannel> for &ImageChannel<TP> {
type Error = ();
fn try_from(value: &DynamicImageChannel) -> Result<Self, Self::Error> {
let typed =
<TP::Primitive as PixelTypePrimitive>::try_from_dynamic_image_ref(value).ok_or(())?;
if typed.0.pixel_elements == TP::ELEMENTS {
Ok(unsafe { std::mem::transmute(&typed.0) })
} else {
Err(())
}
}
}
impl<TP: PixelType> TryFrom<&ImageChannel<DynamicSize<TP::Primitive>>> for &ImageChannel<TP> {
type Error = ();
fn try_from(value: &ImageChannel<DynamicSize<TP::Primitive>>) -> Result<Self, Self::Error> {
if value.pixel_elements() == TP::ELEMENTS {
Ok(unsafe { std::mem::transmute(value) })
} else {
Err(())
}
}
}
impl<TP: PixelType> TryFrom<&mut DynamicImageChannel> for &mut ImageChannel<TP> {
type Error = ();
fn try_from(value: &mut DynamicImageChannel) -> Result<Self, Self::Error> {
let typed =
<TP::Primitive as PixelTypePrimitive>::try_from_dynamic_image_mut(value).ok_or(())?;
if typed.0.pixel_elements == TP::ELEMENTS {
Ok(unsafe { std::mem::transmute(&mut typed.0) })
} else {
Err(())
}
}
}
impl<TP: PixelType> TryFrom<&mut ImageChannel<DynamicSize<TP::Primitive>>>
for &mut ImageChannel<TP>
{
type Error = ();
fn try_from(value: &mut ImageChannel<DynamicSize<TP::Primitive>>) -> Result<Self, Self::Error> {
if value.pixel_elements() == TP::ELEMENTS {
Ok(unsafe { std::mem::transmute(value) })
} else {
Err(())
}
}
}
impl<TP: PixelType> From<ImageChannel<TP>> for DynamicImageChannel {
fn from(value: ImageChannel<TP>) -> Self {
ImageChannel::<DynamicSize<TP::Primitive>>::from(value).into()
}
}
impl<TP: PixelTypePrimitive> From<ImageChannel<DynamicSize<TP>>> for DynamicImageChannel {
fn from(value: ImageChannel<DynamicSize<TP>>) -> Self {
TP::into_runtime_channel(value)
}
}
impl<TP: PixelType> From<ImageChannel<TP>> for ImageChannel<DynamicSize<TP::Primitive>> {
fn from(value: ImageChannel<TP>) -> Self {
ImageChannel(value.0)
}
}
impl<TP: RuntimePixelType> ImageChannel<TP> {
#[must_use]
pub(crate) fn from_unsafe_internal(unsafe_channel: UnsafeImageChannel<TP::Primitive>) -> Self {
Self(unsafe_channel)
}
#[allow(clippy::len_without_is_empty)]
#[must_use]
pub const fn len_flat(&self) -> usize {
self.0.calc_len_flat()
}
#[must_use]
pub const fn buffer_flat(&self) -> &[TP::Primitive] {
let len = self.len_flat();
unsafe { std::slice::from_raw_parts(self.0.ptr, len) }
}
#[must_use]
pub const fn buffer_flat_bytes(&self) -> &[u8] {
let len = self.len_flat();
unsafe {
std::slice::from_raw_parts(
self.0.ptr.cast(),
len * std::mem::size_of::<TP::Primitive>(),
)
}
}
pub fn primitive_make_mut(&mut self) -> &mut [TP::Primitive] {
unsafe {
(self.0.vtable.make_mut)(&mut self.0);
let len = self.len_flat();
std::slice::from_raw_parts_mut(self.0.ptr.cast_mut(), len)
}
}
#[must_use]
pub fn into_vec_flat(self) -> Vec<TP::Primitive>
where
TP::Primitive: Clone,
{
let vec_drop: unsafe extern "C" fn(&mut UnsafeImageChannel<TP::Primitive>) =
crate::vec::clear_vec_channel::<TP::Primitive>;
if std::ptr::fn_addr_eq(self.0.vtable.drop, vec_drop) {
let size = self.len_flat();
let result =
unsafe { Vec::from_raw_parts(self.0.ptr.cast_mut(), size, self.0.data as usize) };
std::mem::forget(self);
result
} else {
self.buffer_flat().to_vec()
}
}
#[must_use]
pub const fn width(&self) -> NonZeroU32 {
self.0.width
}
#[must_use]
pub const fn height(&self) -> NonZeroU32 {
self.0.height
}
#[must_use]
pub const fn dimensions(&self) -> (NonZeroU32, NonZeroU32) {
(self.0.width, self.0.height)
}
pub const fn dimensions_zeroable(&self) -> (u32, u32) {
(self.0.width.get(), self.0.height.get())
}
#[must_use]
pub const fn pixel_elements(&self) -> NonZeroU8 {
self.0.pixel_elements
}
}
impl<T: PixelTypePrimitive> ImageChannel<DynamicSize<T>> {
pub fn new_vec_dynamic(
data: Vec<T>,
width: NonZeroU32,
height: NonZeroU32,
pixel_elements: NonZeroU8,
) -> ImageChannel<DynamicSize<T>> {
Self(UnsafeImageChannel::new_vec(
data,
width,
height,
pixel_elements,
))
}
#[must_use]
pub fn try_cast<TP: PixelType<Primitive = T>>(&self) -> Option<&ImageChannel<TP>> {
if self.pixel_elements() == TP::ELEMENTS {
Some(unsafe { &*(std::ptr::from_ref(self).cast::<ImageChannel<TP>>()) })
} else {
None
}
}
}
impl<TP: RuntimePixelType> Debug for ImageChannel<TP>
where
TP::Primitive: std::any::Any,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("ImageChannel")
.field("width", &self.0.width)
.field("height", &self.0.height)
.field("pixel", &std::any::type_name::<TP::Primitive>())
.field("pixel_elements", &self.0.pixel_elements.get())
.finish()
}
}
unsafe impl<TP: RuntimePixelType> Send for ImageChannel<TP> where TP::Primitive: Send {}
unsafe impl<TP: RuntimePixelType> Sync for ImageChannel<TP> where TP::Primitive: Sync {}
#[repr(C)]
pub struct ImageChannelVTable<T: 'static> {
pub clone: unsafe extern "C" fn(&UnsafeImageChannel<T>) -> UnsafeImageChannel<T>,
pub make_mut: unsafe extern "C" fn(&mut UnsafeImageChannel<T>),
pub drop: unsafe extern "C" fn(&mut UnsafeImageChannel<T>),
}
#[repr(C)]
pub struct UnsafeImageChannel<T: 'static> {
pub ptr: *const T,
pub width: NonZeroU32,
pub height: NonZeroU32,
pub vtable: &'static ImageChannelVTable<T>,
pub data: *mut (),
pub pixel_elements: NonZeroU8,
}
impl<T: 'static> UnsafeImageChannel<T> {
pub unsafe fn new_with_vtable(
ptr: *const T,
width: NonZeroU32,
height: NonZeroU32,
pixel_elements: NonZeroU8,
vtable: &'static ImageChannelVTable<T>,
data: *mut (),
) -> Self {
UnsafeImageChannel {
ptr,
width,
height,
vtable,
data,
pixel_elements,
}
}
pub(crate) const fn buffer_flat(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.ptr, self.calc_len_flat()) }
}
pub(crate) const fn calc_len_packed(&self) -> usize {
calc_pixel_len_packed(self.width, self.height)
}
pub(crate) const fn calc_len_flat(&self) -> usize {
calc_pixel_len_flat(self.width, self.height, self.pixel_elements)
}
}
const fn calc_pixel_len_packed(width: NonZeroU32, height: NonZeroU32) -> usize {
#[allow(clippy::cast_possible_truncation)]
let width_usize = width.get() as usize;
#[allow(clippy::cast_possible_truncation)]
let height_usize = height.get() as usize;
width_usize * height_usize
}
pub(crate) const fn calc_pixel_len_flat(
width: NonZeroU32,
height: NonZeroU32,
pixel_elements: NonZeroU8,
) -> usize {
calc_pixel_len_packed(width, height) * pixel_elements.get() as usize
}
impl<T> Drop for UnsafeImageChannel<T> {
fn drop(&mut self) {
if self.ptr as usize != 0 {
unsafe { (self.vtable.drop)(self) };
}
}
}
pub(crate) trait ChannelFactory<T: 'static> {
const VTABLE: &'static ImageChannelVTable<T>;
}
#[cfg(test)]
mod tests {
use crate::{DynamicImage, Image};
use super::*;
#[test]
fn try_from_dynamic_size_channel() {
let size = NonZeroU32::MIN;
let data = vec![[1u8, 2, 3]];
let ch = ImageChannel::<[u8; 3]>::new_vec(data, size, size);
let image: Image<[u8; 3], 1> = [ch].try_into().unwrap();
let dyn_image: DynamicImage = image.into();
let (restored_channel, mut rest) = dyn_image.into_mapped_channels(|x| match x {
DynamicImageChannel::U8(x) => ImageChannel::<[u8; 3]>::try_from(x).unwrap(),
_ => unreachable!(),
});
assert_eq!(None, rest.next());
assert_eq!(restored_channel.buffer(), &[[1u8, 2, 3]]);
Image::<[u8; 3], 1>::try_from([restored_channel]).unwrap();
}
#[test]
fn try_cast_dynamic_size_channel() {
let size = NonZeroU32::MIN;
let data = vec![[1u8, 2, 3]];
let data_ptr = data.as_ptr() as usize;
let ch = ImageChannel::<[u8; 3]>::new_vec(data, size, size);
let image: Image<[u8; 3], 1> = [ch].try_into().unwrap();
let dyn_image: DynamicImage = image.into();
let DynamicImageChannel::U8(dyn_u8_ch) = &dyn_image[0] else {
panic!("Expected DynamicImageChannel::U8");
};
let as_rgb = dyn_u8_ch.try_cast::<[u8; 3]>().unwrap();
assert_eq!(as_rgb.buffer(), &[[1u8, 2, 3]]);
assert_eq!(
as_rgb.buffer().as_ptr() as usize,
data_ptr,
"Should share the buffer"
);
assert!(dyn_u8_ch.try_cast::<u8>().is_none());
assert!(dyn_u8_ch.try_cast::<[u8; 4]>().is_none());
}
#[test]
fn miri_create_and_clear_vec_image_channel() {
let size = 2.try_into().unwrap();
let image = ImageChannel::<u8>::new_vec(vec![0u8, 64u8, 128u8, 192u8], size, size);
assert_eq!(image.buffer(), &[0u8, 64u8, 128u8, 192u8]);
}
#[test]
fn miri_to_vec_reuses_pointer() {
let raw = vec![0u8, 64u8, 128u8, 192u8];
let pointer = raw[..].as_ptr();
let size = 2.try_into().unwrap();
let image = ImageChannel::<u8>::new_vec(raw, size, size);
let to_vec = image.into_vec();
if !cfg!(miri) {
assert_eq!(
to_vec[..].as_ptr(),
pointer,
"Should reuse the buffer if it was created by vec"
);
}
}
#[test]
fn miri_make_mut_reuses_arc_pointer() {
let raw = Arc::<[u8]>::from([0u8, 64u8, 128u8, 192u8].as_slice());
let pointer = raw[..].as_ptr();
let size = 2.try_into().unwrap();
let mut image = ImageChannel::<u8>::new_arc(raw, size, size);
let ptr_mut = image.make_mut();
assert_eq!(
ptr_mut[..].as_ptr(),
pointer,
"Should reuse the buffer if it was created by arc"
);
}
#[test]
fn miri_make_mut_doesnt_reuse_arc_pointer_if_not_unique() {
let raw = Arc::<[u8]>::from([0u8, 64u8, 128u8, 192u8].as_slice());
let _raw2 = raw.clone();
let pointer = raw[..].as_ptr();
let size = 2.try_into().unwrap();
let mut image = ImageChannel::<u8>::new_arc(raw, size, size);
let ptr_mut = image.make_mut();
assert_ne!(
ptr_mut[..].as_ptr(),
pointer,
"Should not reuse the buffer if arc is not unique"
);
}
#[test]
fn miri_clone_arc_backed_shares_memory() {
let raw = Arc::<[u8]>::from([0u8, 64u8, 128u8, 192u8].as_slice());
let pointer = raw[..].as_ptr();
let size = 2.try_into().unwrap();
let image = ImageChannel::<u8>::new_arc(raw, size, size);
let image2 = image.clone();
assert_eq!(
image2.buffer().as_ptr(),
pointer,
"Should share the buffer if it was created by arc"
);
}
#[test]
fn miri_clone_from_vec() {
let raw = vec![0u8, 64u8, 128u8, 192u8];
let size = 2.try_into().unwrap();
let image = ImageChannel::<u8>::new_vec(raw, size, size);
let image2 = image.clone();
let to_vec = image.into_vec();
let to_vec2 = image2.into_vec();
assert_ne!(
to_vec[..].as_ptr(),
to_vec2[..].as_ptr(),
"Should not share the buffer if it was created by vec"
);
}
#[test]
fn miri_test_shared_arc_u16_channel() {
let arc: Arc<[u16]> = vec![1].into();
test_entire_vtable(ImageChannel::<u16>::new_arc(
arc,
NonZeroU32::MIN,
NonZeroU32::MIN,
));
}
#[test]
fn miri_test_exclusive_arc_u16_channel() {
test_entire_vtable(ImageChannel::<u16>::new_arc(
vec![1u16].into(),
NonZeroU32::MIN,
NonZeroU32::MIN,
));
}
#[test]
fn miri_test_vec_u16_channel() {
test_entire_vtable(ImageChannel::<u16>::new_vec(
vec![1u16],
NonZeroU32::MIN,
NonZeroU32::MIN,
));
}
#[test]
fn miri_test_vec_rgb16_channel() {
test_entire_vtable(ImageChannel::<[u16; 3]>::new_vec(
vec![[1u16, 2u16, 3u16]],
NonZeroU32::MIN,
NonZeroU32::MIN,
));
}
fn test_entire_vtable<TP: RuntimePixelType>(mut image: ImageChannel<TP>)
where
TP::Primitive: 'static + Default + Eq + Debug,
{
image.primitive_make_mut()[0] = TP::Primitive::default();
let clone = image.clone();
assert_eq!(image.primitive_make_mut()[0], TP::Primitive::default());
image.primitive_make_mut()[0] = TP::Primitive::default();
assert_eq!(image, clone);
}
#[test]
fn miri_test_buffer_flat_bytes() {
let image = ImageChannel::new_vec(vec![42u16], NonZeroU32::MIN, NonZeroU32::MIN);
assert_eq!(image.buffer_flat_bytes(), &[42u8, 0u8]);
}
}