use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use exun::{Expect, Expected, Unexpected}; use thiserror::Error; static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0); type Rgba16Texture = image::ImageBuffer, Box<[u16]>>; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct TextureId(usize); impl TextureId { fn new() -> Self { Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed)) } } /// These are the formats supported by the renderer. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ImageFormat { Bmp, Ico, Farbfeld, } impl From for image::ImageFormat { fn from(format: ImageFormat) -> Self { match format { ImageFormat::Bmp => Self::Bmp, ImageFormat::Ico => Self::Ico, ImageFormat::Farbfeld => Self::Farbfeld, } } } #[derive(Debug, Error)] #[error("{}", .0)] pub struct DecodingError(#[from] image::error::DecodingError); type LoadError = Expect; pub struct TextureManager { textures: HashMap, } #[allow(clippy::missing_const_for_fn)] fn convert_image_decoding(e: image::ImageError) -> Expect { if let image::ImageError::Decoding(de) = e { Expected(de.into()) } else { Unexpected(e.into()) } } impl TextureManager { #[must_use] pub fn new() -> Self { Self { textures: HashMap::new(), } } /// Loads a texture from memory in the given format. /// /// # Errors /// /// This returns `Expected(DecodingError)` if the given buffer was invalid /// for the given format. #[allow(clippy::missing_panics_doc)] pub fn load_from_memory( &mut self, buf: &[u8], format: ImageFormat, ) -> Result { let id = TextureId::new(); let texture = image::load_from_memory_with_format(buf, format.into()) .map_err(convert_image_decoding)?; let texture = texture.into_rgb16(); let width = texture.width(); let height = texture.height(); let buf = texture.into_raw().into_boxed_slice(); let texture = image::ImageBuffer::from_raw(width, height, buf).unwrap(); self.textures.insert(id, texture); Ok(id) } }