Skip to content
Snippets Groups Projects
extrude.rs 3.28 KiB
Newer Older
Louis's avatar
Louis committed
use crate::utils::{RgbaOutputFormat, SpriteData};
use clap::Parser;
Louis's avatar
Louis committed
use image::{GenericImage, GenericImageView, Pixel, Rgba, RgbaImage};
use num_traits::cast::ToPrimitive;
use serde::{Deserialize, Serialize};
Louis's avatar
Louis committed

#[inline(always)]
fn tile_size() -> u32 {
	32
}
Louis's avatar
Louis committed

/// Take each tile in an image and expand its borders by a given amount. Optionally fill with
/// nearby pixels instead of empty space
#[derive(Parser, Serialize, Deserialize, Clone, Debug)]
#[clap(author, version = "0.3.0")]
pub struct Extrude {
	/// The path to the spritesheet file
	#[serde(default)]
	pub input: String,
	/// The path to write the extruded spritesheet
	#[serde(default)]
	pub output: String,

	/// The amount of horizontal space to add between each sprite in the image
	#[clap(short = 'x', long, default_value_t = 0)]
	#[serde(default)]
Louis's avatar
Louis committed
	space_x: u32,
	/// The amount of vertical space to add between each sprite in the image
	#[clap(short = 'y', long, default_value_t = 0)]
	#[serde(default)]
Louis's avatar
Louis committed
	space_y: u32,
	/// The amount of padding to add around the edge of the spritesheet
	#[clap(short, long, default_value_t = 0)]
	#[serde(default)]
	/// The size of each tile in the spritesheet. Assumed to be square tiles
	#[clap(short, long, default_value_t = 32)]
	#[serde(default = "tile_size")]
	tile_size: u32,
	/// Use nearby pixels for spacing
	#[clap(short, long)]
	#[serde(default)]
	extrude: bool,
}

impl Extrude {
	pub fn run(&self, image: &impl GenericImage) -> anyhow::Result<RgbaOutputFormat> {
		log::info!(
			"Image loaded with size {} x {}",
			image.width(),
			image.height()
		);
Louis's avatar
Louis committed

		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);
Louis's avatar
Louis committed

		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;
Louis's avatar
Louis committed

				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,
				};
Louis's avatar
Louis committed

				views.push(payload);
			}
Louis's avatar
Louis committed
		}

		let new_width = (self.padding * 2 + self.space_x * columns) + image.width();
		let new_height = (self.padding * 2 + self.space_y * rows) + image.height();
Louis's avatar
Louis committed

		log::info!(
			"Using new image width {} / height {}",
			new_width,
			new_height
		);
		let mut new_image = RgbaImage::new(new_width, new_height);
		let (new_image_x, new_image_y, new_image_width, new_image_height) = new_image.bounds();
		for x in new_image_x..new_image_width {
			for y in new_image_y..new_image_height {
				new_image.put_pixel(x, y, Rgba::from([0, 0, 0, 0]));
			}
Louis's avatar
Louis committed
		}

		for sprite in views.iter() {
			let (img_x, img_y, width, height) = sprite.data.bounds();
			for x in 0..width {
				for y in 0..height {
					let pix = sprite.data.get_pixel(x, y).to_rgba();
					let p = Rgba::from([
						pix.0[0].to_u8().unwrap(),
						pix.0[1].to_u8().unwrap(),
						pix.0[2].to_u8().unwrap(),
						pix.0[3].to_u8().unwrap(),
					]);
Louis's avatar
Louis committed

					new_image.put_pixel(
						self.padding + (sprite.tx * self.space_x) + img_x + x,
						self.padding + (sprite.ty * self.space_y) + img_y + y,
		Ok(new_image)
	}
Louis's avatar
Louis committed
}