use std::error::Error; use std::sync::atomic::{AtomicUsize, Ordering}; use image::error::DecodingError; use image::{DynamicImage, ImageError}; use texture_packer::{ exporter::{ExportResult, ImageExporter}, MultiTexturePacker, TexturePackerConfig, }; use thiserror::Error; static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0); #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct TextureId(usize); impl TextureId { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed)) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ImageFormat { Bmp, Ico, Farbfeld, } impl ImageFormat { const fn format(self) -> image::ImageFormat { match self { Self::Bmp => image::ImageFormat::Bmp, Self::Ico => image::ImageFormat::Ico, Self::Farbfeld => image::ImageFormat::Farbfeld, } } } type PackError = impl std::fmt::Debug; #[derive(Error, Debug)] pub enum TextureError { #[error("{:?}", .0)] TextureTooLarge(PackError), // use an error with a source #[error("{}", .0)] BadImage(#[source] DecodingError), // TODO don't export this #[error("Unexpected Error (this is a bug in alligator_render): {}", .0)] Unexpected(#[source] Box), } impl From for TextureError { fn from(ie: ImageError) -> Self { match ie { ImageError::Decoding(de) => Self::BadImage(de), _ => Self::Unexpected(Box::new(ie)), } } } // TODO make this Debug pub struct TextureAtlases<'a> { packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>, width: u32, height: u32, } impl<'a> Default for TextureAtlases<'a> { fn default() -> Self { Self::new(1024, 1024) } } impl<'a> TextureAtlases<'a> { /// Creates a new texture atlas, with the given size // TODO why is this u32? pub fn new(width: u32, height: u32) -> Self { Self { packer: MultiTexturePacker::new_skyline(TexturePackerConfig { max_width: width, max_height: height, //trim: false, ..Default::default() }), width, height, } } // TODO support RGBA16 pub fn load_from_memory( &mut self, buf: &[u8], format: ImageFormat, ) -> Result { let img = image::load_from_memory_with_format(buf, format.format())?.into_rgba8(); let id = TextureId::new(); self.packer .pack_own(id, img) .map_err(TextureError::TextureTooLarge)?; Ok(id) } fn texture_frame(&self, id: TextureId) -> Option<&texture_packer::Frame> { self.packer .get_pages() .iter() .map(|a| a.get_frame(&id)) .next()? } pub fn texture_width(&self, id: TextureId) -> Option { let frame = self.texture_frame(id)?; Some(frame.frame.w) } pub fn texture_height(&self, id: TextureId) -> Option { let frame = self.texture_frame(id)?; Some(frame.frame.h) } pub fn texture_x(&self, id: TextureId) -> Option { let frame = self.texture_frame(id)?; Some(frame.frame.x) } pub fn texture_y(&self, id: TextureId) -> Option { let frame = self.texture_frame(id)?; Some(frame.frame.y) } pub(crate) const fn bytes_per_row(&self) -> u32 { self.width * 4 } pub(crate) const fn height(&self) -> u32 { self.height } pub(crate) const fn extent_3d(&self) -> wgpu::Extent3d { wgpu::Extent3d { width: self.width, height: self.height, depth_or_array_layers: 1, } } pub(crate) fn atlases(&self) -> ExportResult> { self.packer .get_pages() .iter() .map(ImageExporter::export) .collect::>>() .map(Vec::into_boxed_slice) } }