use anyhow::{anyhow, Result};
use bevy::render::render_resource::TextureFormat;
use serde::{Deserialize, Serialize};
use crate::{math::RectExt, palette::Palette, pixel::Pixel, prelude::*};
#[derive(Serialize, Deserialize, Clone, Reflect, Debug)]
pub(crate) struct PxImage<P: Pixel> {
image: Vec<P>,
width: usize,
}
impl<P: Pixel> PxImage<P> {
pub(crate) fn new(image: Vec<P>, width: usize) -> Self {
Self { image, width }
}
pub(crate) fn empty(size: UVec2) -> Self {
Self {
image: vec![default(); (size.x * size.y) as usize],
width: size.x as usize,
}
}
pub(crate) fn empty_from_image(image: &Image) -> Self {
Self::empty(image.size())
}
pub(crate) fn pixel(&self, position: IVec2) -> P {
self.image[(position.x + position.y * self.width as i32) as usize]
}
pub(crate) fn get_pixel(&self, position: IVec2) -> Option<P> {
IRect {
min: IVec2::splat(0),
max: IVec2::new(self.width as i32, (self.image.len() / self.width) as i32),
}
.contains_exclusive(position)
.then(|| self.pixel(position))
}
#[allow(dead_code)]
pub(crate) fn size(&self) -> UVec2 {
UVec2::new(self.width as u32, (self.image.len() / self.width) as u32)
}
pub(crate) fn width(&self) -> usize {
self.width
}
pub(crate) fn height(&self) -> usize {
self.image.len() / self.width
}
pub(crate) fn area(&self) -> usize {
self.image.len()
}
#[allow(unused)]
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut P> {
self.image.iter_mut()
}
pub(crate) fn slice_mut(&mut self, slice: IRect) -> PxImageSliceMut<P> {
PxImageSliceMut {
slice,
image: self.image.chunks_exact_mut(self.width).collect(),
width: self.width,
}
}
pub(crate) fn slice_all_mut(&mut self) -> PxImageSliceMut<P> {
PxImageSliceMut {
slice: IRect {
min: IVec2::splat(0),
max: IVec2::new(self.width as i32, (self.image.len() / self.width) as i32),
},
image: self.image.chunks_exact_mut(self.width).collect(),
width: self.width,
}
}
pub(crate) fn split_vert(self, chunk_height: usize) -> Vec<Self> {
self.image
.chunks_exact(chunk_height * self.width)
.map(|chunk| Self {
image: chunk.into(),
width: self.width,
})
.collect()
}
pub(crate) fn split_horz(self, chunk_width: usize) -> Vec<Self> {
let chunk_count = self.width / chunk_width;
let mut images = vec![Vec::with_capacity(self.area() / chunk_width); chunk_count];
for (i, chunk_row) in self.image.chunks_exact(chunk_width).enumerate() {
images[i % chunk_count].push(chunk_row);
}
images
.into_iter()
.map(|image| Self {
image: image.into_iter().flatten().copied().collect(),
width: chunk_width,
})
.collect()
}
pub(crate) fn from_parts_vert(parts: impl IntoIterator<Item = Self>) -> Option<Self> {
let (images, widths): (Vec<_>, Vec<_>) = parts
.into_iter()
.map(|image| (image.image, image.width))
.unzip();
match (&widths) as &[_] {
[width, other_widths @ ..] => other_widths
.iter()
.all(|other_width| other_width == width)
.then(|| Self {
image: images.into_iter().flatten().collect(),
width: *width,
}),
[] => None,
}
}
pub(crate) fn clear(&mut self) {
self.image.fill(default());
}
}
impl PxImage<Option<u8>> {
pub(crate) fn palette_indices(palette: &Palette, image: &Image) -> Result<Self> {
Ok(Self {
image: image
.convert(TextureFormat::Rgba8UnormSrgb)
.ok_or_else(|| anyhow!("could not convert image to `Rgba8UnormSrgb`"))?
.data
.chunks_exact(4)
.map(|color| {
(color[3] != 0)
.then(|| {
palette
.indices
.get(&[color[0], color[1], color[2]])
.copied()
.ok_or_else(|| {
anyhow!(
"a sprite contained a color `#{:02X}{:02X}{:02X}` \
that wasn't in the palette",
color[0],
color[1],
color[2]
)
})
})
.transpose()
})
.collect::<Result<_>>()?,
width: image.texture_descriptor.size.width as usize,
})
}
pub(crate) fn trim_right(&mut self) {
while (0..self.height()).all(|row| self.image[self.width * (row + 1) - 1].is_none()) {
for row in (0..self.height()).rev() {
self.image.remove(row * self.width + self.width - 1);
}
self.width -= 1;
}
}
}
pub(crate) struct PxImageSliceMut<'a, P: Pixel> {
image: Vec<&'a mut [P]>,
width: usize,
slice: IRect,
}
impl<P: Pixel> PxImageSliceMut<'_, P> {
pub(crate) fn for_each_mut(&mut self, f: impl Fn(usize, usize, &mut P)) {
let row_min = self.slice.min.x.clamp(0, self.width as i32) as usize;
let row_max = self.slice.max.x.clamp(0, self.width as i32) as usize;
let max_y = self.image.len() as i32;
self.image.iter_mut().enumerate().collect::<Vec<_>>()
[self.slice.min.y.clamp(0, max_y) as usize..self.slice.max.y.clamp(0, max_y) as usize]
.iter_mut()
.for_each(|(i, row)| {
row.iter_mut().enumerate().collect::<Vec<_>>()[row_min..row_max]
.iter_mut()
.for_each(|(j, pixel)| {
f(
((*i as i32 - self.slice.min.y) * (self.slice.max.x - self.slice.min.x)
+ (*j as i32 - self.slice.min.x))
as usize,
*i * self.width + *j,
pixel,
);
});
});
}
pub(crate) fn pixel_mut(&mut self, position: IVec2) -> &mut P {
&mut self.image[(self.slice.min.y + position.y) as usize]
[(self.slice.min.x + position.x) as usize]
}
pub(crate) fn get_pixel_mut(&mut self, position: IVec2) -> Option<&mut P> {
(IRect {
min: IVec2::splat(0),
max: IVec2::new(self.width as i32, self.image.len() as i32),
}
.contains_exclusive(position + self.slice.min)
&& self.slice.contains_exclusive(position))
.then(|| self.pixel_mut(position))
}
pub(crate) fn width(&self) -> u32 {
self.slice.size().x as u32
}
pub(crate) fn height(&self) -> u32 {
self.slice.size().y as u32
}
pub(crate) fn image_width(&self) -> usize {
self.width
}
pub(crate) fn draw(&mut self, image: &PxImage<impl Pixel>) {
self.for_each_mut(|i, _, pixel| {
pixel.set(image.image[i]);
});
}
}
impl<'a> PxImageSliceMut<'a, u8> {
pub(crate) fn from_image_mut(image: &'a mut Image) -> Self {
Self {
slice: IRect {
min: IVec2::splat(0),
max: IVec2::new(
image.texture_descriptor.size.width as i32,
image.texture_descriptor.size.height as i32,
),
},
image: image
.data
.chunks_exact_mut(image.texture_descriptor.size.width as usize)
.collect(),
width: image.texture_descriptor.size.width as usize,
}
}
}