diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 1b6859e49fa177307b324957e0b6b2b43766778f..fba26d873d02596c1f00cba5e57ff2802a4ca5b3 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1,3 +1,5 @@ mod colours; +mod sheets; pub use colours::apply_colour_map; +pub use sheets::ExtrudeOptions; diff --git a/src/actions/sheets.rs b/src/actions/sheets.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1b551e8eeb55af2de1aa5b942688de009cce0d3 --- /dev/null +++ b/src/actions/sheets.rs @@ -0,0 +1,120 @@ +use crate::utils::{RgbaOutputFormat, SpriteData}; +use image::math::Rect; +use image::{DynamicImage, GenericImage, GenericImageView, Rgba, RgbaImage}; +use std::ops::Deref; + +#[derive(Default, Copy, Clone, Debug)] +pub struct ExtrudeOptions { + /// The amount of horizontal space to add between each sprite in the image + pub space_x: u32, + /// The amount of vertical space to add between each sprite in the image + pub space_y: u32, + /// The amount of padding to add around the edge of the spritesheet + pub padding: u32, + /// The size of each tile in the spritesheet. Assumed to be square tiles + pub tile_size: u32, + /// Use nearby pixels for spacing + pub extrude: bool, +} + +impl ExtrudeOptions { + pub fn extrude(&self, image: &DynamicImage) -> anyhow::Result<RgbaOutputFormat> { + log::info!( + "Image loaded with size {} x {}", + image.width(), + image.height() + ); + + let columns = image.width() / self.tile_size; + let rows = image.height() / self.tile_size; + log::info!("Inferred sheet contains {} columns", columns); + log::info!("Inferred sheet contains {} rows", rows); + + let mut views = Vec::with_capacity((columns * rows) as usize); + for x in 0..columns { + for y in 0..rows { + let img_x = x * self.tile_size; + let img_y = y * self.tile_size; + + let payload = SpriteData { + data: image.view(img_x, img_y, self.tile_size, self.tile_size), + x: img_x, + y: img_y, + tx: x, + ty: y, + width: self.tile_size, + height: self.tile_size, + }; + + views.push(payload); + } + } + + let new_width = (self.padding * 2 + self.space_x * columns) + image.width(); + let new_height = (self.padding * 2 + self.space_y * rows) + image.height(); + + log::info!( + "Using new image width {} / height {}", + new_width, + new_height + ); + let mut new_image = RgbaImage::from_pixel(new_width, new_height, Rgba::from([0, 0, 0, 0])); + for sprite in views.iter() { + let (width, height) = sprite.data.dimensions(); + let (img_x, img_y) = sprite.data.offsets(); + let target_x = self.padding + img_x + (sprite.tx * self.space_x); + let target_y = self.padding + img_y + (sprite.ty * self.space_y); + + new_image.copy_from(sprite.data.deref(), target_x, target_y)?; + + if self.extrude { + // Left Side + new_image.copy_within( + Rect { + x: target_x, + y: target_y, + width: 1, + height, + }, + target_x.saturating_sub(1), + target_y, + ); + // Right Side + new_image.copy_within( + Rect { + x: target_x + width - 1, + y: target_y, + width: 1, + height, + }, + target_x + width, + target_y, + ); + // Top Side + new_image.copy_within( + Rect { + x: target_x, + y: target_y, + width, + height: 1, + }, + target_x, + target_y.saturating_sub(1), + ); + // Bottom Side + new_image.copy_within( + Rect { + x: target_x, + y: target_y + height - 1, + width, + height: 1, + }, + target_x, + target_y + height, + ); + } + } + + Ok(new_image) + } +} diff --git a/src/commands/extrude.rs b/src/commands/extrude.rs index 959e8cb7ec595fa3823d345682573cfd454843d5..15aa8dcde64a93f17740f70a05be13f5ada0278a 100644 --- a/src/commands/extrude.rs +++ b/src/commands/extrude.rs @@ -1,11 +1,10 @@ use clap::Parser; -use image::math::Rect; -use image::{GenericImage, GenericImageView, Rgba, RgbaImage}; +use image::{ColorType, DynamicImage, GenericImage, Rgba}; -use crunch_cli::utils::{RgbaOutputFormat, SpriteData}; +use crunch_cli::actions::ExtrudeOptions; +use crunch_cli::utils::RgbaOutputFormat; use serde::{Deserialize, Serialize}; -use std::ops::Deref; #[inline(always)] fn tile_size() -> u32 { @@ -51,102 +50,16 @@ impl Extrude { &self, image: &impl GenericImage<Pixel = Rgba<u8>>, ) -> anyhow::Result<RgbaOutputFormat> { - log::info!( - "Image loaded with size {} x {}", - image.width(), - image.height() - ); + let opts = ExtrudeOptions { + extrude: self.extrude, + space_x: self.space_x, + space_y: self.space_y, + padding: self.padding, + tile_size: self.tile_size, + }; - let columns = image.width() / self.tile_size; - let rows = image.height() / self.tile_size; - log::info!("Inferred sheet contains {} columns", columns); - log::info!("Inferred sheet contains {} rows", rows); - - let mut views = Vec::with_capacity((columns * rows) as usize); - for x in 0..columns { - for y in 0..rows { - let img_x = x * self.tile_size; - let img_y = y * self.tile_size; - - let payload = SpriteData { - data: image.view(img_x, img_y, self.tile_size, self.tile_size), - x: img_x, - y: img_y, - tx: x, - ty: y, - width: self.tile_size, - height: self.tile_size, - }; - - views.push(payload); - } - } - - let new_width = (self.padding * 2 + self.space_x * columns) + image.width(); - let new_height = (self.padding * 2 + self.space_y * rows) + image.height(); - - log::info!( - "Using new image width {} / height {}", - new_width, - new_height - ); - let mut new_image = RgbaImage::from_pixel(new_width, new_height, Rgba::from([0, 0, 0, 0])); - for sprite in views.iter() { - let (width, height) = sprite.data.dimensions(); - let (img_x, img_y) = sprite.data.offsets(); - let target_x = self.padding + img_x + (sprite.tx * self.space_x); - let target_y = self.padding + img_y + (sprite.ty * self.space_y); - - new_image.copy_from(sprite.data.deref(), target_x, target_y)?; - - if self.extrude { - // Left Side - new_image.copy_within( - Rect { - x: target_x, - y: target_y, - width: 1, - height, - }, - target_x.saturating_sub(1), - target_y, - ); - // Right Side - new_image.copy_within( - Rect { - x: target_x + width - 1, - y: target_y, - width: 1, - height, - }, - target_x + width, - target_y, - ); - // Top Side - new_image.copy_within( - Rect { - x: target_x, - y: target_y, - width, - height: 1, - }, - target_x, - target_y.saturating_sub(1), - ); - // Bottom Side - new_image.copy_within( - Rect { - x: target_x, - y: target_y + height - 1, - width, - height: 1, - }, - target_x, - target_y + height, - ); - } - } - - Ok(new_image) + let mut inner_image = DynamicImage::new(image.width(), image.height(), ColorType::Rgba8); + inner_image.copy_from(image, 0, 0)?; + opts.extrude(&inner_image) } }