use crate::{IndexedColorMap, IndexedImage, MAX_PIXELS};
use alloc::{borrow::ToOwned, vec, vec::Vec};
use bytemuck::Zeroable;
use core::{
error::Error,
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreateImageError {
width: u32,
height: u32,
length: usize,
}
impl fmt::Display for CreateImageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { width, height, length } = *self;
if width.checked_mul(height).is_some() {
write!(
f,
"image dimensions of ({width}, {height}) do not match the buffer length of {length}"
)
} else {
write!(
f,
"image dimensions of ({width}, {height}) are above the maximum number of pixels of {MAX_PIXELS}",
)
}
}
}
impl Error for CreateImageError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreateImageBufError<T> {
pub error: CreateImageError,
pub buffer: T,
}
impl<T> fmt::Display for CreateImageBufError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.error, f)
}
}
impl<T: Debug> Error for CreateImageBufError<T> {}
#[derive(Clone, Copy, Debug)]
pub struct Image<Color, Container> {
color: PhantomData<Color>,
width: u32,
height: u32,
pixels: Container,
}
pub type ImageBuf<Color> = Image<Color, Vec<Color>>;
pub type ImageRef<'a, Color> = Image<Color, &'a [Color]>;
pub type ImageMut<'a, Color> = Image<Color, &'a mut [Color]>;
impl<Color, Container> Image<Color, Container> {
#[inline]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[inline]
pub fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
#[inline]
pub fn as_inner(&self) -> &Container {
&self.pixels
}
#[must_use]
#[inline]
pub fn into_inner(self) -> Container {
self.pixels
}
}
impl<Color, Container: AsRef<[Color]>> Image<Color, Container> {
#[inline]
pub(crate) fn new_unchecked(width: u32, height: u32, pixels: Container) -> Self {
debug_assert_eq!(
width.checked_mul(height).map(|len| len as usize),
Some(pixels.as_ref().len())
);
Self {
color: PhantomData,
width,
height,
pixels,
}
}
#[inline]
pub fn new(
width: u32,
height: u32,
pixels: Container,
) -> Result<Self, CreateImageBufError<Container>> {
let length = pixels.as_ref().len();
if width.checked_mul(height).map(|len| len as usize) == Some(length) {
Ok(Self::new_unchecked(width, height, pixels))
} else {
let error = CreateImageError { width, height, length };
Err(CreateImageBufError { error, buffer: pixels })
}
}
#[allow(clippy::cast_possible_truncation)]
#[inline]
pub fn num_pixels(&self) -> u32 {
self.pixels.as_ref().len() as u32
}
#[inline]
pub fn as_slice(&self) -> &[Color] {
self.pixels.as_ref()
}
#[inline]
pub fn as_ref(&self) -> ImageRef<'_, Color> {
let (width, height) = self.dimensions();
Image::new_unchecked(width, height, self.as_slice())
}
#[allow(clippy::missing_panics_doc)]
#[must_use]
#[inline]
pub fn to_owned(&self) -> Image<Color, Container::Owned>
where
Container: ToOwned,
Container::Owned: AsRef<[Color]>,
{
let (width, height) = self.dimensions();
let owned = self.pixels.to_owned();
assert_eq!(
self.pixels.as_ref().len(),
owned.as_ref().len(),
"the owned container to have the same length as the original container"
);
Image::new_unchecked(width, height, owned)
}
#[must_use]
#[inline]
pub fn map<NewColor, NewContainer>(
self,
mapping: impl FnOnce(Container) -> NewContainer,
) -> Image<NewColor, NewContainer>
where
NewContainer: AsRef<[NewColor]>,
{
let num_pixels = self.num_pixels();
let (width, height) = self.dimensions();
let pixels = mapping(self.pixels);
assert_eq!(pixels.as_ref().len(), num_pixels as usize);
Image::new_unchecked(width, height, pixels)
}
#[must_use]
#[inline]
pub fn map_ref<NewColor, NewContainer>(
&self,
mapping: impl FnOnce(&[Color]) -> NewContainer,
) -> Image<NewColor, NewContainer>
where
NewContainer: AsRef<[NewColor]>,
{
let num_pixels = self.num_pixels();
let (width, height) = self.dimensions();
let pixels = mapping(self.pixels.as_ref());
assert_eq!(pixels.as_ref().len(), num_pixels as usize);
Image::new_unchecked(width, height, pixels)
}
#[must_use]
#[inline]
pub fn map_to_image<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> ImageBuf<ColorMap::Output> {
self.map_ref(|pixels| color_map.map_to_colors(pixels))
}
#[must_use]
#[inline]
pub fn map_to_indexed<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> IndexedImage<ColorMap::Output> {
let indices = color_map.map_to_indices(self.as_slice());
assert_eq!(indices.len(), self.num_pixels() as usize);
let (width, height) = self.dimensions();
IndexedImage::new_unchecked(width, height, color_map.into_palette().into_vec(), indices)
}
}
impl<Color, Container: AsMut<[Color]>> Image<Color, Container> {
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [Color] {
self.pixels.as_mut()
}
#[inline]
pub fn as_mut(&mut self) -> ImageMut<'_, Color> {
let (width, height) = self.dimensions();
Image::new_unchecked(width, height, self.as_mut_slice())
}
}
impl<Color: Zeroable> ImageBuf<Color> {
#[must_use]
#[inline]
pub fn zeroed(width: u32, height: u32) -> Option<Self> {
let len = width.checked_mul(height)?;
let pixels = bytemuck::zeroed_vec(len as usize);
Some(Self::new_unchecked(width, height, pixels))
}
}
impl<Color: Clone> ImageBuf<Color> {
#[must_use]
#[inline]
pub fn from_pixel(width: u32, height: u32, pixel: Color) -> Option<Self> {
let len = width.checked_mul(height)?;
let pixels = vec![pixel; len as usize];
Some(Self::new_unchecked(width, height, pixels))
}
}
impl<Color> Default for ImageBuf<Color> {
#[inline]
fn default() -> Self {
Self::new_unchecked(0, 0, Vec::new())
}
}
impl<Color> Default for ImageRef<'_, Color> {
#[inline]
fn default() -> Self {
Self::new_unchecked(0, 0, &[])
}
}
impl<Color> Default for ImageMut<'_, Color> {
#[inline]
fn default() -> Self {
Self::new_unchecked(0, 0, &mut [])
}
}
impl<ColorA, ColorB, ContainerA, ContainerB> PartialEq<Image<ColorB, ContainerB>>
for Image<ColorA, ContainerA>
where
ColorA: PartialEq<ColorB>,
ContainerA: AsRef<[ColorA]>,
ContainerB: AsRef<[ColorB]>,
{
fn eq(&self, other: &Image<ColorB, ContainerB>) -> bool {
self.dimensions() == other.dimensions() && self.as_slice() == other.as_slice()
}
}
impl<Color, Container> Eq for Image<Color, Container>
where
Color: PartialEq<Color>,
Container: AsRef<[Color]>,
{
}
impl<Color, Container> Hash for Image<Color, Container>
where
Color: Hash,
Container: AsRef<[Color]>,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.width.hash(state);
self.height.hash(state);
self.pixels.as_ref().hash(state);
}
}
#[cfg(feature = "image")]
mod image_integration {
use super::{CreateImageBufError, CreateImageError, Image, ImageBuf, ImageMut, ImageRef};
use crate::LengthOutOfRange;
use core::ops::{Deref, DerefMut};
use image::{ImageBuffer, Pixel, Rgb};
use palette::{
ArrayExt, Srgb,
cast::{
ArrayCast, ComponentsAs as _, ComponentsAsMut as _, ComponentsInto as _,
IntoComponents as _,
},
};
impl<Component> From<ImageBuf<Srgb<Component>>> for ImageBuffer<Rgb<Component>, Vec<Component>>
where
Srgb<Component>: ArrayCast,
<Srgb<Component> as ArrayCast>::Array: ArrayExt<Item = Component>,
Rgb<Component>: Pixel<Subpixel = Component>,
Vec<Component>: Deref<Target = [<Rgb<Component> as Pixel>::Subpixel]>,
{
#[allow(clippy::expect_used)]
fn from(image: ImageBuf<Srgb<Component>>) -> Self {
let Image { width, height, pixels, .. } = image;
ImageBuffer::from_raw(width, height, pixels.into_components())
.expect("buffer is large enough")
}
}
impl<Component> TryFrom<ImageBuffer<Rgb<Component>, Vec<Component>>> for ImageBuf<Srgb<Component>>
where
Srgb<Component>: ArrayCast,
<Srgb<Component> as ArrayCast>::Array: ArrayExt<Item = Component>,
Rgb<Component>: Pixel<Subpixel = Component>,
Vec<Component>: Deref<Target = [<Rgb<Component> as Pixel>::Subpixel]>,
{
type Error = CreateImageBufError<ImageBuffer<Rgb<Component>, Vec<Component>>>;
fn try_from(
image: ImageBuffer<Rgb<Component>, Vec<Component>>,
) -> Result<Self, Self::Error> {
let (width, height) = image.dimensions();
if let Some(len) = width.checked_mul(height) {
let mut buf = image.into_raw();
buf.truncate(len as usize * 3);
assert_eq!(buf.len(), len as usize * 3); let pixels = buf.components_into();
Ok(Self::new_unchecked(width, height, pixels))
} else {
let error = CreateImageError {
width,
height,
length: image.pixels().len(),
};
Err(CreateImageBufError { error, buffer: image })
}
}
}
impl<'a, Component, Container> TryFrom<&'a ImageBuffer<Rgb<Component>, Container>>
for ImageRef<'a, Srgb<Component>>
where
Rgb<Component>: Pixel<Subpixel = Component>,
Container: Deref<Target = [<Rgb<Component> as Pixel>::Subpixel]>,
{
type Error = LengthOutOfRange;
fn try_from(
image: &'a ImageBuffer<Rgb<Component>, Container>,
) -> Result<Self, Self::Error> {
let (width, height) = image.dimensions();
let len = LengthOutOfRange::check_dimensions(width, height)?;
let slice = &image.as_raw()[..len as usize * 3];
let pixels = slice.components_as();
Ok(Self::new_unchecked(width, height, pixels))
}
}
impl<'a, Component, Container> TryFrom<&'a mut ImageBuffer<Rgb<Component>, Container>>
for ImageMut<'a, Srgb<Component>>
where
Rgb<Component>: Pixel<Subpixel = Component>,
Container: Deref<Target = [<Rgb<Component> as Pixel>::Subpixel]> + DerefMut,
{
type Error = LengthOutOfRange;
fn try_from(
image: &'a mut ImageBuffer<Rgb<Component>, Container>,
) -> Result<Self, Self::Error> {
let (width, height) = image.dimensions();
let len = LengthOutOfRange::check_dimensions(width, height)?;
let slice = &mut image.deref_mut()[..len as usize * 3];
let pixels = slice.components_as_mut();
Ok(Self::new_unchecked(width, height, pixels))
}
}
}
#[cfg(feature = "image")]
#[allow(unused_imports)]
pub use image_integration::*;