sheetsmithlib 0.1.3

The goto library for sprite sheet packing
Documentation
use anyhow::{Result, bail};
use image::RgbaImage;

pub struct GuillotiereArgs {
    pub size: (u32, u32),
    pub padding: i32,
    pub image_files: Vec<(String, RgbaImage)>,
}

pub fn pack_images_guillotiere(args: GuillotiereArgs) -> Result<RgbaImage> {
    let canvas_width = args.size.0 as i64;
    let pad = args.padding as i64;

    let mut placements: Vec<(String, i64, i64)> = Vec::new();
    let mut cursor_x: i64 = 0;
    let mut cursor_y: i64 = 0;
    let mut row_height: i64 = 0;

    for (filename, image) in &args.image_files {
        let w = image.width() as i64 + pad * 2;
        let h = image.height() as i64 + pad * 2;

        if cursor_x + w > canvas_width {
            if cursor_x == 0 {
                bail!(
                    "Image '{}' ({}x{}) is too wide to fit in the canvas (width={}, padding={}).",
                    filename,
                    image.width(),
                    image.height(),
                    args.size.0,
                    args.padding
                );
            }
            cursor_x = 0;
            cursor_y += row_height;
            row_height = 0;
        }

        placements.push((filename.clone(), cursor_x, cursor_y));
        cursor_x += w;
        row_height = row_height.max(h);
    }

    let total_height = (cursor_y + row_height) as u32;
    if total_height > args.size.1 {
        bail!(
            "Images overflow the canvas height ({} > {}). Try increasing max_size or reducing padding.",
            total_height,
            args.size.1
        );
    }

    let mut output_image = RgbaImage::new(args.size.0, total_height);

    for (filename, x, y) in &placements {
        let image = args
            .image_files
            .iter()
            .find(|(name, _)| name == filename)
            .unwrap()
            .1
            .clone();

        image::imageops::overlay(&mut output_image, &image, x + pad, y + pad);
    }

    Ok(output_image)
}