From 4657c177b02ad3f1ce31f68b3b39f691ca1c9baf Mon Sep 17 00:00:00 2001 From: reidlab Date: Tue, 3 Oct 2023 21:39:04 -0700 Subject: [PATCH] i think 2.0 icons are kinda working --- Cargo.lock | 331 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + readme.md | 10 +- src/assets.rs | 68 +++++++++- src/constants.rs | 9 +- src/lib.rs | 19 +-- src/renderer.rs | 117 +++++++++++++++-- 7 files changed, 522 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf832da..bb9a219 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,27 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -56,6 +71,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -104,6 +128,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + [[package]] name = "deranged" version = "0.3.8" @@ -165,8 +195,32 @@ name = "gd-icon-renderer" version = "0.1.0" dependencies = [ "image", + "imageproc", "maplit", "plist", + "rand 0.8.5", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -213,6 +267,24 @@ dependencies = [ "tiff", ] +[[package]] +name = "imageproc" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aee993351d466301a29655d628bfc6f5a35a0d062b6160ca0808f425805fd7" +dependencies = [ + "approx", + "conv", + "image", + "itertools", + "nalgebra", + "num", + "rand 0.7.3", + "rand_distr", + "rayon", + "rusttype", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -223,6 +295,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -244,6 +325,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + [[package]] name = "line-wrap" version = "0.1.1" @@ -269,6 +356,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.6.3" @@ -294,6 +391,55 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "nalgebra" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb2d0de08694bed883320212c18ee3008576bfe8c306f4c3c4a58b4876998be" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -304,6 +450,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -311,6 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", + "num-bigint", "num-integer", "num-traits", ] @@ -324,6 +482,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "owned_ttf_parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "plist" version = "1.5.0" @@ -351,6 +524,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.67" @@ -387,6 +566,92 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_distr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" +dependencies = [ + "rand 0.7.3", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.8.0" @@ -407,6 +672,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rusttype" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "safe_arch" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +dependencies = [ + "bytemuck", +] + [[package]] name = "safemem" version = "0.3.3" @@ -439,6 +723,19 @@ dependencies = [ "syn", ] +[[package]] +name = "simba" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -510,18 +807,52 @@ dependencies = [ "time-core", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +[[package]] +name = "wide" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa469ffa65ef7e0ba0f164183697b89b854253fd31aeb92358b7b6155177d62f" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/Cargo.toml b/Cargo.toml index c8f7147..aed23c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,5 +5,7 @@ edition = "2021" [dependencies] image = "0.24.7" +imageproc = "0.23.0" maplit = "1.0.2" plist = "1.5.0" +rand = "0.8.5" diff --git a/readme.md b/readme.md index 4ddf515..f9466bd 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # gd-icon-renderer -rust geometryd ash icon redner!! +rust geometryd ash icon redner!! shout out to [gd-icon-renderer](https://github.com/oatmealine/gd-icon-renderer) oat ## usage @@ -8,7 +8,7 @@ Provide your `GJ_GameSheet02-uhd`, `GJ_GameSheetGlow-uhd`, `Robot_AnimDesc2`, an ## todo -- maybe use custom plist parser -- spider + robot support -- make `get_sprite_from_loaded` and `get_sprite` merged into `get_sprite`. i think this needs traits or something -- trim empty alpha space (robtop didnt make the bounds correctly :sob:) \ No newline at end of file +- use custom plist parser. current one takes like 5 seconds to parse an animation file +- change zany anim to argument +- trim empty alpha space (robtop didnt make the bounds correctly :sob:) +- i think theres some slight shifting in the transform to do with rotation. investigate plz. really big on spider 16 for some reason???? \ No newline at end of file diff --git a/src/assets.rs b/src/assets.rs index 6d18a2f..4a200bf 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -128,6 +128,72 @@ pub fn load_spritesheet(path: &str) -> LoadedSpritesheet { } } +// Represents the metadata of an animation frame's sprite +#[derive(Clone, Debug)] +pub struct AnimationSprite { + pub texture: String, + pub position: (f32, f32), + pub scale: (f32, f32), + pub rotation: f64, + pub flipped: (bool, bool), + pub z: i32 +} + +impl AnimationSprite { + fn initialize(obj: plist::Value) -> AnimationSprite { + let hash = obj.as_dictionary().expect("object must be a dict"); + + let hash_keys = vec!["texture", "position", "scale", "rotation", "flipped", "zValue"]; + + let isolated: Vec<(&&str, Option<&plist::Value>)> = hash_keys + .iter() + .map(|s| (s, hash.get(s))) + .collect(); + + let missing: Vec<&(&&str, Option<&plist::Value>)> = isolated + .iter() + .filter(|&&(_, value)| value.is_none()) + .collect(); + + if !missing.is_empty() { + let missing_entries: Vec<&str> = missing.iter().map(|(&key, _)| key).collect(); + panic!("missing entries: {:?}", missing_entries); + } + + let isolated_hash: HashMap = isolated + .iter() + .map(|&(key, value)| (key.to_string(), value.expect("value is none after checking").clone())) + .collect(); + + return AnimationSprite { + texture: isolated_hash.get("texture").expect("missing texture").as_string().expect("texture is not a string").to_string(), + position: parse_vec_f32(isolated_hash.get("position").expect("missing position").as_string().expect("position is not a string")), + scale: parse_vec_f32(isolated_hash.get("scale").expect("missing scale").as_string().expect("scale is not a string")), + rotation: isolated_hash.get("rotation").expect("missing rotation").as_string().expect("rotation is not a string").parse::().expect("couldnt parse rotation as f64"), + flipped: { + let flipped_numbers = parse_vec(isolated_hash.get("flipped").expect("missing flipped").as_string().expect("flipped is not a string")); + (flipped_numbers.0 > 0, flipped_numbers.1 > 0) + }, + z: isolated_hash.get("zValue").expect("missing zValue").as_string().expect("zValue is not a string").parse::().expect("couldnt parse zValue as i32") + } + } +} + +pub type Animations = HashMap>; + +pub fn load_animations(path: &str) -> Animations { + let loaded_plist: plist::Value = plist::from_file(path).expect("could not load plist"); + let animations = loaded_plist.as_dictionary().expect("object must be a dict").get("animationContainer").expect("key `animationContainer` doesnt exist").as_dictionary().expect("`animationContainer` must be a dict"); + let mut parsed_animations: Animations = HashMap::new(); + for (k, v) in animations.iter() { + parsed_animations.insert(k.clone(), vec![] as Vec); + parsed_animations.get_mut(k.as_str()).expect("this should exist..") + .extend(v.as_dictionary().expect("animation must be a dict") + .iter().map(|(_, v)| AnimationSprite::initialize(v.clone()))); + } + return parsed_animations; +} + // Trims out a sprite from an image according to a .plist spritesheet. pub fn get_sprite(spritesheet: Spritesheet, img: DynamicImage, key: String) -> Option<(DynamicImage, Sprite)> { let sprite = spritesheet.sprites.get(&key); @@ -155,7 +221,7 @@ pub fn get_sprite(spritesheet: Spritesheet, img: DynamicImage, key: String) -> O return Some((canvas, sprite.clone())); } - panic!("The sprite should have been found in the spritesheet or not found at all") + unreachable!("The sprite should have been found in the spritesheet or not found at all") } pub fn get_sprite_from_loaded(spritesheet: LoadedSpritesheet, key: String) -> Option<(DynamicImage, Sprite)> { diff --git a/src/constants.rs b/src/constants.rs index 945897a..05dd573 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -49,8 +49,8 @@ pub const COLORS: &'static [[f32; 3]] = &[ // `zany` = uses 2.0 gamemode render system w/ multiple moving parts pub struct Gamemode { - prefix: String, - zany: bool + pub prefix: String, + pub zany: bool } pub static GAMEMODES: LazyLock> = LazyLock::new(|| { hashmap! { @@ -59,7 +59,6 @@ pub static GAMEMODES: LazyLock> = LazyLock::new(|| { has "ball" => Gamemode { prefix: "player_ball_".to_string(), zany: false }, "ufo" => Gamemode { prefix: "bird_".to_string(), zany: false }, "wave" => Gamemode { prefix: "dart_".to_string(), zany: false }, - // unimplemented - // "robot" => Gamemode { prefix: "robot_".to_string(), zany: true }, - // "spider" => Gamemode { prefix: "spider_".to_string(), zany: true }, + "robot" => Gamemode { prefix: "robot_".to_string(), zany: true }, + "spider" => Gamemode { prefix: "spider_".to_string(), zany: true } }}); diff --git a/src/lib.rs b/src/lib.rs index 4b36c24..660e207 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,26 +11,21 @@ mod tests { use renderer::*; use assets::*; - // not actually used, just for benchmarking use std::time::Instant; #[test] fn it_works() { let game_sheet_02 = load_spritesheet("assets/GJ_GameSheet02-uhd.plist"); let game_sheet_glow = load_spritesheet("assets/GJ_GameSheetGlow-uhd.plist"); + let robot_sheet = load_animations("assets/Robot_AnimDesc2.plist"); + let spider_sheet = load_animations("assets/Spider_AnimDesc2.plist"); let start = Instant::now(); - let rendered_img = render_normal( - "ship_18".to_string(), - [0.0/255.0, 0.0/255.0, 0.0/255.0], - [0.0/255.0, 0.0/255.0, 0.0/255.0], - true, - game_sheet_02, - game_sheet_glow, - ); + let rendered_icon = render_icon("ship", 44, [0.0, 0.0, 0.0], [255.0/255.0, 125.0/255.0, 125.0/255.0], true, game_sheet_02, game_sheet_glow, robot_sheet, spider_sheet); + let end = start.elapsed(); - rendered_img.save("rendered_icon.png").expect("saving image failed"); - let end = Instant::now(); - println!("Time elapsed: {:?}", end.duration_since(start)); + println!("time taken to render: {:?}", end); + + rendered_icon.save("rendered_icon.png").expect("saving image failed"); } } diff --git a/src/renderer.rs b/src/renderer.rs index 4bf45df..ca7f5be 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,13 +1,14 @@ use image::*; use image::{DynamicImage, imageops}; +use imageproc::geometric_transformations::{rotate_about_center, Interpolation}; use std::cmp; use crate::assets; -use crate::assets::LoadedSpritesheet; +use crate::assets::{LoadedSpritesheet, Animations, Sprite}; // Internal function to easily transform an image -fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32, f32)>, rotation: Option) -> DynamicImage { +fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32, f32)>, rotation: Option) -> DynamicImage { let mut transformed_image = image.clone(); if let Some(color) = color { @@ -44,22 +45,39 @@ fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32, } if let Some(rotation) = rotation { - match rotation { - 0 => (), - 90 => transformed_image = transformed_image.rotate90(), - 180 => transformed_image = transformed_image.rotate180(), - 270 => transformed_image = transformed_image.rotate270(), - _ => panic!("rotation must be 0, 90, 180, or 270"), + // lets not rotate if we dont need to + if rotation == 0.0 { + return transformed_image; } + + let radians = rotation.to_radians(); + + let (width, height) = transformed_image.dimensions(); + + let trig_width = (width as f32 * radians.cos() + height as f32 * radians.sin()) + .abs() + .ceil() as u32; + let trig_height = (width as f32 * radians.sin() + height as f32 * radians.cos()) + .abs() + .ceil() as u32; + + let transform_x = ((trig_width as f32 / 2.0) - (width as f32 / 2.0)).ceil() as u32; + let transform_y = ((trig_height as f32 / 2.0) - (height as f32 / 2.0)).ceil() as u32; + + let mut canvas = ImageBuffer::new(cmp::max(trig_width, width), cmp::max(trig_height, height)); + canvas.copy_from(&transformed_image, transform_x, transform_y).expect("couldnt copy from img"); + canvas = rotate_about_center(&canvas, radians, Interpolation::Bilinear, Rgba([0, 0, 0, 0])); + + transformed_image = DynamicImage::ImageRgba8(canvas); } return transformed_image; } // Mainly for internal use; given an array of images, their sizes and colors, tints and composits them into a single image -pub fn render_layered(images: Vec, positions: Vec>, colors: Vec<[f32; 3]>, scales: Vec>, rotations: Vec>) -> DynamicImage { +pub fn render_layered(images: Vec, positions: Vec>, colors: Vec>, scales: Vec>, rotations: Vec>) -> DynamicImage { let transformed: Vec = images.iter().enumerate().map(|(i, img)| { - transform(img, Some(colors[i]), scales[i], rotations[i]) + transform(img, colors[i], scales[i], rotations[i]) }).collect(); let sizes: Vec<(i64, i64)> = transformed.iter().map(|img| { (img.width() as i64, img.height() as i64) @@ -139,9 +157,86 @@ pub fn render_normal(basename: String, col1: [f32; 3], col2: [f32; 3], glow: boo .collect(), colors.iter() .enumerate() - .filter_map(|(i, color)| layers[i].clone().map(|_| color.unwrap())) + .filter_map(|(i, color)| layers[i].clone().map(|_| color.to_owned())) .collect(), vec![None, None, None, None, None], vec![None, None, None, None, None] ); +} + +fn flip(scale: (f32, f32), flipped: (bool, bool)) -> (f32, f32) { + (scale.0 * (if flipped.0 { -1 } else { 1 }) as f32, scale.1 * (if flipped.1 { -1 } else { 1 }) as f32) +} + +// Renders out a robot/spider icon. You may be looking for `render_icon`. +pub fn render_zany(basename: String, col1: [f32; 3], col2: [f32; 3], glow: bool, game_sheet_02: LoadedSpritesheet, _game_sheet_glow: LoadedSpritesheet, animations: Animations) -> DynamicImage { + let glow_col = if is_black(col2) { if is_black(col1) { [1.0, 1.0, 1.0] } else { col1 } } else { col2 }; + let glow = glow || (is_black(col1) && is_black(col2)); + + let mut anim = animations.get("Robot_idle_001.png").unwrap_or_else(|| animations.get("Spider_idle_001.png").expect("no animations found")).clone(); + anim.sort_by_key(|spr| spr.z); + + let mut layers = anim + .iter() + .map(|a| { + let texture_name = a.texture.clone().replace("spider_01", &basename).replace("robot_01", &basename); + let mut names = vec![ + texture_name.replace("_001.png", "_2_001.png"), + texture_name.replace("_001.png", "_3_001.png"), + texture_name.clone(), + texture_name.replace("_001.png", "_extra_001.png") + ]; + let mut colors = vec![ + Some(col2), + None, + Some(col1), + None + ]; + + if glow { + names.push(texture_name.replace("_001.png", "_glow_001.png")); + colors.push(Some(glow_col)); + } + + names.iter().enumerate().map(|(i, v)| + ( + assets::get_sprite_from_loaded(game_sheet_02.to_owned().clone(), v.clone()), + a.position, + flip(a.scale, a.flipped), + a.rotation, + glow && i == names.len() - 1, + colors[i] + ) + ).collect::, (f32, f32), (f32, f32), f64, bool, Option<[f32; 3]>)>>() + }) + .flatten() + .collect::, (f32, f32), (f32, f32), f64, bool, Option<[f32; 3]>)>>(); + + // put glow b4 everything else + layers.sort_by_key(|t| if t.4 { 0 } else { 1 }); + + let layers_r = layers.iter() + .filter(|v| v.0.is_some()) + .filter_map(|(opt_sprite, pos, scale, rot, glow, color)| opt_sprite.clone().map(|sprite| ((sprite.0, sprite.1), *pos, *scale, *rot, *glow, *color))) + .collect::)>>(); + + return render_layered( + layers_r.iter().map(|t| t.0.0.clone()).collect(), + layers_r.iter().map(|t| Some((t.0.1.offset.0 + t.1.0 * 4.0, t.0.1.offset.1 * -1.0 + t.1.1 * -4.0))).collect(), + layers_r.iter().map(|t| t.5).collect(), + layers_r.iter().map(|t| Some(t.2)).collect(), + layers_r.iter().map(|t| Some(t.3 as f32)).collect() + ) +} + +// The main entrypoint for icon rendering; this should be all you need to render out an icon. +// `gamemode` must be one of `cube`, `ship`, `ball`, `ufo`, `wave`, `robot`, or `spider` +pub fn render_icon(gamemode_str: &str, icon: i32, col1: [f32; 3], col2: [f32; 3], glow: bool, game_sheet_02: LoadedSpritesheet, game_sheet_glow: LoadedSpritesheet, robot_animations: Animations, spider_animations: Animations) -> DynamicImage { + let gamemode = crate::constants::GAMEMODES.get(gamemode_str).expect("invalid gamemode"); + + if gamemode.zany { + return render_zany(format!("{}{:02}", gamemode.prefix, icon), col1, col2, glow, game_sheet_02, game_sheet_glow, if gamemode_str == "robot" { robot_animations } else { spider_animations }) + } else { + return render_normal(format!("{}{:02}", gamemode.prefix, icon), col1, col2, glow, game_sheet_02, game_sheet_glow) + } } \ No newline at end of file