use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use crate::commands::{ calculate_mapping, execute_pipeline, extrude, flip, palette, remap_image, rescale, rotate, write_palette, FlipDirection, RotateDegree, }; use crate::format::PaletteFormat; use crate::{load_image, Format}; /// Image utilities for Advent #[derive(Parser, Debug, Clone)] #[clap(name = "Crunch")] #[clap(author = "Louis Capitanchik <louis@microhacks.co.uk>")] #[clap(version = "0.4.0")] #[clap(about, long_about = None)] pub struct Args { /// The path to the spritesheet file in_path: String, /// The path to the output file out_path: String, /// Force Crunch to read the input file as a specific format #[clap(short, long, arg_enum)] format: Option<Format>, #[clap(subcommand)] command: CrunchCommand, } #[inline] pub fn u32_zero() -> u32 { 0 } #[inline] pub fn u32_32() -> u32 { 32 } #[inline] pub fn f32_one() -> f32 { 1.0 } #[derive(Debug, Clone, Subcommand, Serialize, Deserialize)] #[serde(tag = "command", content = "params")] pub enum CrunchCommand { /// Take each tile in an image and expand its borders by a given amount #[clap(version = "0.3.0")] Extrude { /// The amount of horizontal padding to add between each sprite in the image #[clap(long, default_value_t = 0)] #[serde(default = "u32_zero")] space_x: u32, /// The amount of vertical padding to add between each sprite in the image #[clap(long, default_value_t = 0)] #[serde(default = "u32_zero")] space_y: u32, /// The amount of horizontal padding to add between each sprite in the image #[clap(long, default_value_t = 0)] #[serde(default = "u32_zero")] pad_x: u32, /// The amount of vertical padding to add between each sprite in the image #[clap(long, default_value_t = 0)] #[serde(default = "u32_zero")] pad_y: u32, /// The size of each tile in the spritesheet. Assumed to be square tiles #[clap(short, long, default_value_t = 32)] #[serde(default = "u32_32")] tile_size: u32, /// Use nearby pixels for padding #[clap(short, long)] #[serde(default)] extrude: bool, }, /// Create a palette file containing every distinct colour from the input image #[clap(version = "0.3.0")] Palette { #[clap(short, long, arg_enum, default_value_t)] #[serde(default)] format: PaletteFormat, }, /// Convert the colour space of an input image to the given palette #[clap(version = "0.3.0")] Remap { /// The path to the palette file containing the output colours palette_file: String, }, /// Make an image larger or smaller #[clap(version = "0.3.0")] Scale { /// The scale factor to use; numbers between 0-1 shrink the image; numbers > 1 enlarge #[clap(long, default_value_t = 1.0)] #[serde(default = "f32_one")] factor: f32, }, /// Apply a clockwise rotation to the image #[clap(version = "0.3.0")] Rotate { /// How many 90 degree steps should this image be rotated by #[clap(long, arg_enum)] amount: RotateDegree, }, /// Flip an image along one or both axis #[clap(version = "0.3.0")] Flip { /// The axis along which the image should be flipped #[clap(long, arg_enum)] direction: FlipDirection, }, /// Execute a pipeline file to run multiple commands on one or more images #[clap(version = "0.3.0")] Pipeline, } impl Args { pub fn run(&self) -> anyhow::Result<()> { match &self.command { CrunchCommand::Extrude { extrude: ext, pad_x, pad_y, space_x, space_y, tile_size, } => { let image = load_image(&self.in_path, self.format)?; let output = extrude(image, *tile_size, *pad_x, *pad_y, *space_x, *space_y, *ext)?; output.save(&self.out_path).map_err(anyhow::Error::from) } CrunchCommand::Palette { format } => { let image = load_image(&self.in_path, self.format)?; let output = palette(&image)?; write_palette(output, *format, &self.out_path) } CrunchCommand::Remap { palette_file } => { let image_data = load_image(&self.in_path, self.format)?; let palette_data = load_image(&palette_file, self.format)?; let image_palette = palette(&image_data)?; let target_palette = palette(&palette_data)?; let mappings = calculate_mapping(&image_palette, &target_palette); let output = remap_image(image_data, mappings)?; output.save(&self.out_path).map_err(anyhow::Error::from) } CrunchCommand::Scale { factor } => { let image = load_image(self.in_path.clone(), self.format)?; let output = rescale(&image, *factor)?; output.save(&self.out_path).map_err(anyhow::Error::from) } CrunchCommand::Rotate { amount } => { let image = load_image(self.in_path.clone(), self.format)?; let output = rotate(&image, *amount)?; output.save(&self.out_path).map_err(anyhow::Error::from) } CrunchCommand::Flip { direction } => { let image = load_image(self.in_path.clone(), self.format)?; let output = flip(&image, *direction)?; output.save(&self.out_path).map_err(anyhow::Error::from) } CrunchCommand::Pipeline => execute_pipeline(&self.in_path, &self.out_path), } } }