Compare commits

..

19 Commits

Author SHA1 Message Date
Max Bradbury 7c444cb35d add support for Bitsy 7.10; turn dithering off by default; more help text 2021-11-06 11:15:34 +00:00
Max Bradbury a2c92b1e12 move deprecated function call to replacement function 2021-07-03 11:31:06 +01:00
Max Bradbury 57b841ac3d add clipboard button 2021-07-03 11:29:59 +01:00
Max Bradbury 3bbf51e5f8 rename download to "done!" 2021-07-03 11:29:26 +01:00
Max Bradbury 7af54d938b things are ok 2021-07-03 11:28:52 +01:00
Max Bradbury f224fe1e27 remove console log 2021-07-03 11:28:33 +01:00
Max Bradbury e6fda7266e version update 2021-07-03 11:28:12 +01:00
Max Bradbury f223e51195 version update 2021-07-03 11:27:58 +01:00
Max Bradbury d72a4df55e remove unused image header 2021-07-03 11:27:27 +01:00
Max Bradbury 0f23b878b2 version update 2021-05-02 11:47:10 +01:00
Max Bradbury 23b99b8ea7 fix loading game from textarea (copy/paste) 2021-05-02 11:45:06 +01:00
Max Bradbury c8b6c772ab remove debug 2021-04-25 18:49:00 +01:00
Max Bradbury 52728e601b remove unnecessary trailing semicolon 2021-04-25 17:47:39 +01:00
Max Bradbury 4bd6286cf0 replace `to_string` with `into` for brevity 2021-04-25 17:47:25 +01:00
Max Bradbury d53244a884 this is no longer a special feature 2021-04-25 17:46:52 +01:00
Max Bradbury f6678f28a9 fix this old broken test 2021-04-25 17:46:25 +01:00
Max Bradbury b157007ff3 restrict file types and add help text about game data 2020-11-28 18:33:11 +00:00
Max Bradbury 704f710047 fix syntax 2020-11-28 15:57:19 +00:00
Max Bradbury 5c4259d222 better splash page for now 2020-11-28 15:15:40 +00:00
8 changed files with 81 additions and 87 deletions

View File

