use crate::{BoundedIndex, ImageBuf, IndexedColorMap, MAX_PIXELS, PaletteCounts};
use alloc::{borrow::ToOwned as _, vec, vec::Vec};
use core::{
error::Error,
fmt::{self, Debug},
};
use num_traits::AsPrimitive;
#[cfg(feature = "threads")]
use rayon::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateIndexedImageError<Color, Index = u8> {
width: u32,
height: u32,
palette: Vec<Color>,
indices: Vec<Index>,
}
impl<Color, Index> CreateIndexedImageError<Color, Index> {
#[inline]
pub fn palette(&self) -> &[Color] {
&self.palette
}
#[inline]
pub fn indices(&self) -> &[Index] {
&self.indices
}
#[must_use]
#[inline]
pub fn into_parts(self) -> (Vec<Color>, Vec<Index>) {
let Self { palette, indices, .. } = self;
(palette, indices)
}
}
impl<Color, Index> fmt::Display for CreateIndexedImageError<Color, Index> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { width, height, .. } = *self;
if width.checked_mul(height).is_some() {
write!(
f,
"image dimensions of ({width}, {height}) do not match the indices length of {}",
self.indices.len(),
)
} else {
write!(
f,
"image dimensions of ({width}, {height}) are above the maximum number of pixels of {MAX_PIXELS}",
)
}
}
}
impl<Color: Debug, Index: Debug> Error for CreateIndexedImageError<Color, Index> {}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IndexedImage<Color, Index = u8> {
width: u32,
height: u32,
palette: Vec<Color>,
indices: Vec<Index>,
}
impl<Color, Index: BoundedIndex> IndexedImage<Color, Index> {
pub(crate) fn new_unchecked(
width: u32,
height: u32,
palette: Vec<Color>,
indices: Vec<Index>,
) -> Self {
debug_assert_eq!(
width.checked_mul(height).map(|len| len as usize),
Some(indices.len()),
);
debug_assert!(indices.iter().all(|i| i.as_() < palette.len()));
Self { width, height, palette, indices }
}
pub fn new(
width: u32,
height: u32,
palette: Vec<Color>,
indices: Vec<Index>,
) -> Result<Self, CreateIndexedImageError<Color, Index>> {
if width.checked_mul(height).map(|len| len as usize) == Some(indices.len()) {
Ok(Self::new_unchecked(width, height, palette, indices))
} else {
Err(CreateIndexedImageError { width, height, palette, indices })
}
}
#[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.indices.is_empty()
}
#[allow(clippy::cast_possible_truncation)]
#[inline]
pub fn num_pixels(&self) -> u32 {
self.indices.len() as u32
}
#[inline]
pub fn palette(&self) -> &[Color] {
&self.palette
}
#[inline]
pub fn palette_mut(&mut self) -> &mut [Color] {
&mut self.palette
}
#[inline]
pub fn indices(&self) -> &[Index] {
&self.indices
}
#[inline]
pub fn indices_mut(&mut self) -> &mut [Index] {
&mut self.indices
}
#[must_use]
#[inline]
pub fn into_parts(self) -> (Vec<Color>, Vec<Index>) {
let Self { palette, indices, .. } = self;
(palette, indices)
}
#[must_use]
pub fn to_larger_indices<NewIndex>(&self) -> IndexedImage<Color, NewIndex>
where
Color: Clone,
NewIndex: BoundedIndex,
Index: Into<NewIndex>,
{
let Self { width, height, palette, indices, .. } = self;
IndexedImage::new_unchecked(
*width,
*height,
palette.clone(),
indices.iter().map(|&i| i.into()).collect(),
)
}
#[must_use]
pub fn to_smaller_indices<NewIndex>(&self) -> Option<IndexedImage<Color, NewIndex>>
where
Color: Clone,
NewIndex: BoundedIndex,
Index: AsPrimitive<NewIndex>,
{
let Self { width, height, palette, indices, .. } = self;
(palette.len() <= NewIndex::MAX_LEN).then(|| {
let palette = palette.to_owned();
let indices = indices.iter().map(|i| i.as_()).collect();
IndexedImage::new_unchecked(*width, *height, palette, indices)
})
}
#[must_use]
pub fn into_larger_indices<NewIndex>(self) -> IndexedImage<Color, NewIndex>
where
NewIndex: BoundedIndex,
Index: Into<NewIndex>,
{
let Self { width, height, palette, indices, .. } = self;
IndexedImage::new_unchecked(
width,
height,
palette,
indices.into_iter().map(Into::into).collect(),
)
}
pub fn into_smaller_indices<NewIndex>(self) -> Result<IndexedImage<Color, NewIndex>, Self>
where
NewIndex: BoundedIndex,
Index: AsPrimitive<NewIndex>,
{
if self.palette.len() <= NewIndex::MAX_LEN {
let Self { width, height, palette, indices, .. } = self;
let indices = indices.into_iter().map(AsPrimitive::as_).collect();
Ok(IndexedImage::new_unchecked(width, height, palette, indices))
} else {
Err(self)
}
}
#[allow(clippy::type_complexity)]
#[inline]
pub fn replace_palette<NewColor>(
self,
palette: Vec<NewColor>,
) -> Result<(IndexedImage<NewColor, Index>, Vec<Color>), (Self, Vec<NewColor>)> {
if self.palette.len() == palette.len() {
let Self {
width,
height,
indices,
palette: old_palette,
..
} = self;
Ok((
IndexedImage::new_unchecked(width, height, palette, indices),
old_palette,
))
} else {
Err((self, palette))
}
}
#[must_use]
#[inline]
pub fn map<NewColor>(
self,
mapping: impl FnOnce(Vec<Color>) -> Vec<NewColor>,
) -> IndexedImage<NewColor, Index> {
let Self { width, height, palette, indices, .. } = self;
let len = palette.len();
let palette = mapping(palette);
assert_eq!(palette.len(), len);
IndexedImage::new_unchecked(width, height, palette, indices)
}
#[must_use]
#[inline]
pub fn map_ref<NewColor>(
&self,
mapping: impl FnOnce(&[Color]) -> Vec<NewColor>,
) -> IndexedImage<NewColor, Index> {
let len = self.palette().len();
let palette = mapping(self.palette());
assert_eq!(palette.len(), len);
IndexedImage::new_unchecked(self.width, self.height, palette, self.indices.clone())
}
#[must_use]
pub fn map_to_indexed<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> IndexedImage<ColorMap::Output> {
let indices = color_map.map_to_indices(&self.palette);
assert_eq!(indices.len(), self.palette.len());
let indices = {
let indices = indices.as_slice(); self.indices.iter().map(|i| indices[i.as_()]).collect()
};
IndexedImage::new_unchecked(
self.width,
self.height,
color_map.into_palette().into_vec(),
indices,
)
}
#[cfg(feature = "threads")]
#[must_use]
pub fn map_to_indexed_par<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> IndexedImage<ColorMap::Output> {
let indices = color_map.map_to_indices(&self.palette);
assert_eq!(indices.len(), self.palette.len());
let indices = {
let indices = indices.as_slice(); self.indices.par_iter().map(|i| indices[i.as_()]).collect()
};
IndexedImage::new_unchecked(
self.width,
self.height,
color_map.into_palette().into_vec(),
indices,
)
}
#[must_use]
pub fn map_to_image<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> ImageBuf<ColorMap::Output> {
let Self { width, height, .. } = *self;
let palette = color_map.map_to_colors(&self.palette);
assert_eq!(palette.len(), self.palette.len());
let palette = palette.as_slice();
let pixels = self
.indices
.iter()
.map(|i| palette[i.as_()].clone())
.collect();
ImageBuf::new_unchecked(width, height, pixels)
}
#[cfg(feature = "threads")]
#[must_use]
pub fn map_to_image_par<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> ImageBuf<ColorMap::Output> {
let Self { width, height, .. } = *self;
let palette = color_map.map_to_colors(&self.palette);
assert_eq!(palette.len(), self.palette.len());
let palette = palette.as_slice();
let pixels = self
.indices
.par_iter()
.map(|i| palette[i.as_()].clone())
.collect();
ImageBuf::new_unchecked(width, height, pixels)
}
#[must_use]
pub fn into_indexed_image_counts(self) -> IndexedImageCounts<Color, Index> {
let mut counts = vec![0; self.palette.len()];
for i in &self.indices {
counts[i.as_()] += 1;
}
IndexedImageCounts { image: self, counts }
}
#[must_use]
pub fn to_image(&self) -> ImageBuf<Color>
where
Color: Clone,
{
let Self { width, height, .. } = *self;
let palette = self.palette.as_slice();
let pixels = self
.indices
.iter()
.map(|i| palette[i.as_()].clone())
.collect();
ImageBuf::new_unchecked(width, height, pixels)
}
#[cfg(feature = "threads")]
#[must_use]
pub fn to_image_par(&self) -> ImageBuf<Color>
where
Color: Clone + Send + Sync,
{
let Self { width, height, .. } = *self;
let palette = self.palette.as_slice();
let pixels = self
.indices
.par_iter()
.map(|i| palette[i.as_()].clone())
.collect();
ImageBuf::new_unchecked(width, height, pixels)
}
}
impl<Color, Index: BoundedIndex> Default for IndexedImage<Color, Index> {
#[inline]
fn default() -> Self {
Self::new_unchecked(0, 0, Vec::new(), Vec::new())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IndexedImageCounts<Color, Index = u8> {
image: IndexedImage<Color, Index>,
counts: Vec<u32>,
}
impl<Color, Index: BoundedIndex> IndexedImageCounts<Color, Index> {
#[inline]
pub(crate) fn new_unchecked(
width: u32,
height: u32,
palette: Vec<Color>,
counts: Vec<u32>,
indices: Vec<Index>,
) -> Self {
let image = IndexedImage::new_unchecked(width, height, palette, indices);
Self::from_indexed_image_unchecked(image, counts)
}
#[inline]
pub(crate) fn from_palette_counts_unchecked(
width: u32,
height: u32,
palette_counts: PaletteCounts<Color>,
indices: Vec<Index>,
) -> Self {
let (palette, counts) = palette_counts.into_parts();
Self::new_unchecked(width, height, palette, counts, indices)
}
#[inline]
pub(crate) fn from_indexed_image_unchecked(
image: IndexedImage<Color, Index>,
counts: Vec<u32>,
) -> Self {
debug_assert_eq!(image.palette.len(), counts.len());
debug_assert_eq!(
image.indices().len(),
counts.iter().copied().sum::<u32>() as usize,
);
Self { image, counts }
}
#[inline]
pub fn dimensions(&self) -> (u32, u32) {
self.image.dimensions()
}
#[inline]
pub fn width(&self) -> u32 {
self.image.width()
}
#[inline]
pub fn height(&self) -> u32 {
self.image.height()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.image.is_empty()
}
#[inline]
pub fn num_pixels(&self) -> u32 {
self.image.num_pixels()
}
#[inline]
pub fn as_indexed_image(&self) -> &IndexedImage<Color, Index> {
&self.image
}
#[inline]
pub fn palette(&self) -> &[Color] {
self.image.palette()
}
#[inline]
pub fn palette_mut(&mut self) -> &mut [Color] {
self.image.palette_mut()
}
#[inline]
pub fn counts(&self) -> &[u32] {
&self.counts
}
#[inline]
pub fn indices(&self) -> &[Index] {
self.image.indices()
}
#[inline]
pub fn total_count(&self) -> u32 {
self.image.num_pixels()
}
#[must_use]
#[inline]
pub fn into_indexed_image(self) -> IndexedImage<Color, Index> {
self.image
}
#[must_use]
#[inline]
pub fn into_palette_counts(self) -> PaletteCounts<Color> {
let total_count = self.total_count();
let Self { image, counts, .. } = self;
PaletteCounts::new_unchecked(image.palette, counts, total_count)
}
#[must_use]
#[inline]
pub fn into_parts(self) -> (IndexedImage<Color, Index>, Vec<u32>) {
let Self { image, counts, .. } = self;
(image, counts)
}
#[allow(clippy::type_complexity)]
#[inline]
pub fn replace_palette<NewColor>(
self,
palette: Vec<NewColor>,
) -> Result<(IndexedImageCounts<NewColor, Index>, Vec<Color>), (Self, Vec<NewColor>)> {
if self.palette().len() == palette.len() {
let IndexedImage {
width,
height,
palette: old_palette,
indices,
..
} = self.image;
Ok((
IndexedImageCounts::new_unchecked(width, height, palette, self.counts, indices),
old_palette,
))
} else {
Err((self, palette))
}
}
#[must_use]
#[inline]
pub fn map<NewColor>(
self,
mapping: impl FnOnce(Vec<Color>) -> Vec<NewColor>,
) -> IndexedImageCounts<NewColor, Index> {
let Self { image, counts } = self;
let image = image.map(mapping);
IndexedImageCounts::from_indexed_image_unchecked(image, counts)
}
#[must_use]
#[inline]
pub fn map_ref<NewColor>(
&self,
mapping: impl FnOnce(&[Color]) -> Vec<NewColor>,
) -> IndexedImageCounts<NewColor, Index> {
let Self { image, counts } = self;
let image = image.map_ref(mapping);
IndexedImageCounts::from_indexed_image_unchecked(image, counts.clone())
}
#[must_use]
#[inline]
pub fn map_to_indexed<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> IndexedImage<ColorMap::Output> {
self.image.map_to_indexed(color_map)
}
#[cfg(feature = "threads")]
#[must_use]
#[inline]
pub fn map_to_indexed_par<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> IndexedImage<ColorMap::Output> {
self.image.map_to_indexed_par(color_map)
}
#[must_use]
#[inline]
pub fn map_to_image<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> ImageBuf<ColorMap::Output> {
self.image.map_to_image(color_map)
}
#[cfg(feature = "threads")]
#[must_use]
#[inline]
pub fn map_to_image_par<ColorMap: IndexedColorMap<Color>>(
&self,
color_map: ColorMap,
) -> ImageBuf<ColorMap::Output> {
self.image.map_to_image_par(color_map)
}
#[must_use]
#[inline]
pub fn to_image(&self) -> ImageBuf<Color>
where
Color: Clone,
{
self.image.to_image()
}
#[cfg(feature = "threads")]
#[must_use]
#[inline]
pub fn to_image_par(&self) -> ImageBuf<Color>
where
Color: Clone + Send + Sync,
{
self.image.to_image_par()
}
}
impl<Color, Index: BoundedIndex> Default for IndexedImageCounts<Color, Index> {
#[inline]
fn default() -> Self {
Self::from_indexed_image_unchecked(IndexedImage::default(), Vec::new())
}
}
impl<Color, Index: BoundedIndex> AsRef<IndexedImage<Color, Index>>
for IndexedImageCounts<Color, Index>
{
#[inline]
fn as_ref(&self) -> &IndexedImage<Color, Index> {
self.as_indexed_image()
}
}