mod atlasset; use atlasset::AtlasSet; use anyhow::{bail, Context, Result}; use image::io::Reader; use std::path::{Path, PathBuf}; use walkdir::WalkDir; // TODO: warning when images have extra transparency // TODO: dynamic packing (for plugins) // TODO: standalone cli (galactica should ask to run this) // TODO: randomly assign sprites to textures, for efficiency // TODO: group images by use case fn main() -> Result<()> { let mut files = Vec::new(); let asset_root = Path::new("./assets/render"); // Total number of pixels we want to add let mut total_dim = 0f64; for e in WalkDir::new(&asset_root).into_iter().filter_map(|e| e.ok()) { if e.metadata().unwrap().is_file() { // TODO: better warnings match e.path().extension() { Some(t) => { if t.to_str() != Some("png") && t.to_str() != Some("jpg") { println!("[WARNING] {e:#?} is not a png file, skipping."); continue; } } None => { println!("[WARNING] {e:#?} is not a png file, skipping."); continue; } } let path = e.path().to_path_buf(); let reader = Reader::open(&path) .with_context(|| format!("While reading file `{}`", path.display()))?; let dim = reader.into_dimensions().with_context(|| { format!("While reading dimensions of file `{}`", path.display()) })?; files.push((path, [dim.0, dim.1])); total_dim += dim.0 as f64 * dim.1 as f64; } } // Sort by image size files.sort_by(|a, b| { let a = a.1[0] * a.1[1]; let b = b.1[0] * b.1[1]; b.cmp(&a) }); // Make sure we have enough pixels. // This check is conservative and imperfect: // Our tiling algorithm usually has efficiency better than 80% (~90%, as of writing) // We need room for error, though, since this check doesn't guarante success. if total_dim / 0.80 >= (8192.0 * 8192.0 * 16.0) { bail!("Texture atlas is too small") } // Create atlas set let mut atlas_set = AtlasSet::new(8192, 8192, 16, &asset_root, 1); let total = files.len(); let mut i = 0; let mut peak_efficiency = 0f64; for (path, _) in files { i += 1; let atlas_idx = atlas_set.write_image(&path)?; println!( "({i} of {total}, atlas {atlas_idx}, efficiency {:.02}%) Added {}", 100.0 * atlas_set.get_efficiency(), path.display() ); peak_efficiency = peak_efficiency.max(atlas_set.get_efficiency()); } println!( "Packing efficiency: {:.02}%", 100.0 * atlas_set.get_efficiency() ); println!("Peak efficiency: {:.02}%", 100.0 * peak_efficiency); println!("Saving files..."); atlas_set.save_files( |x| PathBuf::from(format!("cache/atlas-{x:0.2}.bmp")), &PathBuf::from("cache/spriteatlas.toml"), )?; return Ok(()); }