From 4b3e9e5bba42cea847a1b0efe91b3baac21e74d9 Mon Sep 17 00:00:00 2001 From: Florian Gebhardt Date: Sat, 23 Mar 2024 14:31:57 +0100 Subject: [PATCH] feat: lua output generation (#15) * feat: lua output generation * feat: add scale lua output --- .gitignore | 2 +- Cargo.lock | 2 +- Cargo.toml | 5 +- README.md | 8 +-- src/lua.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 120 +++++++++++++++++++++++++++++++------ 6 files changed, 277 insertions(+), 26 deletions(-) create mode 100644 src/lua.rs diff --git a/.gitignore b/.gitignore index 14a199e..5fda29b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .vscode /target -*.png \ No newline at end of file +test/ diff --git a/Cargo.lock b/Cargo.lock index 7285bc4..0057955 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "spritter" -version = "0.3.0" +version = "0.4.0" dependencies = [ "clap", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 688b7f5..29eb892 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "spritter" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["fgardt "] description = "Spritesheet generator for factorio" +repository = "https://github.com/fgardt/factorio-spritter" [profile.release] strip = true @@ -17,7 +18,9 @@ nursery = "warn" pedantic = "warn" unwrap_used = "warn" expect_used = "warn" +module_name_repetitions = "allow" cast_possible_truncation = "allow" +cast_precision_loss = "allow" cast_possible_wrap = "allow" cast_lossless = "allow" cast_sign_loss = "allow" diff --git a/README.md b/README.md index f73a51c..64b1738 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -[![release](https://github.com/fgardt/factorio-spritter/actions/workflows/release.yml/badge.svg)](https://github.com/fgardt/factorio-spritter/releases) +![actions status](https://img.shields.io/github/actions/workflow/status/fgardt/factorio-spritter/rust.yml) +[![release](https://img.shields.io/github/v/release/fgardt/factorio-spritter)](https://github.com/fgardt/factorio-spritter/releases) +[![ko-fi](https://img.shields.io/badge/Ko--fi-Donate%20-hotpink?logo=kofi&logoColor=white)](https://ko-fi.com/fgardt) # Spritter @@ -62,7 +64,3 @@ Arguments: Output folder ``` - -## Todo - -- Lua output generation diff --git a/src/lua.rs b/src/lua.rs new file mode 100644 index 0000000..d53cab5 --- /dev/null +++ b/src/lua.rs @@ -0,0 +1,166 @@ +use std::{collections::BTreeMap, io::Write, path::Path}; + +#[derive(Debug, Clone)] +pub enum LuaValue { + String(String), + Float(f64), + Int(i64), + Bool(bool), + Shift(f64, f64, usize), + Require(String), +} + +impl From for LuaValue { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From<&str> for LuaValue { + fn from(value: &str) -> Self { + Self::String(value.to_owned()) + } +} + +impl From for LuaValue { + fn from(value: f64) -> Self { + Self::Float(value) + } +} + +impl From for LuaValue { + fn from(value: f32) -> Self { + Self::Float(value as f64) + } +} + +impl From for LuaValue { + fn from(value: isize) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: i64) -> Self { + Self::Int(value) + } +} + +impl From for LuaValue { + fn from(value: i32) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: i16) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: i8) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: usize) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: u64) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: u32) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: u16) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: u8) -> Self { + Self::Int(value as i64) + } +} + +impl From for LuaValue { + fn from(value: bool) -> Self { + Self::Bool(value) + } +} + +impl From<(f64, f64, usize)> for LuaValue { + fn from((shift_x, shift_y, res): (f64, f64, usize)) -> Self { + Self::Shift(shift_x, shift_y, res) + } +} + +impl std::fmt::Display for LuaValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::String(value) => write!(f, "\"{value}\""), + Self::Float(value) => write!(f, "{value}"), + Self::Int(value) => write!(f, "{value}"), + Self::Bool(value) => write!(f, "{value}"), + Self::Shift(x, y, res) => write!(f, "{{x = {x} / {res}, y = {y} / {res}}}"), + Self::Require(value) => write!(f, "require(\"{value}\")"), + } + } +} + +pub struct LuaOutput { + map: BTreeMap, +} + +impl LuaOutput { + pub const fn new() -> Self { + Self { + map: BTreeMap::new(), + } + } + + pub fn set(mut self, key: impl AsRef, value: impl Into) -> Self { + self.map.insert(key.as_ref().to_owned(), value.into()); + self + } + + pub fn reexport(mut self, name: impl AsRef) -> Self { + self.map.insert( + name.as_ref().to_owned(), + LuaValue::Require(name.as_ref().to_owned()), + ); + self + } + + pub fn save(&self, path: impl AsRef) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + + writeln!( + file, + "-- Generated by {} v{} - {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_REPOSITORY") + )?; + writeln!(file, "return {{")?; + + for (key, data) in &self.map { + writeln!(file, " [\"{key}\"] = {data},")?; + } + + writeln!(file, "}}")?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 7c5da2f..5282ab4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use std::{ use clap::{Args, Parser, Subcommand}; use image::{ImageBuffer, RgbaImage}; use image_util::ImageBufferExt; +use lua::LuaOutput; use rayon::prelude::*; #[macro_use] @@ -14,6 +15,7 @@ extern crate log; mod image_util; mod logger; +mod lua; #[derive(Parser, Debug)] #[command(version, about, long_about=None)] @@ -133,14 +135,63 @@ impl SpritesheetArgs { return Ok(()); } - sources.par_iter().for_each(|source| { - if let Err(err) = generate_spritesheet(self, source) { - error!("{}: {err}", source.display(),); + let res = sources + .par_iter() + .filter_map(|source| match generate_spritesheet(self, source) { + Ok(res_name) => { + if res_name.is_empty() { + None + } else { + Some(res_name) + } + } + Err(err) => { + error!("{}: {err}", source.display()); + None + } + }) + .collect::>(); + + 'r_group: { + if self.recursive && self.lua && !res.is_empty() { + #[allow(clippy::unwrap_used)] + let name = self + .source + .components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .to_string(); + + if res.contains(&name) { + warn!("skipping lua generation for recursive group: collision with source folder name"); + break 'r_group; + } + + let mut out_path = self.output.join(name); + out_path.set_extension("lua"); + + let mut output = LuaOutput::new(); + + for name in res { + output = output.reexport(name); + } + + output.save(out_path)?; } - }); + } Ok(()) } + + const fn tile_res(&self) -> usize { + if self.hr { + 64 + } else { + self.tile_resolution + } + } } #[derive(Args, Debug)] @@ -165,9 +216,10 @@ struct SharedArgs { /// Output folder pub output: PathBuf, - // /// Enable lua output generation - // #[clap(short, long, action)] - // lua: bool, + + /// Enable lua output generation + #[clap(short, long, action)] + lua: bool, } fn main() -> ExitCode { @@ -188,22 +240,28 @@ fn main() -> ExitCode { ExitCode::SUCCESS } -fn output_name(source: &Path, output_dir: &Path, id: Option, extension: &str) -> PathBuf { +fn output_name( + source: impl AsRef, + output_dir: impl AsRef, + id: Option, + extension: &str, +) -> PathBuf { + #[allow(clippy::unwrap_used)] let name = source + .as_ref() .components() .last() .unwrap() .as_os_str() - .to_str() - .unwrap_or_default() - .to_owned(); + .to_string_lossy() + .to_string(); let suffixed_name = match id { Some(id) => format!("{name}-{id}"), None => name, }; - let mut out = output_dir.to_path_buf().join(suffixed_name); + let mut out = output_dir.as_ref().join(suffixed_name); out.set_extension(extension); out @@ -239,6 +297,7 @@ fn generate_mipmap_icon(args: &IconArgs) -> Result<(), CommandError> { images.sort_by_key(ImageBuffer::width); images.reverse(); + #[allow(clippy::unwrap_used)] let (base_width, base_height) = images.first().unwrap().dimensions(); if base_width != base_height { Err(IconError::ImageNotSquare)?; @@ -281,6 +340,13 @@ fn generate_mipmap_icon(args: &IconArgs) -> Result<(), CommandError> { .to_image() .save_optimized_png(output_name(&args.source, &args.output, None, "png"))?; + if args.lua { + LuaOutput::new() + .set("icon_size", base_width) + .set("icon_mipmaps", images.len()) + .save(output_name(&args.source, &args.output, None, "lua"))?; + } + Ok(()) } @@ -294,13 +360,13 @@ enum SpriteSheetError { fn generate_spritesheet( args: &SpritesheetArgs, path: impl AsRef, -) -> Result<(), CommandError> { +) -> Result { let source = path.as_ref(); let mut images = image_util::load_from_path(source)?; if images.is_empty() { warn!("{}: no source images found", source.display()); - return Ok(()); + return Ok(String::new()); } let (shift_x, shift_y) = if args.no_crop { @@ -309,8 +375,9 @@ fn generate_spritesheet( image_util::crop_images(&mut images)? }; - let sprite_count = images.len() as u32; + #[allow(clippy::unwrap_used)] let (sprite_width, sprite_height) = images.first().unwrap().dimensions(); + let sprite_count = images.len() as u32; let max_size: u32 = if args.hr { 8192 } else { 2048 }; @@ -403,8 +470,14 @@ fn generate_spritesheet( sheet.save_optimized_png(path)?; } - let name = output_name(source, &args.output, None, ""); - let name = name.file_name().unwrap().to_str().unwrap(); + #[allow(clippy::unwrap_used)] + let name = source + .components() + .last() + .unwrap() + .as_os_str() + .to_string_lossy() + .to_string(); if args.no_crop { info!("completed {name}, size: ({sprite_width}px, {sprite_height}px)"); @@ -414,7 +487,18 @@ fn generate_spritesheet( ); } - Ok(()) + if args.lua { + LuaOutput::new() + .set("width", sprite_width) + .set("height", sprite_height) + .set("shift", (shift_x, shift_y, args.tile_res())) + .set("scale", 32.0 / args.tile_res() as f64) + .set("sprite_count", sprite_count) + .set("line_length", cols_per_sheet) + .save(output_name(source, &args.output, None, "lua"))?; + } + + Ok(name) } /*