tileset 0.1.3

Tailset definition, and supports importing multiple formats
Documentation
#![doc = include_str!("readme.md")]
use crate::{grids::rpg_maker_xp::GridCornerRMXP, GridAtlas, GridCompleteAtlas, GridCornerRMVX, GridEdgeWang};
use image::{
    error::{ParameterError, ParameterErrorKind},
    ColorType, GenericImageView, ImageError, ImageFormat, ImageResult, RgbaImage,
};
use std::{
    collections::BTreeMap,
    fmt::{Display, Formatter},
    io::{Error, ErrorKind},
    path::{Path, PathBuf},
};

/// Decompose image grids by cells count.
pub fn decompose_image_grid_by_cells<P>(path: P, cols: u32, rows: u32) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    let path = path.as_ref().canonicalize()?;
    let dir = path.parent().expect("The path must have a parent directory");
    let name = path.file_stem().expect("The path must have a file name");
    let image = image::open(&path)?;
    let (width, height) = image.dimensions();
    let cell_width = width / cols;
    let cell_height = height / rows;
    for row in 0..rows {
        for col in 0..cols {
            let view = image.view(col * cell_width, row * cell_height, cell_width, cell_height);
            let out = dir.join(format!("{}-{}-{}.png", name.to_str().unwrap(), col, row));
            view.to_image().save(&out)?;
        }
    }
    Ok(())
}

/// Create a new tile set from rpg maker xp atlas.
///
/// ## Example
///
/// ```no_run
/// # use tileset::{GridAtlas, GridCompleteAtlas};
/// let raw: GridCompleteAtlas = GridAtlas::load("assets/grass-xp.png").unwrap();
/// let size = raw.get_cell_size();
/// ```
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct AnimationSlice {}

/// Create a new animation slice from image.
pub fn grid_corner_mask(lu: bool, ru: bool, ld: bool, rd: bool) -> u8 {
    (lu as u8) << 0 | (ru as u8) << 1 | (ld as u8) << 2 | (rd as u8) << 3
}

/// Create a new tile set from rpg maker xp atlas.
///
/// ## Example
///
/// ```no_run
/// # use tileset::{GridAtlas, GridCompleteAtlas};
/// let raw: GridCompleteAtlas = GridAtlas::load("assets/grass-xp.png").unwrap();
/// let size = raw.get_cell_size();
/// ```
#[derive(Debug)]
pub struct MaskBuilder {
    map: BTreeMap<u8, (u32, u32)>,
    defaults: (u32, u32),
}
impl Display for MaskBuilder {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "match mask {{")?;
        for (mask, (x, y)) in self.map.iter() {
            writeln!(f, "    0b{:08b} => ({}, {}),", mask, x.saturating_sub(1), y.saturating_sub(1))?;
        }
        writeln!(f, "    _ => ({}, {}),", self.defaults.0.saturating_sub(1), self.defaults.1.saturating_sub(1))?;
        writeln!(f, "}}")?;
        Ok(())
    }
}

