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