use crate::error::AvengerWgpuError;
use etagere::Size;
use image::DynamicImage;
use wgpu::Extent3d;
pub struct ImageAtlasBuilder {
extent: Extent3d,
next_image: image::RgbaImage,
images: Vec<DynamicImage>,
initialized: bool,
allocator: etagere::AtlasAllocator,
}
#[derive(Copy, Clone)]
pub struct ImageAtlasCoords {
pub x0: f32,
pub y0: f32,
pub x1: f32,
pub y1: f32,
}
impl Default for ImageAtlasBuilder {
fn default() -> Self {
Self::new()
}
}
impl ImageAtlasBuilder {
pub fn new() -> Self {
Self {
extent: Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
next_image: image::RgbaImage::new(1, 1),
images: vec![],
initialized: false,
allocator: etagere::AtlasAllocator::new(etagere::Size::new(1, 1)),
}
}
pub fn register_image(
&mut self,
img: &image::RgbaImage,
) -> Result<(usize, ImageAtlasCoords), AvengerWgpuError> {
if !self.initialized {
let limits = wgpu::Limits::downlevel_webgl2_defaults();
self.extent = Extent3d {
width: limits.max_texture_dimension_1d,
height: limits.max_texture_dimension_2d,
depth_or_array_layers: 1,
};
self.next_image = image::RgbaImage::new(self.extent.width, self.extent.height);
self.allocator = etagere::AtlasAllocator::new(etagere::Size::new(
self.extent.width as i32,
self.extent.height as i32,
));
self.initialized = true;
}
let allocation = match self
.allocator
.allocate(Size::new(img.width() as i32, img.height() as i32))
{
Some(allocation) => allocation,
None => {
let full_image = std::mem::take(&mut self.next_image);
self.next_image = image::RgbaImage::new(self.extent.width, self.extent.height);
self.images
.push(image::DynamicImage::ImageRgba8(full_image));
self.allocator = etagere::AtlasAllocator::new(etagere::Size::new(
self.extent.width as i32,
self.extent.height as i32,
));
match self
.allocator
.allocate(Size::new(img.width() as i32, img.height() as i32))
{
Some(allocation) => allocation,
None => {
if img.width() > self.extent.width || img.height() > self.extent.height {
return Err(AvengerWgpuError::ImageAllocationError(format!(
"Image dimensions ({}, {}) exceed the maximum size of ({}, {})",
img.width(),
img.height(),
self.extent.width,
self.extent.height
)));
} else {
return Err(AvengerWgpuError::ImageAllocationError(
"Unknown error".to_string(),
));
}
}
}
}
};
let p0 = allocation.rectangle.min;
let p1 = allocation.rectangle.max;
let x0 = p0.x;
let x1 = p1.x.min(x0 + img.width() as i32);
let y0 = p0.y;
let y1 = p1.y.min(y0 + img.height() as i32);
for (src_x, dest_x) in (x0..x1).enumerate() {
for (src_y, dest_y) in (y0..y1).enumerate() {
self.next_image.put_pixel(
dest_x as u32,
dest_y as u32,
*img.get_pixel(src_x as u32, src_y as u32),
);
}
}
let coords = ImageAtlasCoords {
x0: x0 as f32 / self.extent.width as f32,
x1: x1 as f32 / self.extent.width as f32,
y0: y0 as f32 / self.extent.height as f32,
y1: y1 as f32 / self.extent.height as f32,
};
let atlas_index = self.images.len();
Ok((atlas_index, coords))
}
pub fn build(&self) -> (Extent3d, Vec<DynamicImage>) {
let mut images = self.images.clone();
images.push(image::DynamicImage::ImageRgba8(self.next_image.clone()));
(self.extent, images)
}
}