This commit is contained in:
Reid 2023-10-06 16:20:02 -07:00
parent 3eacdc2c32
commit 1a440b855a
Signed by: reidlab
GPG key ID: 6C9EAA3364F962C8
4 changed files with 45 additions and 33 deletions

View file

@ -2,10 +2,9 @@ use plist;
use std::collections::HashMap; use std::collections::HashMap;
use image::*; use image::DynamicImage;
use image::{DynamicImage, ImageBuffer, imageops};
// "{1,2}" -> `(1, 2)` /// "{1,2}" -> `(1, 2)`
fn parse_vec(str: &str) -> (i32, i32) { fn parse_vec(str: &str) -> (i32, i32) {
let parts: Vec<&str> = str[1..str.len()-1].split(",").collect(); let parts: Vec<&str> = str[1..str.len()-1].split(",").collect();
let a: Vec<i32> = parts let a: Vec<i32> = parts
@ -15,7 +14,7 @@ fn parse_vec(str: &str) -> (i32, i32) {
return (a[0], a[1]) return (a[0], a[1])
} }
// parse_vec, but for float64 /// parse_vec, but for float64
fn parse_vec_f32(str: &str) -> (f32, f32) { fn parse_vec_f32(str: &str) -> (f32, f32) {
let parts: Vec<&str> = str[1..str.len()-1].split(",").collect(); let parts: Vec<&str> = str[1..str.len()-1].split(",").collect();
let a: Vec<f32> = parts let a: Vec<f32> = parts
@ -25,7 +24,7 @@ fn parse_vec_f32(str: &str) -> (f32, f32) {
return (a[0], a[1]) return (a[0], a[1])
} }
// `"{{1,2},{3,4}}"` -> `{{1, 2}, {3, 4}}` /// `"{{1,2},{3,4}}"` -> `{{1, 2}, {3, 4}}`
fn parse_rect_vecs(str: &str) -> ((i32, i32), (i32, i32)) { fn parse_rect_vecs(str: &str) -> ((i32, i32), (i32, i32)) {
let cleaned_str = str.replace("{", "").replace("}", ""); let cleaned_str = str.replace("{", "").replace("}", "");
let parts: Vec<&str> = cleaned_str.split(",").collect(); let parts: Vec<&str> = cleaned_str.split(",").collect();
@ -37,22 +36,23 @@ fn parse_rect_vecs(str: &str) -> ((i32, i32), (i32, i32)) {
return ((a[0], a[1]), (a[2], a[3])) return ((a[0], a[1]), (a[2], a[3]))
} }
// Represents a sprite along with its texture data in a spritesheet. /// Represents a sprite along with its texture data in a spritesheet.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Sprite { pub struct Sprite {
// Whenever rendering the sprite, offset it by this much /// Whenever rendering the sprite, offset it by this much.
pub offset: (f32, f32), pub offset: (f32, f32),
// {left, top}, {width, height}. Controls the cropping /// {left, top}, {width, height}. Controls the cropping.
rect: ((i32, i32), (i32, i32)), pub rect: ((i32, i32), (i32, i32)),
// Whether the texture needs to be counter-rotated 90 degrees counter-clockwise /// Whether the texture needs to be counter-rotated 90 degrees counter-clockwise.
rotated: bool, pub rotated: bool,
size: (i32, i32), /// Size of the sprite.
// Difference between this and `size` is unknown to me pub size: (i32, i32),
source_size: (i32, i32) /// Difference between this and `size` is unknown to me.
pub source_size: (i32, i32)
} }
impl Sprite { impl Sprite {
// Shorthand for initializing a sprite with its .plist representation. /// Shorthand for initializing a sprite with its .plist representation.
fn initialize(obj: plist::Value) -> Sprite { fn initialize(obj: plist::Value) -> Sprite {
let hash = obj.as_dictionary().expect("object must be a dict"); let hash = obj.as_dictionary().expect("object must be a dict");
@ -88,17 +88,17 @@ impl Sprite {
} }
} }
// Represents a spritesheet along with its sprites. /// Represents a spritesheet along with its sprites.
#[derive(Clone)] #[derive(Clone)]
pub struct Spritesheet { pub struct Spritesheet {
sprites: HashMap<String, Sprite>, pub sprites: HashMap<String, Sprite>,
texture_file_name: String, pub texture_file_name: String,
size: (i32, i32) pub size: (i32, i32)
} }
impl Spritesheet { impl Spritesheet {
// Shorthand for initializing a spritesheet with its .plist representation. /// Shorthand for initializing a spritesheet with its .plist representation.
fn initialize(obj: plist::Value) -> Spritesheet { fn initialize(obj: plist::Value) -> Spritesheet {
let hash = obj.as_dictionary().expect("object must be a dict"); let hash = obj.as_dictionary().expect("object must be a dict");
@ -113,14 +113,14 @@ impl Spritesheet {
} }
} }
// Stores both a spritesheet and its associated `DynamicImage` for easy access. /// Stores both a spritesheet and its associated `DynamicImage` for easy access.
#[derive(Clone)] #[derive(Clone)]
pub struct LoadedSpritesheet { pub struct LoadedSpritesheet {
spritesheet: Spritesheet, pub spritesheet: Spritesheet,
texture: DynamicImage pub texture: DynamicImage
} }
// Loads the spritesheet and readies the associated image. /// Loads the spritesheet and readies the associated image.
pub fn load_spritesheet(path: &str) -> LoadedSpritesheet { pub fn load_spritesheet(path: &str) -> LoadedSpritesheet {
return LoadedSpritesheet { return LoadedSpritesheet {
spritesheet: Spritesheet::initialize(plist::from_file(path).expect("could not load plist")), spritesheet: Spritesheet::initialize(plist::from_file(path).expect("could not load plist")),
@ -128,7 +128,7 @@ pub fn load_spritesheet(path: &str) -> LoadedSpritesheet {
} }
} }
// Represents the metadata of an animation frame's sprite /// Represents the metadata of an animation frame's sprite
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AnimationSprite { pub struct AnimationSprite {
pub texture: String, pub texture: String,
@ -194,7 +194,7 @@ pub fn load_animations(path: &str) -> Animations {
return parsed_animations; return parsed_animations;
} }
// Trims out a sprite from an image according to a .plist spritesheet. /// 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)> { pub fn get_sprite(spritesheet: Spritesheet, img: DynamicImage, key: String) -> Option<(DynamicImage, Sprite)> {
let sprite = spritesheet.sprites.get(&key); let sprite = spritesheet.sprites.get(&key);
@ -224,6 +224,7 @@ pub fn get_sprite(spritesheet: Spritesheet, img: DynamicImage, key: String) -> O
unreachable!("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")
} }
/// Trims out a sprite from an image according to a LoadedSpritesheet object.
pub fn get_sprite_from_loaded(spritesheet: LoadedSpritesheet, key: String) -> Option<(DynamicImage, Sprite)> { pub fn get_sprite_from_loaded(spritesheet: LoadedSpritesheet, key: String) -> Option<(DynamicImage, Sprite)> {
let texture = spritesheet.texture.clone(); let texture = spritesheet.texture.clone();
let sprite = get_sprite(spritesheet.spritesheet.clone(), texture, key); let sprite = get_sprite(spritesheet.spritesheet.clone(), texture, key);

View file

@ -2,6 +2,7 @@ use std::{collections::HashMap, sync::LazyLock};
use maplit::hashmap; use maplit::hashmap;
/// Colors used in the game stored as floats from 0 to 1.
pub const COLORS: &'static [[f32; 3]] = &[ pub const COLORS: &'static [[f32; 3]] = &[
[125.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0], [125.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0],
[0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0], [0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0],
@ -47,12 +48,13 @@ pub const COLORS: &'static [[f32; 3]] = &[
[125.0 / 255.0, 125.0 / 255.0, 255.0 / 255.0] [125.0 / 255.0, 125.0 / 255.0, 255.0 / 255.0]
]; ];
// `zany` = uses 2.0 gamemode render system w/ multiple moving parts /// `zany` = uses 2.0 gamemode render system w/ multiple moving parts
pub struct Gamemode { pub struct Gamemode {
pub prefix: String, pub prefix: String,
pub zany: bool pub zany: bool
} }
/// Gamemodes used in the game. `zany` = uses 2.0 gamemode render system w/ multiple moving parts
pub static GAMEMODES: LazyLock<HashMap<&str, Gamemode>> = LazyLock::new(|| { hashmap! { pub static GAMEMODES: LazyLock<HashMap<&str, Gamemode>> = LazyLock::new(|| { hashmap! {
"cube" => Gamemode { prefix: "player_".to_string(), zany: false }, "cube" => Gamemode { prefix: "player_".to_string(), zany: false },
"ship" => Gamemode { prefix: "ship_".to_string(), zany: false }, "ship" => Gamemode { prefix: "ship_".to_string(), zany: false },

View file

@ -1,3 +1,11 @@
//! # gd-icon-renderer
//!
//! gd-icon-renderer is a library for rendering Geometry Dash icons.
//!
//! It uses the [image](https://crates.io/crates/image) crate and [imageproc](https://crates.io/crates/imageproc) crate for image manipulation and [plist](https://crates.io/crates/plist) for parsing plist files.
//!
//! The main entrypoint is found in the [`renderer`](renderer/index.html) module.
#![feature(lazy_cell)] #![feature(lazy_cell)]
pub mod assets; pub mod assets;

View file

@ -7,7 +7,7 @@ use std::cmp;
use crate::assets; use crate::assets;
use crate::assets::{LoadedSpritesheet, Animations, Sprite}; use crate::assets::{LoadedSpritesheet, Animations, Sprite};
// Internal function to easily transform an image /// Internal function to easily transform an image
fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32, f32)>, rotation: Option<f32>) -> DynamicImage { fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32, f32)>, rotation: Option<f32>) -> DynamicImage {
let mut transformed_image = image.clone(); let mut transformed_image = image.clone();
@ -74,7 +74,7 @@ fn transform(image: &DynamicImage, color: Option<[f32; 3]>, scale: Option<(f32,
return transformed_image; return transformed_image;
} }
// Mainly for internal use; given an array of images, their sizes and colors, tints and composits them into a single 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<DynamicImage>, positions: Vec<Option<(f32, f32)>>, colors: Vec<Option<[f32; 3]>>, scales: Vec<Option<(f32, f32)>>, rotations: Vec<Option<f32>>) -> DynamicImage { pub fn render_layered(images: Vec<DynamicImage>, positions: Vec<Option<(f32, f32)>>, colors: Vec<Option<[f32; 3]>>, scales: Vec<Option<(f32, f32)>>, rotations: Vec<Option<f32>>) -> DynamicImage {
let transformed: Vec<DynamicImage> = images.iter().enumerate().map(|(i, img)| { let transformed: Vec<DynamicImage> = images.iter().enumerate().map(|(i, img)| {
transform(img, colors[i], scales[i], rotations[i]) transform(img, colors[i], scales[i], rotations[i])
@ -123,7 +123,7 @@ fn is_black(c: [f32; 3]) -> bool {
c == [0.0, 0.0, 0.0] c == [0.0, 0.0, 0.0]
} }
// Renders out a non-robot/spider icon. You may be looking for `render_icon`. /// Renders out a non-robot/spider icon. You may be looking for `render_icon`.
pub fn render_normal(basename: String, col1: [f32; 3], col2: [f32; 3], glow: bool, game_sheet_02: LoadedSpritesheet, game_sheet_glow: LoadedSpritesheet) -> DynamicImage { pub fn render_normal(basename: String, col1: [f32; 3], col2: [f32; 3], glow: bool, game_sheet_02: LoadedSpritesheet, game_sheet_glow: LoadedSpritesheet) -> DynamicImage {
let glow_col = if is_black(col2) { if is_black(col1) { [1.0, 1.0, 1.0] } else { col1 } } else { col2 }; let glow_col = if is_black(col2) { if is_black(col1) { [1.0, 1.0, 1.0] } else { col1 } } else { col2 };
@ -167,7 +167,7 @@ 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) (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`. /// 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 { 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_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 glow = glow || (is_black(col1) && is_black(col2));
@ -228,8 +228,9 @@ pub fn render_zany(basename: String, col1: [f32; 3], col2: [f32; 3], glow: bool,
) )
} }
// The main entrypoint for icon rendering; this should be all you need to render out an icon. /// 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` ///
/// `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 { 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"); let gamemode = crate::constants::GAMEMODES.get(gamemode_str).expect("invalid gamemode");