summaryrefslogtreecommitdiff
path: root/src/texture.rs
blob: aa6f871ba537dac353469568fd055e0dcabbc872 (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)]
struct TextureId(usize);

impl TextureId {
	pub fn new() -> Self {
		Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed))
	}
}

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)),
		}
	}
}

struct TextureAtlases<'a> {
	packer: MultiTexturePacker<'a, image::RgbaImage, TextureId>,
}

impl<'a> Default for TextureAtlases<'a> {
	fn default() -> Self {
		Self {
			packer: MultiTexturePacker::new_skyline(TexturePackerConfig::default()),
		}
	}
}

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,
				..Default::default()
			}),
		}
	}

	// TODO specify format
	// TODO support RGBA16
	pub fn load_from_memory(&mut self, buf: &[u8]) -> Result<TextureId, TextureError> {
		let img = image::load_from_memory(buf)?.into_rgba8();
		let id = TextureId::new();
		self.packer
			.pack_own(id, img)
			.map_err(TextureError::TextureTooLarge)?;

		Ok(id)
	}

	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)
	}
}