Compare commits

..

1 Commits

Author SHA1 Message Date
Max Bradbury 4d40fd6e08 wip 2020-11-28 14:51:43 +00:00
8 changed files with 101 additions and 85 deletions

View File

@ -1,19 +1,19 @@
[package] [package]
name = "pixsy" name = "pixsy"
version = "0.710.0" version = "0.72.8"
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/pixsy" repository = "https://tinybird.dev/max/image-to-bitsy"
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
base64 = "^0.12.3" base64 = "^0.12.3"
bitsy-parser = "^0.710.0" bitsy-parser = "^0.72.5"
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.78" wasm-bindgen = "=0.2.64" # newer versions are bugged...

View File

@ -2,6 +2,7 @@
@page-background: #968eb5; @page-background: #968eb5;
@accent: #ec6d7d; @accent: #ec6d7d;
@text: #464256; @text: #464256;
@ok: #caec6d;
* { * {
box-sizing: border-box; box-sizing: border-box;
@ -22,11 +23,6 @@ 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;
@ -136,3 +132,13 @@ textarea {
image-rendering: pixelated; image-rendering: pixelated;
image-rendering: crisp-edges; image-rendering: crisp-edges;
} }
.message {
background-color: @ok;
padding: 1em;
text-align: left;
&#game-data-errors {
background-color: @accent;
}
}

View File