impl MaskBuilder {
    /// Create a new mask set
    pub fn new(x: u32, y: u32) -> MaskBuilder {
        Self { map: BTreeMap::default(), defaults: (x, y) }
    }
    /// Get all of the masks
    pub fn masks(&self) -> Vec<u8> {
        self.map.keys().copied().collect()
    }
    /// Set mask by bits
    pub fn has_bits(&mut self, (x, y): (u32, u32), mask: &[u32]) {
        let s: u32 = mask.iter().map(|i| 2u32.pow(*i)).sum();
        self.has_mask((x, y), s as u8);
    }
    /// Set mask by byte
    pub fn has_mask(&mut self, (x, y): (u32, u32), mask: u8) {
        let pop = self.map.insert(mask, (x, y));
        if let Some((i, j)) = pop {
            panic!("duplicate mask {}: new {:?}, old {:?}", mask, (x, y), (i, j))
        }
    }
    /// The masks of complete set
    pub fn complete_set() -> Self {
        let mut masks = MaskBuilder::new(1, 4);
        // part1
        masks.has_bits((1, 1), &[4]);
        masks.has_bits((2, 1), &[2, 4]);
        masks.has_bits((3, 1), &[2, 4, 6]);
        masks.has_bits((4, 1), &[4, 6]);
        masks.has_bits((1, 2), &[0, 4]);
        masks.has_bits((2, 2), &[0, 2, 4]);
        masks.has_bits((3, 2), &[0, 2, 4, 6]);
        masks.has_bits((4, 2), &[0, 4, 6]);
        masks.has_bits((1, 3), &[0]);
        masks.has_bits((2, 3), &[0, 2]);
        masks.has_bits((3, 3), &[0, 2, 6]);
        masks.has_bits((4, 3), &[0, 6]);
        // <default>
        masks.has_bits((2, 4), &[2]);
        masks.has_bits((3, 4), &[2, 6]);
        masks.has_bits((4, 4), &[6]);
        // part2, &[0, 1, 2, 3, 4, 5, 6, 7]
        masks.has_bits((5, 1), &[0, 2, 4, 6, 7]);
        masks.has_bits((6, 1), &[2, 3, 4, 6]);
        masks.has_bits((7, 1), &[2, 4, 5, 6]);
        masks.has_bits((8, 1), &[0, 1, 2, 4, 6]);
        masks.has_bits((5, 2), &[0, 2, 3, 4]);
        masks.has_bits((6, 2), &[0, 1, 2, 3, 4, 5, 6]);
        masks.has_bits((7, 2), &[0, 2, 3, 4, 5, 6, 7]);
        masks.has_bits((8, 2), &[0, 4, 5, 6]);
        masks.has_bits((5, 3), &[0, 1, 2, 4]);
        masks.has_bits((6, 3), &[0, 1, 2, 3, 4, 6, 7]);
        masks.has_bits((7, 3), &[0, 1, 2, 4, 5, 6, 7]);
        masks.has_bits((8, 3), &[0, 4, 6, 7]);
        masks.has_bits((5, 4), &[0, 2, 4, 5, 6]);
        masks.has_bits((6, 4), &[0, 1, 2, 6]);
        masks.has_bits((7, 4), &[0, 2, 6, 7]);
        masks.has_bits((8, 4), &[0, 2, 3, 4, 6]);
        // part3
        masks.has_bits((9, 1), &[2, 3, 4]);
        masks.has_bits((10, 1), &[0, 2, 3, 4, 5, 6]);
        masks.has_bits((11, 1), &[2, 3, 4, 5, 6]);
        masks.has_bits((12, 1), &[4, 5, 6]);
        masks.has_bits((9, 2), &[0, 1, 2, 3, 4]);
        masks.has_bits((10, 2), &[0, 1, 2, 4, 5, 6]);
        masks.has_bits((11, 2), &[]);
        masks.has_bits((12, 2), &[0, 2, 4, 5, 6, 7]);
        masks.has_bits((9, 3), &[0, 1, 2, 3, 4, 6]);
        masks.has_bits((10, 3), &[0, 1, 2, 3, 4, 5, 6, 7]);
        masks.has_bits((11, 3), &[0, 2, 3, 4, 6, 7]);
        masks.has_bits((12, 3), &[0, 4, 5, 6, 7]);
        masks.has_bits((9, 4), &[0, 1, 2]);
        masks.has_bits((10, 4), &[0, 1, 2, 6, 7]);
        masks.has_bits((11, 4), &[0, 1, 2, 4, 6, 7]);
        masks.has_bits((12, 4), &[0, 6, 7]);

        masks
    }
    /// The masks of blob set type A
    pub fn blob7x7_set() -> Self {
        let mut masks = MaskBuilder::new(1, 1);
        // part1
        // <default>
        masks.has_mask((2, 1), 4);
        masks.has_mask((3, 1), 92);
        masks.has_mask((4, 1), 124);
        masks.has_mask((5, 1), 116);
        masks.has_mask((6, 1), 80);
        // <excess>
        masks.has_mask((1, 2), 16);
        masks.has_mask((2, 2), 20);
        masks.has_mask((3, 2), 87);
        masks.has_mask((4, 2), 223);
        masks.has_mask((5, 2), 241);
        masks.has_mask((6, 2), 21);
        masks.has_mask((7, 2), 64);

        masks.has_mask((1, 3), 29);
        masks.has_mask((2, 3), 117);
        masks.has_mask((3, 3), 85);
        masks.has_mask((4, 3), 71);
        masks.has_mask((5, 3), 221);
        masks.has_mask((6, 3), 125);
        masks.has_mask((7, 3), 112);

        masks.has_mask((1, 4), 31);
        masks.has_mask((2, 4), 253);
        masks.has_mask((3, 4), 113);
        masks.has_mask((4, 4), 28);
        masks.has_mask((5, 4), 127);
        masks.has_mask((6, 4), 247);
        masks.has_mask((7, 4), 209);

        masks.has_mask((1, 5), 23);
        masks.has_mask((2, 5), 199);
        masks.has_mask((3, 5), 213);
        masks.has_mask((4, 5), 95);
        masks.has_mask((5, 5), 255);
        masks.has_mask((6, 5), 245);
        masks.has_mask((7, 5), 81);

        masks.has_mask((1, 6), 5);
        masks.has_mask((2, 6), 84);
        masks.has_mask((3, 6), 93);
        masks.has_mask((4, 6), 119);
        masks.has_mask((5, 6), 215);
        masks.has_mask((6, 6), 193);
        masks.has_mask((7, 6), 17);

        masks.has_mask((1, 7), 0);
        masks.has_mask((2, 7), 1);
        masks.has_mask((3, 7), 7);
        masks.has_mask((4, 7), 197);
        masks.has_mask((5, 7), 69);
        masks.has_mask((6, 7), 68);
        masks.has_mask((7, 7), 65);

        masks
    }
}

