use image::{GenericImage, RgbaImage};
use once_cell::sync::Lazy;
use rayon::prelude::*;
use std::collections::HashSet;
use std::path::Path;
use walkdir::WalkDir;
static IMAGE_EXTENSIONS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
let exts = ["png", "jpg", "jpeg", "gif", "bmp", "ico", "tiff", "webp"];
exts.iter().cloned().collect()
});
pub struct Spriterator {
dir_path: String,
max_width: u32,
max_height: u32,
}
impl Spriterator {
pub fn new(dir_path: &str, max_width: u32, max_height: u32) -> Self {
Self {
dir_path: dir_path.to_string(),
max_width,
max_height,
}
}
pub fn generate(&self) -> Result<Vec<RgbaImage>, Box<dyn std::error::Error>> {
let images: Vec<RgbaImage> = WalkDir::new(&self.dir_path)
.into_iter()
.par_bridge()
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path().is_file() && is_image(entry.path()))
.filter_map(|entry| image::open(entry.path()).ok().map(|img| img.to_rgba8()))
.collect();
if images.is_empty() {
return Err("No images found in the specified directory.".into());
}
let mut sprites = Vec::new();
let mut current_sprite = RgbaImage::new(self.max_width, self.max_height);
let mut current_x = 0;
let mut current_y = 0;
let mut row_height = 0;
for img in &images {
if current_x + img.width() > self.max_width {
current_y += row_height;
current_x = 0;
row_height = 0;
}
if current_y + img.height() > self.max_height {
sprites.push(current_sprite);
current_sprite = RgbaImage::new(self.max_width, self.max_height);
current_y = 0;
}
current_sprite.copy_from(img, current_x, current_y)?;
row_height = row_height.max(img.height());
current_x += img.width();
}
sprites.push(current_sprite);
Ok(sprites)
}
}
fn is_image(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| IMAGE_EXTENSIONS.contains(ext.to_ascii_lowercase().as_str()))
.unwrap_or(false)
}