@ -1,20 +1,19 @@
[package] [package]
name = "pixsy" name = "pixsy"
version = "0.72.7" version = "0.710.0"
description = "convert images to Bitsy rooms" description = "convert images to Bitsy rooms"
authors = ["Max Bradbury <max@tinybird.info>"] authors = ["Max Bradbury <max@tinybird.info>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
repository = "https://tinybird.dev/max/image-to-bitsy" repository = "https://tinybird.dev/max/pixsy"
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
base64 = "^0.12.3" base64 = "^0.12.3"
bitsy-parser = "^0.72.5" bitsy-parser = "^0.710.0"
dither = "^1.3.9"
image = "^0.23.7" image = "^0.23.7"
json = "^0.12.4" json = "^0.12.4"
lazy_static = "^1.4.0" lazy_static = "^1.4.0"
wasm-bindgen = "=0.2.64" # newer versions are bugged... wasm-bindgen = "^0.2.78"

View File

@ -22,6 +22,11 @@ button {
white-space: nowrap; white-space: nowrap;
width: 100%; width: 100%;
&.half {
float: left;
width: 50%;
}
&.pagination:not(.normal) { &.pagination:not(.normal) {
position: absolute; position: absolute;
bottom: 5vmin; bottom: 5vmin;

View File

@ -8,11 +8,9 @@ html(lang="en-gb")
script(src="includes/croppie.min.js") script(src="includes/croppie.min.js")
body body
header header
h1 h1 pixsy
| pixsy
//img(alt="pixsy" src="includes/pixsy.png")
p. p.
convert images to Bitsy rooms version 0.710.0
| |
#[a(href="http://tinybird.info/image-to-bitsy/old/" target="_blank") old version] #[a(href="http://tinybird.info/image-to-bitsy/old/" target="_blank") old version]
| |
@ -21,12 +19,35 @@ html(lang="en-gb")
#[a(href="https://twitter.com/synth_ruiner") twitter] #[a(href="https://twitter.com/synth_ruiner") twitter]
.pages .pages
.page#start .page#start
p.
#[b pixsy] is a tool for #[a(href="https://bitsy.org/") Bitsy Game Maker]
that allows you to generate a room from an image and add it to your game.
p.
this version is tested to be compatible with Bitsy version 7.10 and earlier.
later versions may also work fine, but make sure you have a backup of your game data.
p.
#[b pixsy] does not currently work via the Itch desktop program.
if pixsy does not work for you, please try the
#[a(href="http://tinybird.info/image-to-bitsy/old/") old version] instead.
p.
if your image is already the correct size for a bitsy room (128×128),
simply leave the zoom slider at the default setting.
you can draw your room in a pixel-art program and import it here.
p.
full instructions can be found on the
#[a(href="https://ruin.itch.io/pixsy/") itch.io page] -
scroll down to "how to use".
button.normal.pagination.next#new create a new bitsy game button.normal.pagination.next#new create a new bitsy game
button.normal.pagination.next#load load an existing bitsy game button.normal.pagination.next#load load an existing bitsy game
.page.game-data .page.game-data
h2 game data h2 game data
input#game(type="file" autocomplete="off") p.
your game data is available from the #[i game data] window in bitsy,
under the #[i tools] dropdown.
input#game(type="file" accept=".bitsy,.txt" autocomplete="off")
br br
textarea#game-data( textarea#game-data(
@ -74,24 +95,22 @@ html(lang="en-gb")
input#colour-foreground(type="color" value="#ffffff") input#colour-foreground(type="color" value="#ffffff")
label label
input#dither(type="checkbox")
| dither | dither
select#dither p (approximates a greyscale effect)
option None br
option(value="Atkinson" selected=true) Atkinson
option(value="Bayer4x4") Bayer 4×4
option(value="FloydSteinberg") Floyd-Steinberg
button.pagination.prev#back-to-image previous button.pagination.prev#back-to-image previous
button.pagination.next#room-next add room button.pagination.next#room-next add room
.page.download .page.download
h2 done!
p#added p#added
h2 download
textarea#output(autocomplete="off") textarea#output(autocomplete="off")
br
button#download download button#clipboard.half copy to clipboard
button#download.half download
button.pagination.prev#add add another image button.pagination.prev#add add another image
button.pagination.start#reset start again button.pagination.start#reset start again

View File

@ -24,6 +24,18 @@ function download(filename, text) {
document.body.removeChild(element); document.body.removeChild(element);
} }
function copyToClipboard() {
const button = el("clipboard");
el("output").select();
document.execCommand("copy");
button.innerText = "copied!";
setTimeout(() => {
button.innerText = "copy to clipboard";
}, 2000);
}
function el(id) { function el(id) {
return document.getElementById(id); return document.getElementById(id);
} }
@ -64,6 +76,7 @@ async function run() {
const buttonAddImage = el("add"); const buttonAddImage = el("add");
const buttonBackToImage = el("back-to-image"); const buttonBackToImage = el("back-to-image");
const buttonCopyToClipboard = el("clipboard")
const buttonDownload = el("download"); const buttonDownload = el("download");
const buttonGameDataProceed = el("game-data-next"); const buttonGameDataProceed = el("game-data-next");
const buttonImageProceed = el("image-next"); const buttonImageProceed = el("image-next");
@ -126,14 +139,12 @@ async function run() {
el("game").addEventListener("change", function() { el("game").addEventListener("change", function() {
readFile(this, function (e) { readFile(this, function (e) {
textareaGameDataInput.value = e.target.result; textareaGameDataInput.value = e.target.result;
console.log(load_game(e.target.result));
checkGameData(); checkGameData();
}, "text"); }, "text");
}); });
function setPaletteDropdown() { function setPaletteDropdown() {
const palettes = JSON.parse(get_palettes()); const palettes = JSON.parse(get_palettes());
console.debug(palettes);
selectPalette.innerHTML = ""; selectPalette.innerHTML = "";
@ -153,7 +164,9 @@ async function run() {
} }
function checkGameData() { function checkGameData() {
if (textareaGameDataInput.value.length > 0) { let result = load_game(textareaGameDataInput.value)
if (result === "Loaded game") {
buttonGameDataProceed.removeAttribute("disabled"); buttonGameDataProceed.removeAttribute("disabled");
setPaletteDropdown(); setPaletteDropdown();
} else { } else {
@ -241,6 +254,9 @@ async function run() {
download("output.bitsy", textareaGameDataOutput.value); download("output.bitsy", textareaGameDataOutput.value);
} }
buttonCopyToClipboard.addEventListener("click", copyToClipboard);
buttonCopyToClipboard.addEventListener("touchend", copyToClipboard);
buttonDownload.addEventListener("click", handleDownload); buttonDownload.addEventListener("click", handleDownload);
buttonDownload.addEventListener("touchend", handleDownload); buttonDownload.addEventListener("touchend", handleDownload);

View File

@ -1,9 +1,6 @@
#![feature(clamp)]
use bitsy_parser::game::Game; use bitsy_parser::game::Game;
use bitsy_parser::image::Image; use bitsy_parser::image::Image;
use bitsy_parser::tile::Tile; use bitsy_parser::tile::Tile;
use dither::prelude::Dither;
use image::{GenericImageView, Pixel, DynamicImage}; use image::{GenericImageView, Pixel, DynamicImage};
use image::imageops::ColorMap; use image::imageops::ColorMap;
use image::imageops::FilterType::CatmullRom; use image::imageops::FilterType::CatmullRom;
@ -28,18 +25,12 @@ enum SelectedPalette {
} }
} }
enum DitherAlgorithm {
Atkinson,
Bayer4x4,
FloydSteinberg,
}
struct State { struct State {
game: Option<Game>, game: Option<Game>,
image: Option<DynamicImage>, image: Option<DynamicImage>,
room_name: Option<String>, room_name: Option<String>,
palette: SelectedPalette, palette: SelectedPalette,
dither: Option<DitherAlgorithm>, dither: bool,
brightness: i32, brightness: i32,
} }
@ -50,7 +41,7 @@ lazy_static! {
image: None, image: None,
room_name: None, room_name: None,
palette: SelectedPalette::None, palette: SelectedPalette::None,
dither: Some(DitherAlgorithm::Atkinson), dither: false,
brightness: 0, brightness: 0,
} }
); );
@ -133,15 +124,9 @@ pub fn load_image(image_base64: String) -> String {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn set_dither(dither: &str) { pub fn set_dither(dither: bool) {
let mut state = STATE.lock().unwrap(); let mut state = STATE.lock().unwrap();
state.dither = dither;
state.dither = match dither {
"Atkinson" => Some(DitherAlgorithm::Atkinson),
"Bayer4x4" => Some(DitherAlgorithm::Bayer4x4),
"FloydSteinberg" => Some(DitherAlgorithm::FloydSteinberg),
_ => None,
}
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -214,58 +199,28 @@ fn palette_from(bg: &bitsy_parser::Colour, fg: &bitsy_parser::Colour) -> bitsy_p
} }
fn render_preview(state: &State) -> DynamicImage { fn render_preview(state: &State) -> DynamicImage {
let mut buffer = state.image.as_ref().unwrap().clone().into_rgba(); let mut buffer = state.image.as_ref().unwrap().clone().into_rgba8();
let palette = match &state.palette { let palette = match &state.palette {
SelectedPalette::None => { SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].clone(),
bitsy_parser::mock::game_default().palettes[0].clone() SelectedPalette::Existing { id } => state.game.as_ref().unwrap().get_palette(id).unwrap().clone(),
}, SelectedPalette::New { background, foreground } => palette_from(background, foreground),
SelectedPalette::Existing { id } => {
state.game.as_ref().unwrap().get_palette(id).unwrap().clone()
},
SelectedPalette::New { background, foreground } => {
palette_from(background, foreground)
},
}; };
let colour_map = crate::ColourMap::from(&palette); let colour_map = crate::ColourMap::from(&palette);
// adjust brightness // adjust brightness
let mut buffer = image::imageops::brighten( let mut buffer = image::imageops::brighten(&mut buffer, state.brightness);
&mut buffer,
state.brightness
);
if state.dither.is_some() { if state.dither {
// image::imageops::dither(&mut buffer, &colour_map); image::imageops::dither(&mut buffer, &colour_map);
// so, what needs doing?
// convert the buffer to the format required by the dither crate
let dither_img = dither::prelude::Img::new(buffer.iter(), 128).unwrap();
// run the dither according to user preference
// todo what is the quantise function supposed to be like?
// dither::ditherer::ATKINSON.dither(dither_img, colour_map);
// convert the dither format back to an ImageBuffer
// buffer = image::ImageBuffer::new(128, 128); //from_vec(128, 128, pixels).unwrap();
//
// for pixel in dither_img.iter() {
// // todo enumerate?
// buffer.put_pixel(0,0, image::Rgba::from([0,0,0,0]));
// }
} else { } else {
// just do colour indexing // just do colour indexing
let colour_indices = image::imageops::colorops::index_colors( let indices = image::imageops::colorops::index_colors(&mut buffer, &colour_map);
&mut buffer,
&colour_map
);
// todo get rid of magic numbers! what about Bitsy HD? // todo get rid of magic numbers! what about Bitsy HD?
buffer = image::ImageBuffer::from_fn(128, 128, |x, y| -> image::Rgba<u8> { buffer = image::ImageBuffer::from_fn(128, 128, |x, y| -> image::Rgba<u8> {
let p = colour_indices.get_pixel(x, y); let p = indices.get_pixel(x, y);
colour_map colour_map
.lookup(p.0[0] as usize) .lookup(p.0[0] as usize)
@ -292,18 +247,18 @@ pub fn add_room() -> String {
let mut state = STATE.lock().expect("Couldn't lock application state"); let mut state = STATE.lock().expect("Couldn't lock application state");
if state.game.is_none() { if state.game.is_none() {
return "No game data loaded".to_string(); return "No game data loaded".into();
} }
match &state.palette { match &state.palette {
SelectedPalette::None => { return "No palette selected".to_string(); }, SelectedPalette::None => { return "No palette selected".into(); },
_ => {} _ => {}
}; };
let mut game = state.game.clone().unwrap(); let mut game = state.game.clone().unwrap();
if state.image.is_none() { if state.image.is_none() {
return "No image loaded".to_string(); return "No image loaded".into();
} }
let palette_id = Some(match &state.palette { let palette_id = Some(match &state.palette {
@ -335,7 +290,7 @@ pub fn add_room() -> String {
fn colour_match(a: &image::Rgb<u8>, b: &bitsy_parser::Colour) -> u8 { fn colour_match(a: &image::Rgb<u8>, b: &bitsy_parser::Colour) -> u8 {
if a[0] == b.red && a[1] == b.green && a[2] == b.blue { 1 } else { 0 } if a[0] == b.red && a[1] == b.green && a[2] == b.blue { 1 } else { 0 }
}; }
for y in (row * SD)..((row + 1) * SD) { for y in (row * SD)..((row + 1) * SD) {
for x in (column * SD)..((column + 1) * SD) { for x in (column * SD)..((column + 1) * SD) {
@ -422,7 +377,7 @@ mod test {
load_default_game(); load_default_game();
load_image(include_str!("test-resources/colour_input.png.base64").trim().to_string()); load_image(include_str!("test-resources/colour_input.png.base64").trim().to_string());
let output = get_preview(); let output = get_preview();
let expected = include_str!("test-resources/colour_input.png.base64.greyscale").trim(); let expected = include_str!("test-resources/colour_input.png.base64.blueprint").trim();
assert_eq!(output, expected); assert_eq!(output, expected);
} }

View File

@ -0,0 +1 @@
data:image/png;base64,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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
Write your game's title here Write your game's title here
# BITSY VERSION 7.2 # BITSY VERSION 7.5
! ROOM_FORMAT 1 ! ROOM_FORMAT 1