diff --git a/script.js b/script.js index 80eacc6..401a089 100644 --- a/script.js +++ b/script.js @@ -6,6 +6,7 @@ import init, { load_game, load_default_game, output, + set_brightness, set_dither, set_room_name, } from './pkg/pixsy.js'; @@ -70,6 +71,7 @@ async function run() { const buttonNewGame = el("new"); const buttonReset = el("reset"); const checkboxDither = el("dither"); + const inputBrightness = el("brightness"); const inputRoomName = el("room-name"); const selectPalette = el("palette"); const textareaGameDataInput = el("game-data"); @@ -171,12 +173,18 @@ async function run() { checkboxDither.addEventListener("change", () => { set_dither(checkboxDither.checked); + loadPreview(); }); inputRoomName.addEventListener("change", () => { set_room_name(inputRoomName.value); }); + inputBrightness.addEventListener("input", () => { + set_brightness(inputBrightness.value); + loadPreview(); + }); + function addRoom() { console.log(add_room()); textareaGameDataOutput.value = output(); diff --git a/src/lib.rs b/src/lib.rs index 168e29b..1268165 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(clamp)] + use bitsy_parser::game::Game; use bitsy_parser::image::Image; use bitsy_parser::tile::Tile; @@ -10,21 +12,23 @@ use image::imageops::dither; const SD: u32 = 8; struct State { - game: Option, - image: Option, - room_name: Option, - palette: Option, - dither: bool, + game: Option, + image: Option, + room_name: Option, + palette: Option, + dither: bool, + brightness: i32, } lazy_static! { static ref STATE: Mutex = Mutex::new( State { - game: None, - image: None, - room_name: None, - palette: None, - dither: true, + game: None, + image: None, + room_name: None, + palette: None, + dither: true, + brightness: 0, } ); } @@ -117,6 +121,12 @@ pub fn set_room_name(room_name: String) { } } +#[wasm_bindgen] +pub fn set_brightness(brightness: i32) { + let mut state = STATE.lock().unwrap(); + state.brightness = brightness; +} + #[wasm_bindgen] pub fn get_palettes() -> String { let state = STATE.lock().unwrap(); @@ -146,15 +156,54 @@ fn image_to_base64(image: &DynamicImage) -> String { format!("data:image/png;base64,{}", base64::encode(&bytes)) } -fn render_preview(image: &DynamicImage) -> DynamicImage { - let image = image.clone(); - let image = image.grayscale(); +fn colour_difference(compare: image::Rgba, other: &bitsy_parser::Colour) -> u32 { + let diff_red = (compare[0] as i16 - other.red as i16).abs(); + let diff_green= (compare[1] as i16 - other.green as i16).abs(); + let diff_blue = (compare[2] as i16 - other.blue as i16).abs(); + + (diff_red + diff_green + diff_blue) as u32 +} + +fn closest_colour(compare: image::Rgba, colours: &[bitsy_parser::Colour]) -> image::Rgba { + let diff_background = colour_difference(compare, &colours[0]); + let diff_foreground = colour_difference(compare, &colours[1]); + + if diff_foreground <= diff_background { + image::Rgba::from([colours[1].red, colours[1].green, colours[1].blue, 255]) + } else { + image::Rgba::from([colours[0].red, colours[0].green, colours[0].blue, 255]) + } +} + +fn adjust_brightness(pixel: &mut image::Rgba, state: &State) -> image::Rgba { + pixel[0] = (pixel[0] as i32 + state.brightness).clamp(0, 255) as u8; + pixel[1] = (pixel[1] as i32 + state.brightness).clamp(0, 255) as u8; + pixel[2] = (pixel[2] as i32 + state.brightness).clamp(0, 255) as u8; + + *pixel +} + +fn render_preview(state: &State) -> DynamicImage { + let image = state.image.as_ref().unwrap().clone(); + let mut preview = image.clone(); // todo dither // todo convert to palette colours - image + // get background and foreground colours from palette + let colours = &state.game.as_ref().unwrap().palettes + .iter() + .find(|palette| &palette.id == state.palette.as_ref().unwrap()) + .unwrap() + .colours[0..2]; + + for (x, y, mut pixel) in image.pixels() { + // is pixel closer to background or foreground? + preview.put_pixel(x, y, closest_colour(adjust_brightness(&mut pixel, &state), colours)); + } + + preview } #[wasm_bindgen] @@ -162,7 +211,7 @@ pub fn get_preview() -> String { let state = STATE.lock().unwrap(); match &state.image.is_some() { - true => image_to_base64(&render_preview(state.image.as_ref().unwrap())), + true => image_to_base64(&render_preview(&state)), false => "".to_string(), } } @@ -238,6 +287,7 @@ pub fn output() -> String { #[cfg(test)] mod test { use crate::{add_room, load_image, load_default_game, output, get_preview}; + use image::Rgba; #[test] fn image_to_base64() { @@ -247,6 +297,70 @@ mod test { assert_eq!(output, expected); } + #[test] + fn colour_difference_none() { + let output = crate::colour_difference( + Rgba::from([255; 4]), + &bitsy_parser::Colour { + red: 255, + green: 255, + blue: 255 + } + ); + assert_eq!(output, 0); + } + + #[test] + fn colour_difference_some() { + let output = crate::colour_difference( + Rgba::from([255; 4]), + &bitsy_parser::Colour { + red: 254, + green: 255, + blue: 255 + } + ); + assert_eq!(output, 1); + } + + #[test] + fn colour_difference_some_2() { + let output = crate::colour_difference( + Rgba::from([254; 4]), + &bitsy_parser::Colour { + red: 254, + green: 255, + blue: 254 + } + ); + assert_eq!(output, 1); + } + + #[test] + fn colour_difference_max() { + let expected = 255 * 3; + + let output = crate::colour_difference( + Rgba::from([0; 4]), + &bitsy_parser::Colour { + red: 255, + green: 255, + blue: 255 + } + ); + assert_eq!(output, expected); + + let output = crate::colour_difference( + Rgba::from([255; 4]), + &bitsy_parser::Colour { + red: 0, + green: 0, + blue: 0 + } + ); + assert_eq!(output, expected); + } + #[test] fn get_palettes() { load_default_game();