@ -8,9 +8,11 @@ html(lang="en-gb")
script(src="includes/croppie.min.js") script(src="includes/croppie.min.js")
body body
header header
h1 pixsy h1
| pixsy
//img(alt="pixsy" src="includes/pixsy.png")
p. p.
version 0.710.0 convert images to Bitsy rooms
| |
#[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]
| |
@ -19,35 +21,12 @@ 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
p. input#game(type="file" autocomplete="off")
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(
@ -55,6 +34,9 @@ html(lang="en-gb")
autocomplete="off" autocomplete="off"
) )
p#game-data-result.message(style="display: none;")
p#game-data-errors.message(style="display: none;")
button.pagination.prev previous button.pagination.prev previous
button.pagination.next#game-data-next(disabled=true) next button.pagination.next#game-data-next(disabled=true) next
.page.image#page-image .page.image#page-image
@ -95,22 +77,21 @@ html(lang="en-gb")
input#colour-foreground(type="color" value="#ffffff") input#colour-foreground(type="color" value="#ffffff")
label label
input#dither(type="checkbox") input#dither(type="checkbox" checked=true)
| dither | dither
p (approximates a greyscale effect)
br br
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
textarea#output(autocomplete="off") h2 download
button#clipboard.half copy to clipboard textarea#output(autocomplete="off")
button#download.half download br
button#download 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,18 +24,6 @@ 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);
} }
@ -76,7 +64,6 @@ 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");
@ -91,6 +78,8 @@ async function run() {
const inputColourBackground = el("colour-background"); const inputColourBackground = el("colour-background");
const inputColourForeground = el("colour-foreground"); const inputColourForeground = el("colour-foreground");
const inputRoomName = el("room-name"); const inputRoomName = el("room-name");
const paragraphGameResult = el("game-data-result");
const paragraphGameErrors = el("game-data-errors");
const selectPalette = el("palette"); const selectPalette = el("palette");
const textareaGameDataInput = el("game-data"); const textareaGameDataInput = el("game-data");
const textareaGameDataOutput = el("output"); const textareaGameDataOutput = el("output");
@ -145,6 +134,7 @@ async function run() {
function setPaletteDropdown() { function setPaletteDropdown() {
const palettes = JSON.parse(get_palettes()); const palettes = JSON.parse(get_palettes());
console.debug(palettes);
selectPalette.innerHTML = ""; selectPalette.innerHTML = "";
@ -164,18 +154,39 @@ async function run() {
} }
function checkGameData() { function checkGameData() {
let result = load_game(textareaGameDataInput.value) paragraphGameResult.style.display = "none";
paragraphGameErrors.style.display = "none";
if (result === "Loaded game") { let game_data = textareaGameDataInput.value;
if (game_data.length === 0) {
buttonGameDataProceed.setAttribute("disabled", "disabled");
return;
}
let result = load_game(game_data);
console.debug(result);
let parts = result.split(". Errors: ");
result = parts[0];
let errors = parts[1];
paragraphGameResult.innerText = result;
paragraphGameResult.style.display = "block";
if (errors) {
paragraphGameErrors.innerText = errors;
paragraphGameErrors.style.display = "block";
}
if (result.startsWith("Error")) {
buttonGameDataProceed.setAttribute("disabled", "disabled");
} else {
buttonGameDataProceed.removeAttribute("disabled"); buttonGameDataProceed.removeAttribute("disabled");
setPaletteDropdown(); setPaletteDropdown();
} else {
buttonGameDataProceed.setAttribute("disabled", "disabled");
} }
} }
textareaGameDataInput.addEventListener("change", checkGameData); textareaGameDataInput.addEventListener("change", checkGameData);
textareaGameDataInput.addEventListener("keyup", checkGameData);
checkGameData(); checkGameData();
el('image').addEventListener('change', function () { el('image').addEventListener('change', function () {
@ -254,9 +265,6 @@ 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,3 +1,5 @@
#![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;
@ -41,7 +43,7 @@ lazy_static! {
image: None, image: None,
room_name: None, room_name: None,
palette: SelectedPalette::None, palette: SelectedPalette::None,
dither: false, dither: true,
brightness: 0, brightness: 0,
} }
); );
@ -74,16 +76,21 @@ pub fn load_game(game_data: String) -> String {
let result = Game::from(game_data); let result = Game::from(game_data);
match result { match result {
Ok((game, _errs)) => { Ok((game, errors)) => {
let palette_id = game.palette_ids()[0].clone(); let palette_id = game.palette_ids()[0].clone();
let game_name = game.name.clone();
let errors: Vec<String> = errors.iter().map(|err| format!("{}", err)).collect();
state.game = Some(game); state.game = Some(game);
state.palette = SelectedPalette::Existing { id: palette_id }; state.palette = SelectedPalette::Existing { id: palette_id };
format!("Loaded game")
}, format!("Loaded game: {}. Errors: {}", game_name, errors.join(", "))
}
_ => { _ => {
state.game = None; state.game = None;
state.palette = SelectedPalette::None; state.palette = SelectedPalette::None;
format!("{}", result.err().unwrap())
format!("Error: {}", result.err().unwrap())
} }
} }
} }
@ -165,6 +172,7 @@ pub fn get_palettes() -> String {
let mut palette_objects = json::JsonValue::new_array(); let mut palette_objects = json::JsonValue::new_array();
if state.game.is_some() {
for palette in &state.game.as_ref().unwrap().palettes { for palette in &state.game.as_ref().unwrap().palettes {
let mut object = json::JsonValue::new_object(); let mut object = json::JsonValue::new_object();
@ -178,6 +186,7 @@ pub fn get_palettes() -> String {
palette_objects.push(object).unwrap(); palette_objects.push(object).unwrap();
} }
}
json::stringify(palette_objects) json::stringify(palette_objects)
} }
@ -199,7 +208,7 @@ 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_rgba8(); let mut buffer = state.image.as_ref().unwrap().clone().into_rgba();
let palette = match &state.palette { let palette = match &state.palette {
SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].clone(), SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].clone(),
@ -247,18 +256,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".into(); return "No game data loaded".to_string();
} }
match &state.palette { match &state.palette {
SelectedPalette::None => { return "No palette selected".into(); }, SelectedPalette::None => { return "No palette selected".to_string(); },
_ => {} _ => {}
}; };
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".into(); return "No image loaded".to_string();
} }
let palette_id = Some(match &state.palette { let palette_id = Some(match &state.palette {
@ -290,7 +299,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) {
@ -377,7 +386,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.blueprint").trim(); let expected = include_str!("test-resources/colour_input.png.base64.greyscale").trim();
assert_eq!(output, expected); assert_eq!(output, expected);
} }
@ -390,4 +399,16 @@ mod test {
// todo what? why are extraneous pixels appearing in the output tiles? // todo what? why are extraneous pixels appearing in the output tiles?
assert_eq!(output(), include_str!("test-resources/expected.bitsy")); assert_eq!(output(), include_str!("test-resources/expected.bitsy"));
} }
#[test]
fn palettes() {
load_default_game();
assert_eq!(crate::get_palettes(), "[{\"id\":\"0\",\"name\":\"blueprint\"}]");
}
#[test]
fn no_palettes() {
assert_eq!(crate::get_palettes(), "[]");
}
} }

