summaryrefslogtreecommitdiff
path: root/src/texture.rs
blob: 918430486c22c075c6caeff59f9c4103af1ff734 (plain)
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<dyn Error>),
}

impl From<ImageError> 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<TextureId, TextureError> {
		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<TextureId>> {
		self.packer
			.get_pages()
			.iter()
			.map(|a| a.get_frame(&id))
			.next()?
	}

	pub fn texture_width(&self, id: TextureId) -> Option<u32> {
		let frame = self.texture_frame(id)?;
		Some(frame.frame.w)
	}

	pub fn texture_height(&self, id: TextureId) -> Option<u32> {
		let frame = self.texture_frame(id)?;
		Some(frame.frame.h)
	}

	pub fn texture_x(&self, id: TextureId) -> Option<u32> {
		let frame = self.texture_frame(id)?;
		Some(frame.frame.x)
	}

	pub fn texture_y(&self, id: TextureId) -> Option<u32> {
		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<Box<[DynamicImage]>> {
		self.packer
			.get_pages()
			.iter()
			.map(ImageExporter::export)
			.collect::<ExportResult<Vec<DynamicImage>>>()
			.map(Vec::into_boxed_slice)
	}
}