10 Commits

7 changed files with 46 additions and 86 deletions

View File

@@ -5,7 +5,7 @@ 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"]

View File

@@ -2,7 +2,6 @@
@page-background: #968eb5; @page-background: #968eb5;
@accent: #ec6d7d; @accent: #ec6d7d;
@text: #464256; @text: #464256;
@ok: #caec6d;
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -132,13 +131,3 @@ 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

@@ -12,7 +12,7 @@ html(lang="en-gb")
| pixsy | pixsy
//img(alt="pixsy" src="includes/pixsy.png") //img(alt="pixsy" src="includes/pixsy.png")
p. p.
convert images to Bitsy rooms version 0.72.8
| |
#[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 +21,30 @@ 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="http://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 compatible with Bitsy version 7.2 and earlier.
p.
#[b pixsy] does not currently work via the Itch desktop program.
some users are also currently reporting problems.
if pixsy does not work for you, please try the
#[a(href="http://tinybird.info/image-to-bitsy/old/") old version] instead.
p.
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(
@@ -34,9 +52,6 @@ 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

View File

@@ -78,8 +78,6 @@ 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");
@@ -134,7 +132,6 @@ 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 = "";
@@ -154,39 +151,19 @@ async function run() {
} }
function checkGameData() { function checkGameData() {
paragraphGameResult.style.display = "none"; let result = load_game(textareaGameDataInput.value)
paragraphGameErrors.style.display = "none"; console.log(result);
let game_data = textareaGameDataInput.value; if (result === "Loaded game") {
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 () {

View File

@@ -1,5 +1,3 @@
#![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;
@@ -76,21 +74,16 @@ 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, errors)) => { Ok((game, _errs)) => {
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())
} }
} }
} }
@@ -172,7 +165,6 @@ 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();
@@ -186,7 +178,6 @@ pub fn get_palettes() -> String {
palette_objects.push(object).unwrap(); palette_objects.push(object).unwrap();
} }
}
json::stringify(palette_objects) json::stringify(palette_objects)
} }
@@ -256,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 {
@@ -299,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) {
@@ -386,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);
} }
@@ -399,16 +390,4 @@ 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

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

File diff suppressed because one or more lines are too long