diff --git a/Cargo.toml b/Cargo.toml index 7881897..caf9afa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ crate-type = ["cdylib"] [dependencies] "base64" = "^0.12.3" "bitsy-parser" = "^0.72.3" +"dither" = "1.3.9" "image" = "^0.23.7" +"json" = "^0.12.4" "lazy_static" = "^1.4.0" "wasm-bindgen" = "=0.2.64" # newer versions are bugged... diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..18682bc --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +# todo + +* preview +* dithering +* palette selection +* palette dropdown +* unit test +* if uploaded image is exactly 128×128, *don't* crop diff --git a/deploy.sh b/deploy.sh index e38a15e..b21127a 100644 --- a/deploy.sh +++ b/deploy.sh @@ -4,4 +4,4 @@ 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 +# butler push dist ruin/pixsy:html diff --git a/includes/style.less b/includes/style.less index 748f62b..b55b4be 100644 --- a/includes/style.less +++ b/includes/style.less @@ -58,7 +58,6 @@ input { } img { - max-height: 12em; max-width: 100%; margin: 0; } @@ -73,6 +72,10 @@ label { font-weight: bold; } +select { + width: 100%; +} + textarea { height: 15em; padding: 0.5em; @@ -94,6 +97,12 @@ textarea { margin-top: 0; } +.half { + display: inline-block; + text-align: left; + width: 50%; +} + .image-container { height: 46vh; text-align: left; @@ -114,3 +123,10 @@ textarea { #crop canvas { height: 32vh; } + +#preview { + width: 256px; + height: 256px; + image-rendering: pixelated; + image-rendering: crisp-edges; +} diff --git a/index.pug b/index.pug index ce491fd..ca35914 100644 --- a/index.pug +++ b/index.pug @@ -24,34 +24,63 @@ html(lang="en-gb") button.normal.pagination.next#load load an existing bitsy game .page.game-data h2 game data + input#game(type="file" autocomplete="off") br + textarea#game-data( placeholder="Paste your game data here or use the file chooser button above" autocomplete="off" ) + button.pagination.prev previous button.pagination.next#game-data-next(disabled=true) next .page.image h2 image + .image-container input#image(type="file" accept="image/*") #crop + button.pagination.prev previous - button.pagination.next next - .page.extras - h2 tiles - label - | tile name (optional) - input#prefix(type="text" placeholder="e.g. 'forest'" autocomplete="off") + button.pagination.next#image-next(disabled=true) next + .page.room + h2 room + + table + tbody + tr + td(style="width: 60%") + img#preview(alt="preview") + br + + label + | brightness + input#brightness(type="range" min=-64 max=64 value=0) + td + label + | palette + select#palette + + label + input#dither(type="checkbox") + | dither + br + + label + | name (optional) + input#room-name(type="text" placeholder="e.g. 'bedroom'" autocomplete="off") button.pagination.prev#back-to-image previous - button.pagination.next#import next + button.pagination.next#room-next next .page.download h2 download + textarea#output(autocomplete="off") br + button#download download + button.pagination.prev#add add another image button.pagination.start#reset start again script(type="module") diff --git a/script.js b/script.js index fa75634..80eacc6 100644 --- a/script.js +++ b/script.js @@ -1,14 +1,17 @@ import init, { add_room, + get_palettes, + get_preview, load_image, load_game, load_default_game, output, + set_dither, set_room_name, } from './pkg/pixsy.js'; if (typeof WebAssembly !== "object") { - document.getElementById("start").innerText = "Sorry - your browser does not support WebAssembly"; + window.location = "./old/" } // 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 @@ -61,10 +64,14 @@ async function run() { const buttonBackToImage = el("back-to-image"); const buttonDownload = el("download"); const buttonGameDataProceed = el("game-data-next"); - const buttonImportGame = el("import"); + const buttonImageProceed = el("image-next"); + const buttonRoomProceed = el("room-next"); const buttonLoadGame = el("load"); const buttonNewGame = el("new"); const buttonReset = el("reset"); + const checkboxDither = el("dither"); + const inputRoomName = el("room-name"); + const selectPalette = el("palette"); const textareaGameDataInput = el("game-data"); const textareaGameDataOutput = el("output"); @@ -110,9 +117,25 @@ async function run() { }, "text"); }); + function setPaletteDropdown() { + let palettes = JSON.parse(get_palettes()); + + selectPalette.innerHTML = ""; + + for (let palette of palettes) { + let option = document.createElement("option"); + + option.value = palette.id; + option.innerText = palette.name; + + selectPalette.appendChild(option); + } + } + function checkGameData() { if (textareaGameDataInput.value.length > 0) { buttonGameDataProceed.removeAttribute("disabled"); + setPaletteDropdown(); } else { buttonGameDataProceed.setAttribute("disabled", "disabled"); } @@ -127,19 +150,40 @@ async function run() { if ( ! cropperRendered) { cropper.render("#crop"); cropperRendered = true; + buttonImageProceed.removeAttribute("disabled"); } cropper.loadImage(e.target.result); }, "image"); }); - function addTiles() { - console.log(add_tiles()); + function loadPreview() { + el("preview").setAttribute("src", get_preview()); + } + + function handleImage() { + console.log(load_image(cropper.getCroppedImage())); + loadPreview(); + } + + buttonImageProceed.addEventListener("click", handleImage); + buttonImageProceed.addEventListener("touchend", handleImage); + + checkboxDither.addEventListener("change", () => { + set_dither(checkboxDither.checked); + }); + + inputRoomName.addEventListener("change", () => { + set_room_name(inputRoomName.value); + }); + + function addRoom() { + console.log(add_room()); textareaGameDataOutput.value = output(); } - buttonImportGame.addEventListener("click", addTiles); - buttonImportGame.addEventListener("touchend", addTiles); + buttonRoomProceed.addEventListener("click", addRoom); + buttonRoomProceed.addEventListener("touchend", addRoom); function handleDownload() { download("output.bitsy", textareaGameDataOutput.value); diff --git a/src/lib.rs b/src/lib.rs index f56f6d7..168e29b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use image::{GenericImageView, Pixel, DynamicImage}; use lazy_static::lazy_static; use std::sync::Mutex; use wasm_bindgen::prelude::*; +use image::imageops::dither; const SD: u32 = 8; @@ -13,6 +14,7 @@ struct State { image: Option, room_name: Option, palette: Option, + dither: bool, } lazy_static! { @@ -22,6 +24,7 @@ lazy_static! { image: None, room_name: None, palette: None, + dither: true, } ); } @@ -38,6 +41,7 @@ fn tile_name(prefix: &Option, index: &u32) -> Option { pub fn load_default_game() { let mut state = STATE.lock().unwrap(); state.game = Some(bitsy_parser::mock::game_default()); + state.palette = Some(bitsy_parser::mock::game_default().palette_ids()[0].clone()) } #[wasm_bindgen] @@ -47,11 +51,14 @@ pub fn load_game(game_data: String) -> String { match result { Ok(game) => { - state.game = Some(game); + let palette_id = game.palette_ids()[0].clone(); + state.game = Some(game); + state.palette = Some(palette_id); "".to_string() }, _ => { - state.game = None; + state.game = None; + state.palette = None; format!("{}", result.err().unwrap()) } } @@ -84,6 +91,22 @@ pub fn load_image(image_base64: String) -> String { }.to_string() } +#[wasm_bindgen] +pub fn set_dither(dither: bool) { + let mut state = STATE.lock().unwrap(); + state.dither = dither; +} + +#[wasm_bindgen] +pub fn set_palette(palette_id: String) { + let mut state = STATE.lock().unwrap(); + + match palette_id.is_empty() { + true => { state.palette = None }, + false => { state.palette = Some(palette_id) }, + } +} + #[wasm_bindgen] pub fn set_room_name(room_name: String) { let mut state = STATE.lock().unwrap(); @@ -95,12 +118,52 @@ pub fn set_room_name(room_name: String) { } #[wasm_bindgen] -pub fn set_palette(palette_id: String) { - let mut state = STATE.lock().unwrap(); +pub fn get_palettes() -> String { + let state = STATE.lock().unwrap(); - match palette_id.is_empty() { - true => { state.palette = None }, - false => { state.palette = Some(palette_id) }, + let mut palette_objects = json::JsonValue::new_array(); + + for palette in &state.game.as_ref().unwrap().palettes { + let mut object = json::JsonValue::new_object(); + + object.insert("id", palette.id.clone()).unwrap(); + + object.insert( + "name", + palette.name.clone().unwrap_or( + format!("Palette {}", palette.id)) + ).unwrap(); + + palette_objects.push(object).unwrap(); + } + + json::stringify(palette_objects) +} + +fn image_to_base64(image: &DynamicImage) -> String { + let mut bytes: Vec = Vec::new(); + image.write_to(&mut bytes, image::ImageOutputFormat::Png).unwrap(); + format!("data:image/png;base64,{}", base64::encode(&bytes)) +} + +fn render_preview(image: &DynamicImage) -> DynamicImage { + let image = image.clone(); + let image = image.grayscale(); + + // todo dither + + // todo convert to palette colours + + image +} + +#[wasm_bindgen] +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())), + false => "".to_string(), } } @@ -174,13 +237,38 @@ pub fn output() -> String { #[cfg(test)] mod test { - use crate::{add_tiles, load_image, load_default_game, output}; + use crate::{add_room, load_image, load_default_game, output, get_preview}; + + #[test] + fn image_to_base64() { + let image = image::load_from_memory(include_bytes!("test-resources/test.png")).unwrap(); + let output = crate::image_to_base64(&image); + let expected = include_str!("test-resources/test.png.base64").trim(); + assert_eq!(output, expected); + } + + #[test] + fn get_palettes() { + load_default_game(); + let output = crate::get_palettes(); + let expected = "[{\"id\":\"0\",\"name\":\"blueprint\"}]"; + assert_eq!(output, expected); + } + + #[test] + fn render_preview() { + load_default_game(); + load_image(include_str!("test-resources/colour_input.png.base64").trim().to_string()); + let output = get_preview(); + let expected = include_str!("test-resources/colour_input.png.base64.greyscale").trim(); + assert_eq!(output, expected); + } #[test] fn example() { load_default_game(); load_image(include_str!("test-resources/test.png.base64").to_string()); - add_tiles(); + add_room(); assert_eq!(output(), include_str!("test-resources/expected.bitsy")); } diff --git a/src/test-resources/colour_input.png b/src/test-resources/colour_input.png new file mode 100644 index 0000000..227c1a3 Binary files /dev/null and b/src/test-resources/colour_input.png differ diff --git a/src/test-resources/colour_input.png.base64 b/src/test-resources/colour_input.png.base64 new file mode 100644 index 0000000..7321a57 --- /dev/null +++ b/src/test-resources/colour_input.png.base64 @@ -0,0 +1 @@ +data:image/png;base64,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 diff --git a/src/test-resources/colour_input.png.base64.greyscale b/src/test-resources/colour_input.png.base64.greyscale new file mode 100644 index 0000000..e6383ce --- /dev/null +++ b/src/test-resources/colour_input.png.base64.greyscale @@ -0,0 +1 @@ +data:image/png;base64,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 diff --git a/src/test-resources/expected.bitsy b/src/test-resources/expected.bitsy new file mode 100644 index 0000000..1bbfbd3 --- /dev/null +++ b/src/test-resources/expected.bitsy @@ -0,0 +1,136 @@ +Write your game's title here + +# BITSY VERSION 7.2 + +! ROOM_FORMAT 1 + +PAL 0 +NAME blueprint +0,82,204 +128,159,255 +255,255,255 + +ROOM 0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0 +0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0 +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +NAME example room +PAL 0 + +TIL a +11111111 +10000001 +10000001 +10011001 +10011001 +10000001 +10000001 +11111111 +NAME block + +TIL 2 +00011000 +00111000 +00011000 +00011000 +00011000 +00011000 +00011000 +00111100 + +TIL 3 +00111100 +01100110 +01100110 +00001100 +00011000 +00110000 +01100000 +01111110 + +TIL 4 +00111100 +01100110 +01100110 +00001100 +00001100 +01100110 +01100110 +00111100 + +SPR A +00011000 +00011000 +00011000 +00111100 +01111110 +10111101 +00100100 +00100100 +POS 0 4,4 + +SPR a +00000000 +00000000 +01010001 +01110001 +01110010 +01111100 +00111100 +00100100 +NAME cat +DLG 0 +POS 0 8,12 + +ITM 0 +00000000 +00000000 +00000000 +00111100 +01100100 +00100100 +00011000 +00000000 +NAME tea +DLG 1 + +ITM 1 +00000000 +00111100 +00100100 +00111100 +00010000 +00011000 +00010000 +00011000 +NAME key +DLG 2 + +DLG 0 +I'm a cat +NAME cat dialog + +DLG 1 +You found a nice warm cup of tea +NAME tea dialog + +DLG 2 +A key! {wvy}What does it open?{wvy} +NAME key dialog + +VAR a +42 + diff --git a/src/test-resources/test.png b/src/test-resources/test.png new file mode 100644 index 0000000..63df3da Binary files /dev/null and b/src/test-resources/test.png differ diff --git a/src/test-resources/test.png.base64 b/src/test-resources/test.png.base64 new file mode 100644 index 0000000..4c542ca --- /dev/null +++ b/src/test-resources/test.png.base64 @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAQCAYAAACm53kpAAAAvElEQVR4nO2QQQrDMBAD6/8/upVSBMqClab2IWAP2M1qtgmovcFrgNYa7i96lWdO8nKi7oz6HkcBvWUo3FgKXo7PQpmTvJzy2XNiWgGEM/HM6fmaz54TpwLwiBvhjVnPhDPxzLny5Gpn1FceVcCoJ7/sOKcCKlC4sRS8O87EMyf55Ejy1dU58YgCerm46+ucOArA79/oI/U1ykXy1QntXHlSd9wluHX+52LsAnB2ATjLsgvA2QXgLMvyBXwAjqzoAQg4VfAAAAAASUVORK5CYII=