diff --git a/Cargo.lock b/Cargo.lock index df714a9ca3bba969392086af86662b36ab358804..d6e742141295881728784c923e70ab6d3126372f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler" @@ -233,7 +233,7 @@ dependencies = [ [[package]] name = "crunch-cli" -version = "0.8.2" +version = "0.9.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2eabda7129cb693edfe94e9db7caa9d3f617efc8..a23223dc4e29026187d60e296f067cc62c44c873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crunch-cli" -version = "0.8.2" +version = "0.9.0" edition = "2021" homepage = "https://microhacks.lcr.app/crunch/" diff --git a/src/cli_args.rs b/src/cli_args.rs index ba1cc538e3375af0b03ce475a9eac7ac22dce4c4..8e8090dbd8970405105dcb0aa3dcd3a807743aaa 100644 --- a/src/cli_args.rs +++ b/src/cli_args.rs @@ -2,9 +2,7 @@ use clap::Parser; use image::ImageFormat; use serde::{Deserialize, Serialize}; -use crate::commands::{ - Atlas, Extract, Extrude, Flip, Info, Palette, Pipeline, Reduce, Remap, Rotate, Scale, Split, -}; +use crate::commands::{Atlas, Extract, Extrude, Flip, Info, Palette, Pipeline, Reduce, Remap, Rotate, Scale, Split, Swap}; use crate::load_image; @@ -53,6 +51,9 @@ pub enum Args { #[clap(name = "info")] #[serde(alias = "info")] Info(Info), + #[clap(name = "info")] + #[serde(alias = "info")] + Swap(Swap), } impl Args { @@ -104,6 +105,15 @@ impl Args { .save_with_format(&remap.output, ImageFormat::Png) .map_err(anyhow::Error::from) } + Args::Swap(swap) => { + let image_data = load_image(&swap.input, None)?; + let palette_data = load_image(&swap.palette, None)?; + let palette_swap = Palette::from_columns(&palette_data)?; + let output = Remap::remap_image(image_data, palette_swap)?; + output + .save_with_format(&swap.output, ImageFormat::Png) + .map_err(anyhow::Error::from) + }, Args::Reduce(reduce) => { if let Some(amount) = reduce.colours { log::info!("Num cols {}", amount); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a3c215c687b8ede2018ceb59bf620ed70e682967..7a2d578e2c22fd1fbd6cbfc2fc5bf84a3a819168 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -10,6 +10,7 @@ mod remap; mod rotate; mod scale; mod split; +mod swap; pub use atlas::Atlas; pub use extract::Extract; @@ -23,3 +24,4 @@ pub use remap::Remap; pub use rotate::Rotate; pub use scale::Scale; pub use split::Split; +pub use swap::Swap; \ No newline at end of file diff --git a/src/commands/palette.rs b/src/commands/palette.rs index bc6040f21eaeeefa95ea37eb1ce115d69d7458e4..2778712b38c7587968175a77b20e3357637efa66 100644 --- a/src/commands/palette.rs +++ b/src/commands/palette.rs @@ -4,7 +4,7 @@ use std::collections::hash_map::RandomState; use std::collections::{HashMap, HashSet}; use std::io::Write; - +use anyhow::Error; use deltae::{Delta, LabValue, DE2000}; use image::{GenericImage, Pixel, Rgba}; use num_traits::ToPrimitive; @@ -110,6 +110,37 @@ impl Palette { Ok(()) } + pub fn from_columns(image: &impl GenericImage) -> anyhow::Result<ColourMapping> { + if image.width() != 2 { + return Err(Error::msg("Image must have a pixel width of 2"))?; + } + + let mut mapping = ColourMapping::with_capacity(image.height() as usize); + for y in 0..image.height() { + let left = image.get_pixel(0, y); + let right = image.get_pixel(1, y); + let left = left.to_rgba(); + let right = right.to_rgba(); + let data = Rgba::from([ + left.0[0].to_u8().unwrap(), + left.0[1].to_u8().unwrap(), + left.0[2].to_u8().unwrap(), + left.0[3].to_u8().unwrap(), + ]); + let left_pixel = BasicRgba::from(data); + let data = Rgba::from([ + left.0[0].to_u8().unwrap(), + left.0[1].to_u8().unwrap(), + left.0[2].to_u8().unwrap(), + left.0[3].to_u8().unwrap(), + ]); + let right_pixel = BasicRgba::from(data); + mapping.insert(left_pixel, right_pixel); + } + + Ok(mapping) + } + pub fn calculate_mapping(from: &PixelPalette, to: &PixelPalette) -> ColourMapping { let colour_labs = Vec::from_iter(to.iter().map(LabValue::from)); diff --git a/src/commands/swap.rs b/src/commands/swap.rs new file mode 100644 index 0000000000000000000000000000000000000000..c8e772e1e99ad05e4d71be268a764c7969a0d1f4 --- /dev/null +++ b/src/commands/swap.rs @@ -0,0 +1,62 @@ +use crate::commands::palette::ColourMapping; +use clap::Parser; +use image::{GenericImage, Pixel, Rgba}; +use num_traits::ToPrimitive; +use serde::{Deserialize, Serialize}; + +use crate::utils::{new_image, BasicRgba, OutputFormat}; + +/// Use an existing colour mapping file to swap colours in the input sprite +#[derive(Debug, Clone, Parser, Serialize, Deserialize)] +#[clap(author, version = "0.9.0")] +pub struct Swap { + /// The path to the image file + #[serde(default)] + pub input: String, + /// The path to write the recoloured image + #[serde(default)] + pub output: String, + + /// The path to the palette file containing the output colours + #[clap(short, long)] + pub palette: String, +} + +impl Swap { + pub fn swap_pixels( + image: impl GenericImage, + mappings: ColourMapping, + ) -> anyhow::Result<OutputFormat> { + let mut output = new_image(image.width(), image.height()); + for (x, y, pixel) in image.pixels() { + let pixel = pixel.to_rgba(); + + if pixel.0[3].to_u8().unwrap() == 0 { + output.put_pixel(x, y, Rgba::from(BasicRgba::transparent())); + continue; + } + + let data = Rgba::from([ + pixel.0[0].to_u8().unwrap(), + pixel.0[1].to_u8().unwrap(), + pixel.0[2].to_u8().unwrap(), + pixel.0[3].to_u8().unwrap(), + ]); + let basic = BasicRgba::from(data); + + match mappings.get(&basic) { + Some(mapped_data) => output.put_pixel( + x, + y, + Rgba::from(BasicRgba { + a: basic.a, + ..*mapped_data + }), + ), + None => output.put_pixel(x, y, data), + }; + } + + Ok(output) + } +}