#![crate_name = "sprite_gen"]
use hsl::HSL;
use randomize::{formulas, PCG32};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MaskValue {
Solid,
Empty,
Body1,
Body2,
}
impl MaskValue {
pub fn i8(&self) -> i8 {
match self {
MaskValue::Solid => -1,
MaskValue::Empty => 0,
MaskValue::Body1 => 1,
MaskValue::Body2 => 2,
}
}
}
impl From<MaskValue> for i8 {
fn from(from: MaskValue) -> Self {
from.i8()
}
}
impl From<i8> for MaskValue {
fn from(from: i8) -> Self {
match from {
-1 => MaskValue::Solid,
1 => MaskValue::Body1,
2 => MaskValue::Body2,
_ => MaskValue::Empty,
}
}
}
impl Default for MaskValue {
fn default() -> Self {
MaskValue::Empty
}
}
#[derive(Debug, Copy, Clone)]
pub struct Options {
pub mirror_x: bool,
pub mirror_y: bool,
pub colored: bool,
pub edge_brightness: f32,
pub color_variations: f32,
pub brightness_noise: f32,
pub saturation: f32,
pub seed: u64,
}
impl Default for Options {
fn default() -> Self {
Options {
mirror_x: false,
mirror_y: false,
colored: true,
edge_brightness: 0.3,
color_variations: 0.2,
brightness_noise: 0.3,
saturation: 0.5,
seed: 0,
}
}
}
pub fn gen_sprite<T>(mask_buffer: &[T], mask_width: usize, options: Options) -> Vec<u32>
where
T: Into<i8> + Clone,
{
let mask_height = mask_buffer.len() / mask_width;
let mut mask: Vec<i8> = mask_buffer
.iter()
.map(|v| std::convert::Into::into(v.clone()))
.collect::<_>();
let mut rng = PCG32::seed(options.seed, 5);
for val in mask.iter_mut() {
if *val == 1 {
*val = formulas::f32_closed(rng.next_u32()).round() as i8;
} else if *val == 2 {
*val = formulas::f32_closed_neg_pos(rng.next_u32()).signum() as i8;
}
}
for y in 0..mask_height {
for x in 0..mask_width {
let index = x + y * mask_width;
if mask[index] <= 0 {
continue;
}
if y > 0 && mask[index - mask_width] == 0 {
mask[index - mask_width] = -1;
}
if y < mask_height - 1 && mask[index + mask_width] == 0 {
mask[index + mask_width] = -1;
}
if x > 0 && mask[index - 1] == 0 {
mask[index - 1] = -1;
}
if x < mask_width - 1 && mask[index + 1] == 0 {
mask[index + 1] = -1;
}
}
}
let colored: Vec<u32> = if options.colored {
color_output(&mask, (mask_width, mask_height), &options, &mut rng)
} else {
onebit_output(&mask)
};
if options.mirror_x && options.mirror_y {
let width = mask_width * 2;
let height = mask_height * 2;
let mut result = vec![0; width * height];
for y in 0..mask_height {
for x in 0..mask_width {
let index = x + y * mask_width;
let value = colored[index];
let index = x + y * width;
result[index] = value;
let index = (width - x - 1) + y * width;
result[index] = value;
let index = x + (height - y - 1) * width;
result[index] = value;
let index = (width - x - 1) + (height - y - 1) * width;
result[index] = value;
}
}
return result;
} else if options.mirror_x {
let width = mask_width * 2;
let mut result = vec![0; width * mask_height];
for y in 0..mask_height {
for x in 0..mask_width {
let index = x + y * mask_width;
let value = colored[index];
let index = x + y * width;
result[index] = value;
let index = (width - x - 1) + y * width;
result[index] = value;
}
}
return result;
} else if options.mirror_y {
let height = mask_height * 2;
let mut result = vec![0; mask_width * height];
for y in 0..mask_height {
for x in 0..mask_width {
let index = x + y * mask_width;
let value = colored[index];
result[index] = value;
let index = x + (height - y - 1) * mask_width;
result[index] = value;
}
}
return result;
}
colored
}
#[inline]
fn onebit_output(mask: &[i8]) -> Vec<u32> {
mask.iter()
.map(|&v| match v {
-1 => 0,
_ => 0xFF_FF_FF_FF,
})
.collect()
}
#[inline]
fn color_output(
mask: &[i8],
mask_size: (usize, usize),
options: &Options,
rng: &mut PCG32,
) -> Vec<u32> {
let mut result = vec![0xFF_FF_FF_FF; mask.len()];
let is_vertical_gradient = formulas::f32_closed_neg_pos(rng.next_u32()) > 0.0;
let saturation = formulas::f32_closed(rng.next_u32()) * options.saturation;
let mut hue = formulas::f32_closed(rng.next_u32());
let variation_check = 1.0 - options.color_variations;
let brightness_inv = 1.0 - options.brightness_noise;
let uv_size = if is_vertical_gradient {
(mask_size.1, mask_size.0)
} else {
mask_size
};
for u in 0..uv_size.0 {
let is_new_color = (formulas::f32_closed(rng.next_u32())
+ formulas::f32_closed(rng.next_u32())
+ formulas::f32_closed(rng.next_u32()))
/ 3.0;
if is_new_color > variation_check {
hue = formulas::f32_closed(rng.next_u32());
}
let u_sin = ((u as f32 / uv_size.0 as f32) * std::f32::consts::PI).sin();
for v in 0..uv_size.1 {
let index = if is_vertical_gradient {
v + u * mask_size.0
} else {
u + v * mask_size.0
};
let val = mask[index];
if val == 0 {
continue;
}
let brightness = u_sin * brightness_inv
+ formulas::f32_closed(rng.next_u32()) * options.brightness_noise;
let mut rgb = HSL {
h: hue as f64 * 360.0,
s: saturation as f64,
l: brightness as f64,
}
.to_rgb();
if val == -1 {
rgb.0 = (rgb.0 as f32 * options.edge_brightness) as u8;
rgb.1 = (rgb.1 as f32 * options.edge_brightness) as u8;
rgb.2 = (rgb.2 as f32 * options.edge_brightness) as u8;
}
result[index] = ((rgb.0 as u32) << 16) | ((rgb.1 as u32) << 8) | (rgb.2 as u32);
}
}
result
}