summaryrefslogtreecommitdiff
path: root/alligator_resources/src/texture.rs
blob: ba0ff478b256192528a5d7c71ae58f02b3cb7d47 (plain)
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<image::Rgba<u16>, 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<ImageFormat> 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<DecodingError>;

pub struct TextureManager {
	textures: HashMap<TextureId, Rgba16Texture>,
}

#[allow(clippy::missing_const_for_fn)]
fn convert_image_decoding(e: image::ImageError) -> Expect<DecodingError> {
	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<TextureId, LoadError> {
		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)
	}
}