putpng 1.0.0

Replacement for the doom modding tools grabpng and setpng
Documentation
use crate::crc::*;
use crate::grab::*;
use image::*;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

struct ImageCropper<'a> {
    image: DynamicImage,
    width: u32,
    height: u32,
    path: &'a str,
}

impl<'a> ImageCropper<'a> {
    fn open(path: &'a str) -> Result<Self> {
        let image = image::open(path)?;
        let (width, height) = image.dimensions();
        Ok(Self {
            image,
            width,
            height,
            path,
        })
    }

    fn is_visible(&self, x: u32, y: u32) -> bool {
        let pixel = self.image.get_pixel(x, y).to_rgba();
        pixel[3] != 0
    }

    fn left_offset(&self) -> u32 {
        for x in 0..self.width {
            if (0..self.height).any(|y| self.is_visible(x, y)) {
                return x;
            }
        }
        0
    }

    fn right_offset(&self) -> u32 {
        for x in (0..self.width).rev() {
            if (0..self.height).any(|y| self.is_visible(x, y)) {
                return x;
            }
        }
        self.width - 1
    }

    fn top_offset(&self) -> u32 {
        for y in 0..self.height {
            if (0..self.width).any(|x| self.is_visible(x, y)) {
                return y;
            }
        }
        0
    }

    fn bottom_offset(&self) -> u32 {
        for y in (0..self.height).rev() {
            if (0..self.width).any(|x| self.is_visible(x, y)) {
                return y;
            }
        }
        self.height - 1
    }

    fn save(&self) -> Result<(i32, i32)> {
        let left = self.left_offset();
        let right = self.right_offset();
        let top = self.top_offset();
        let bottom = self.bottom_offset();
        let image = imageops::crop_imm(&self.image, left, top, right - left + 1, bottom - top + 1)
            .to_image();
        std::fs::remove_file(&self.path)?;
        image.save(&self.path)?;
        Ok((left as i32, top as i32))
    }
}

///Crops all the specified pngs while preserving relative grab offsets
pub fn crop_all(paths: impl Iterator<Item = String>, crc: &Crc32) -> Result<()> {
    for path in paths {
        let new_offset = {
            let grab_offset = read_grab(&path)?.unwrap_or_default();
            let crop_offset = ImageCropper::open(&path)?.save()?;
            (grab_offset.0 - crop_offset.0, grab_offset.1 - crop_offset.1)
        };
        push_grab(&path, crc, new_offset.0, new_offset.1)?;
        println!("Cropped '{path}' successfully!");
    }
    Ok(())
}