diff --git a/.gitignore b/.gitignore index 6daf201..7943bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ itch* .idea /Cargo.lock +/dist/ diff --git a/Cargo.toml b/Cargo.toml index a9c3136..7881897 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pixsy" -version = "0.72.0" +version = "0.72.3" description = "convert images to Bitsy rooms" authors = ["Max Bradbury "] edition = "2018" @@ -8,10 +8,11 @@ license = "MIT" repository = "https://tinybird.dev/max/image-to-bitsy" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [dependencies] -"base64" = "^0.13.0" +"base64" = "^0.12.3" "bitsy-parser" = "^0.72.3" -"image" = "^0.23.11" -"wasm-bindgen" = "=0.2.65" # because later versions are currently broken +"image" = "^0.23.7" +"lazy_static" = "^1.4.0" +"wasm-bindgen" = "=0.2.64" # newer versions are bugged... diff --git a/deploy.sh b/deploy.sh index 23ee025..e38a15e 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,7 @@ #! /usr/bin/env bash ./build.sh -# todo create dist directory instead of zip -zip -r tilesy.zip README.md index.html script.js background.png pkg/ includes/ -butler push tilesy.zip ruin/image-to-bitsy:html +rm -rf dist +mkdir dist +cp -r README.md LICENSE index.html script.js background.png pkg includes dist +butler push dist ruin/image-to-bitsy:html diff --git a/script.js b/script.js index c8ec306..d292584 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,19 @@ -import init, {load_default_game_data, add_tiles} from './pkg/pixsy.js'; +import init, { + add_tiles, + load_image, + load_game, + load_default_game, + output, + set_prefix, + set_invert, + set_flip, + set_mirror, + set_rotate +} from './pkg/pixsy.js'; + +if (typeof WebAssembly !== "object") { + document.getElementById("start").innerText = "Sorry - your browser does not support WebAssembly"; +} // stolen from https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server function download(filename, text) { @@ -75,7 +90,8 @@ async function run() { } function new_game() { - textareaGameDataInput.value = load_default_game_data(); + load_default_game() + textareaGameDataInput.value = output(); checkGameData(); // we don't need to look at the default game data, so skip ahead to the image page buttonGameDataProceed.click(); @@ -96,6 +112,7 @@ async function run() { el("game").addEventListener("change", function() { readFile(this, function (e) { textareaGameDataInput.value = e.target.result; + console.log(load_game(e.target.result)); checkGameData(); }, "text"); }); @@ -117,24 +134,14 @@ async function run() { el('image').addEventListener('change', function () { readFile(this, function (e) { imagePreview.src = e.target.result; - imagePreview.style.display = "initial" + imagePreview.style.display = "initial"; + console.log(load_image(imagePreview.getAttribute("src"))); }, "image"); }); function addTiles() { - let image = imagePreview.getAttribute("src"); - let gameData = textareaGameDataInput.value; - let prefix = inputPrefix.value; - - textareaGameDataOutput.value = add_tiles( - gameData, - image, - prefix, - checkboxInvertTiles.checked, - checkboxFlipTiles.checked, - checkboxMirrorTiles.checked, - checkboxRotateTiles.checked - ); + console.log(add_tiles()); + textareaGameDataOutput.value = output(); } buttonImportGame.addEventListener("click", addTiles); @@ -156,6 +163,26 @@ async function run() { buttonAddImage.addEventListener("click", addImage); buttonAddImage.addEventListener("touchend", addImage); + inputPrefix.addEventListener("change", () => { + set_prefix(inputPrefix.value); + }) + + checkboxInvertTiles.addEventListener("change", () => { + set_invert(checkboxInvertTiles.checked); + }); + + checkboxFlipTiles.addEventListener("change", () => { + set_flip(checkboxFlipTiles.checked); + }); + + checkboxMirrorTiles.addEventListener("change", () => { + set_mirror(checkboxMirrorTiles.checked); + }); + + checkboxRotateTiles.addEventListener("change", () => { + set_rotate(checkboxRotateTiles.checked); + }); + function reset() { clear_game(); imagePreview.removeAttribute("src"); diff --git a/src/lib.rs b/src/lib.rs index a7316bf..f060e5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,35 @@ use bitsy_parser::game::Game; use bitsy_parser::image::Image; use bitsy_parser::tile::Tile; -use image::{GenericImageView, Pixel}; +use image::{GenericImageView, Pixel, DynamicImage}; +use lazy_static::lazy_static; +use std::sync::Mutex; use wasm_bindgen::prelude::*; const SD: u32 = 8; -#[wasm_bindgen] -pub fn load_default_game_data() -> String { - bitsy_parser::mock::game_default().to_string() +struct State { + game: Option, + image: Option, + prefix: String, + invert: bool, + flip: bool, + mirror: bool, + rotate: bool, +} + +lazy_static! { + static ref STATE: Mutex = Mutex::new( + State { + game: None, + image: None, + prefix: "".to_string(), + invert: false, + flip: false, + mirror: false, + rotate: false, + } + ); } fn tile_name(prefix: &str, index: &u32) -> Option { @@ -19,40 +40,108 @@ fn tile_name(prefix: &str, index: &u32) -> Option { } } -/// image is a base64-encoded string +#[wasm_bindgen] +pub fn load_default_game() { + let mut state = STATE.lock().unwrap(); + state.game = Some(bitsy_parser::mock::game_default()); +} + +#[wasm_bindgen] +pub fn load_game(game_data: String) -> String { + let result = Game::from(game_data); + let mut state = STATE.lock().unwrap(); + + match result { + Ok(game) => { + state.game = Some(game); + "".to_string() + }, + _ => { + state.game = None; + format!("{}", result.err().unwrap()) + } + } +} + +#[wasm_bindgen] +pub fn load_image(image_base64: String) -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); + + let image_base64: Vec<&str> = image_base64.split("base64,").collect(); + let image_base64 = image_base64[1]; + + match base64::decode(image_base64) { + Ok(image) => { + match image::load_from_memory(image.as_ref()) { + Ok(image) => { + state.image = Some(image); + "OK" + }, + _ => { + state.image = None; + "Couldn't load image" + } + } + }, + _ => { + state.image = None; + "Couldn't decode image" + } + }.to_string() +} + +#[wasm_bindgen] +pub fn set_prefix(prefix: String) { + let mut state = STATE.lock().unwrap(); + + state.prefix = prefix; +} + +#[wasm_bindgen] +pub fn set_invert(invert: bool) { + let mut state = STATE.lock().unwrap(); + + state.invert = invert; +} + +#[wasm_bindgen] +pub fn set_flip(flip: bool) { + let mut state = STATE.lock().unwrap(); + + state.flip = flip; +} + +#[wasm_bindgen] +pub fn set_mirror(mirror: bool) { + let mut state = STATE.lock().unwrap(); + + state.mirror = mirror; +} + +#[wasm_bindgen] +pub fn set_rotate(rotate: bool) { + let mut state = STATE.lock().unwrap(); + + state.rotate = rotate; +} + /// prefix will be ignored if empty #[wasm_bindgen] -pub fn add_tiles( - game_data: String, - image: String, - prefix: String, - invert: bool, - flip: bool, - mirror: bool, - rotate: bool -) -> String { - let game = Game::from(game_data); - if game.is_err() { - return format!("Couldn't parse game data"); - } - let mut game = game.unwrap(); +pub fn add_tiles() -> String { + let mut state = STATE.lock().expect("Couldn't lock application state"); - let image: Vec<&str> = image.split("base64,").collect(); - let image = image[1]; - - let image = base64::decode(image); - if image.is_err() { - return format!("Couldn't decode image"); + if state.game.is_none() { + return "No game data loaded".to_string(); } - let image = image.unwrap(); - let image = image::load_from_memory(image.as_ref()); - if image.is_err() { - return format!("Couldn't load image"); - } - let image = image.unwrap(); - let width = image.width(); - let height = image.height(); + let mut game = state.game.clone().unwrap(); + + if state.image.is_none() { + return "No image loaded".to_string(); + } + + let width = state.image.as_ref().unwrap().width(); + let height = state.image.as_ref().unwrap().height(); let columns = (width as f64 / SD as f64).floor() as u32; let rows = (height as f64 / SD as f64).floor() as u32; @@ -64,7 +153,7 @@ pub fn add_tiles( for y in (row * SD)..((row + 1) * SD) { for x in (column * SD)..((column + 1) * SD) { - let pixel = image.get_pixel(x, y).to_rgb(); + let pixel = state.image.as_ref().unwrap().get_pixel(x, y).to_rgb(); let total = pixel[0] as u32 + pixel[1] as u32 + pixel[2] as u32; // is each channel brighter than 128/255 on average? pixels.push(if total >= 384 {1} else {0}); @@ -72,9 +161,9 @@ pub fn add_tiles( } let tile = Tile { - /// "0" will get overwritten to a new, safe tile ID + // "0" will get overwritten to a new, safe tile ID id: "0".to_string(), - name: tile_name(&prefix, &tile_index), + name: tile_name(&state.prefix, &tile_index), wall: None, animation_frames: vec![Image { pixels }], colour_id: None @@ -86,7 +175,7 @@ pub fn add_tiles( tile_index += 1; } - if invert { + if state.invert { let mut inverted = tile.clone(); inverted.invert(); @@ -99,7 +188,7 @@ pub fn add_tiles( } } - if flip { + if state.flip { let mut flipped = tile.clone(); flipped.flip(); @@ -112,7 +201,7 @@ pub fn add_tiles( } } - if mirror { + if state.mirror { let mut mirrored = tile.clone(); mirrored.mirror(); @@ -125,7 +214,7 @@ pub fn add_tiles( } } - if rotate { + if state.rotate { for i in 1..4 { let mut rotated = tile.clone(); @@ -146,29 +235,32 @@ pub fn add_tiles( } game.dedupe_tiles(); - game.to_string() + + state.game = Some(game.to_owned()); + + "OK".to_string() +} + +#[wasm_bindgen] +pub fn output() -> String { + let state = STATE.lock().unwrap(); + + match &state.game { + Some(game) => game.to_string(), + None => "No game loaded".to_string(), + } } #[cfg(test)] mod test { - use crate::add_tiles; + use crate::{add_tiles, load_image, load_default_game, output}; #[test] fn example() { - let game_data = bitsy_parser::mock::game_default().to_string(); - let image = include_str!("test-resources/test.png.base64").to_string(); + load_default_game(); + load_image(include_str!("test-resources/test.png.base64").to_string()); + add_tiles(); - let output = add_tiles( - game_data, - image, - "".to_string(), - false, - false, - false, - false - ); - - let expected = include_str!("test-resources/expected.bitsy"); - assert_eq!(output, expected); + assert_eq!(output(), include_str!("test-resources/expected.bitsy")); } }