/// Convert a 7x7 blob type A tile set to complete set atlas
pub fn convert_blob7x7a<P>(image: P) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    let path = image.as_ref().canonicalize()?;
    let new_name = path.file_stem().and_then(|s| s.to_str()).map(|s| format!("{}-std.png", s)).unwrap();
    let raw = image::open(image.as_ref())?.to_rgba8();
    let new = GridCompleteAtlas::from_blob7x7a(&raw, raw.width() / 7, raw.height() / 7);
    new.save(path.with_file_name(new_name))
}

/// Convert a 4x4 corner tile set to complete set atlas
pub fn convert_edge4x4<P>(image: P) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    let (raw, output) = image_with_new_path(image)?;
    let new = GridEdgeWang::create(&raw, (0, 0), (raw.width() / 4, raw.height() / 4))?;
    new.as_complete().save(output)
}

/// Convert a 4x6 rpg tile set to complete set atlas
pub fn convert_rpg4x6<P>(image: P) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    let (raw, output) = image_with_new_path(image)?;
    let rpg = GridCornerRMVX::create(&raw, (0, 0), (raw.width() / 4, raw.height() / 6))?;
    rpg.as_complete().save(output)
}

/// Convert a 6x8 rpg tile set to complete set atlas
pub fn convert_rpg6x8<P>(image: P) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    let (raw, output) = image_with_new_path(image)?;
    let rpg = GridCornerRMXP::create(&raw, (0, 0), (raw.width() / 6, raw.height() / 8))?;
    rpg.as_complete().save(output)
}

fn image_with_new_path<P>(image: P) -> ImageResult<(RgbaImage, PathBuf)>
where
    P: AsRef<Path>,
{
    let path = image.as_ref().canonicalize()?;
    let raw = image::open(image.as_ref())?.to_rgba8();
    let new_name = path.file_stem().and_then(|s| s.to_str()).map(|s| format!("{}-std.png", s)).unwrap();
    let new_path = path.with_file_name(new_name);
    Ok((raw, new_path))
}

/// force save image as png
pub(crate) fn save_as_png<P>(image: &RgbaImage, path: P) -> ImageResult<()>
where
    P: AsRef<Path>,
{
    image::save_buffer_with_format(path, &image, image.width(), image.height(), ColorType::Rgba8, ImageFormat::Png)
}

pub(crate) fn parameter_error<T, S: ToString>(message: S) -> ImageResult<T> {
    Err(ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::Generic(message.to_string()))))
}

pub(crate) fn io_error<T, S>(message: S, kind: ErrorKind) -> ImageResult<T>
where
    S: ToString,
{
    Err(ImageError::IoError(Error::new(kind, message.to_string())))
}

pub(crate) fn check_image_multiple(image: &RgbaImage, width: u32, height: u32) -> ImageResult<()> {
    let (w, h) = image.dimensions();
    if w % width != 0 {
        parameter_error(format!("Image width {} is not a multiple of {}", w, width))?
    }
    if h % height != 0 {
        parameter_error(format!("Image height {} is not a multiple of {}", h, height))?
    }
    Ok(())
}