View File

@ -1 +0,0 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAIvUlEQVR4nO2YwbHsuBEEVzbJDvn0PZERskM27UYeMqKiAyA5Q3DAGTAv1dUAe9iPdXr/+vPfv//+62FZngAszhOAxXkCsDhPABbnCcDiPAFYnCcAi/MEYHGeACzOE4DFeQKwOE8AFucJwOI8AVicJwCL8wRgcZ4ALM4TgMV5ArA4TwAW5wnA4jwBWJwnAIvzBGBxngDs8Od////rz3/+3dVv5wlAg6Mf13vqN7JkAPxgqvR8TxN76rewVAB6H6f2q2/hHfVbWSoA4kdTped7mthTv4UlAuBHUSu1Xz0c7YF99c4sEYDEj6JKz6vS81W/hZ8OgB+jqux5qf3qv5mfDkCP/IBZg16Vnq/6bfx0APwoVSu9PuRZ1r/CTweghx9Shayheun1v5UlA9Dj1z7uEZYOwIofvLJ0AB6eACzPE4DFeQKwOE8AFucJwOI8AVicJwCL8wRgcZ4AFN7572B9pvo7s2QAWh8oe1lvUe9V/w0sEQA/jCpb3rqnsufvzs8GYOtD1LPq96j39SpkfWd+LgCtP7w9VXp+TyHrytbZ3fi5ALzDkQ/WumOvqlR/R54ABH6wnkLWLfbO78YTgDfwI/cUsr4zTwA6+AF7+issGwA+pLzzQXme53r6LSwfgL2PxT3uVE1avZFcOf8rAjD6D8C85NXZPM8z6jfzFQEYDR9O9j4gd7lTFahBPwrmjp7ZY8kAwCt/5FfunuWTvwXLBuAo+UGyvppP/dZXBOBTf4w7UHetfjS3CIBLVoWsZzDr9z/1u1MDsLXk1tkvU/eufjRTAlCX0quiV1eh7lv9SKYEQFqLtXrQ6/8Sdcfqr2BqAMRFVbBWVyJ3tlZHc4sAVFrLtnq/Rt2x+iuYGgAXrCp7/lepe1Y/kqkBaFGX3fO/gnupkDVUP4JpAXCZqpA1VL8aV+4/LQAtclFrFazVX8Bd9hSyHsWUALhI1S3qnep/nav2nRIAqAvpeyp7/pvw3ff0Sj4egLpU9S3qnT1/d468b96xVkfy8QBIXUZfVXq+6p2p76jv6SeYFgDYW7Set3ySZ3ci35N3xKN71HvVj+DjAahL6HsKrbqlQH03eDfeCwVqwFP3FLK+go8HQI4u1rpHb4t6fzb5vrwbHk1qr/qrmBKAXK5VVwXro3onfCe0BWfCnS0/mikBkKPLeQ89CvfvQOudebfs61Gp/iqmBKC3nP2Win5PgXomvgfwLukhe9aoVH8FUwJwFP4AwB/B+h14/tP4vvy2NRz16Ce4TQBcOjWx11No1amfgt9rwTt4ljWkpwa89VXcJgAJiwPLW4/CmWgPz9VX8bnUJHtZgx79BLcIgAu3FKzPao96np76FepzeOrUFq0ze+hV3CIAFZa+Av6QzK4qLf8KPlufs59wh74q6amv5nYByOWp9xSsq0LWPXp36L+Kc3jWWlo96Z3Rv5LbBuAs/OFyVvqsK56hr8JzFeZkX19V9KlXcqsAuPARBeuqSe2lt1YTeu/AHJ5Fk1Yv2Trn7CpuF4Cz8MdijirVVzxPfRWeE57Hq5I+a7GXeiW3CQDLAgtT9xSsq7aoZ/qqFfrv0JoFzGud2Vd7cH4FtwvAu/AHYkZVqb5Sz/HvwAyeTU3sqRX7KlBfxS0CwKIsuaVgXXWLekdfVfToO/Bswhx6asW+Wsk+9WhuEQBwyVfhj8KzPZXqK3lO/S45wzqxr1bsqwm90dwiALkoS+qtq0LWW9R76bNO6L9LnccsemqLI2epI7lNAFgMleqTeqZXIWvY8tap7+LzaIs8yxr0agvORnKbAAgL9nzWUL1kP2uovsL5GXI2s/Cq7HmxnzqaWwWABVs1VJ94praoZ/qWnsEZaKX2e17twfkopgeARVkIbVHP9GoLz9Sk1Us4P0POZhZelep7eE8F6pHcIgDCcng12epVrbT69lSgHoHzKszPs/S9OrGPjmJ6AIClKixJX5XqW3hHBWu1BWdnYTZzWirV9/CemtAbwW0CwEKpldrXVz2K91WgPouzWjA/z/U9rdhHR3GbAFRYkr4q1Vc839MK/VEwn3mpSavXwntqQm8EtwkAC6VWal9f9SjeV4F6BM4T5tJTJb212sIzdBS3CUCFJe1nvYd397QFZyNwPvOsk15fPK9aoX+W6QFgMRZJlZ7v6SvkM9boCJiVMJeeKvqqPfKcegTTAwAuJSyXvep7eG9Pe3A+An6DWWiLrTPI86zFHnqW6QFgEWGhI76nR8n71ugomAfMpK6a2FNlzwO9s9wiACyCyp7v4b09lZYfRc6t8Dut81Y/e9apZ5kaAJZIWCh71YM9FbJ+B59HR+Cslib2VNnzQv8sUwMALMYiaI+t8zyzViFr0KsJvVHU2cJvHDnLWuylnmVqAFgiYSF6asW++gqtZ+yljoBZwky8Knq1hWdqhf5ZpgWAhVgAhazFnlrJfqtWRa9W6I+C+cxDk1YP7KstPEs9y7QAAEtUWKrVh9ZZq5dsnXuWOgrmJcw+0gP7VSv0zzItAC7EEtSq6FXRq6KvKnq1BWej8DeYaS21p1d75Dn1CKYFAFwmYbHab/Vk6wx65/ZTR+JMVPZ8kmdZgx49y7QAsACwBLUqPV9V9FVFr7bgbBT5G8w94tUe9Rx/likBYAleHq3UfvWVd8/tq0A9CmYyDwXqLbwH3MVXTeiNYEoAIBdimfRQe/qqkj7rZK+PjoJ5ySuz67MJc/Icf4YpAXABXt4aqodWL6nn6a1V0auCHwVzmae+g8+mJvTOMiUAkMuwiN5alfRZS6tX6d2xj46Cecm7s1tzsoc/w5QAuAAvb92j3kmfdZL9rMWeCtSjYfaoucxKmEsPPcOUAAAvLyyBr5q0emBflT3fgjujyN8aNZeZzEIFf4YpAWABXhzt4bkqejXZ61lv6UiYCaPmOg+YiUfPMCUAwMsDC1BXrbzSr73qW3BnJPweM1GgPguzmIMK/gxTApAL9GAx7qlSvdR+9WCvKlCP5orZzkzOzP8HNQ0vxIkGykwAAAAASUVORK5CYII=

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.5 # BITSY VERSION 7.2
! ROOM_FORMAT 1 ! ROOM_FORMAT 1