Compare commits

...

3 Commits

4 changed files with 80 additions and 30 deletions

View File

@ -11,9 +11,9 @@ repository = "https://tinybird.dev/max/image-to-bitsy"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
"base64" = "^0.12.3" base64 = "^0.12.3"
"bitsy-parser" = "^0.72.4" 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.64" # newer versions are bugged... wasm-bindgen = "=0.2.64" # newer versions are bugged...

View File

@ -62,6 +62,12 @@ html(lang="en-gb")
| palette | palette
select#palette select#palette
#new-palette(style="display: none;")
.half
input#colour-background(type="color" value="#2f4ac9")
.half
input#colour-foreground(type="color" value="#8798fe")
label label
input#dither(type="checkbox" checked=true) input#dither(type="checkbox" checked=true)
| dither | dither

View File

@ -72,7 +72,10 @@ async function run() {
const buttonNewGame = el("new"); const buttonNewGame = el("new");
const buttonReset = el("reset"); const buttonReset = el("reset");
const checkboxDither = el("dither"); const checkboxDither = el("dither");
const divNewPalette = el("new-palette");
const inputBrightness = el("brightness"); const inputBrightness = el("brightness");
const inputColourBackground = el("colour-background");
const inputColourForeground = el("colour-foreground");
const inputRoomName = el("room-name"); const inputRoomName = el("room-name");
const selectPalette = el("palette"); const selectPalette = el("palette");
const textareaGameDataInput = el("game-data"); const textareaGameDataInput = el("game-data");
@ -126,6 +129,11 @@ async function run() {
selectPalette.innerHTML = ""; selectPalette.innerHTML = "";
palettes.push({
id: "NEW_PALETTE",
name: "new palette"
});
for (let palette of palettes) { for (let palette of palettes) {
let option = document.createElement("option"); let option = document.createElement("option");
@ -174,10 +182,25 @@ async function run() {
buttonImageProceed.addEventListener("touchend", handleImage); buttonImageProceed.addEventListener("touchend", handleImage);
selectPalette.addEventListener("change", () => { selectPalette.addEventListener("change", () => {
set_palette(selectPalette.value); set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value);
if (selectPalette.value === "NEW_PALETTE") {
divNewPalette.style.display = "block";
} else {
divNewPalette.style.display = "none";
}
loadPreview(); loadPreview();
}); });
function updateCustomPalette() {
set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value);
loadPreview();
}
inputColourForeground.addEventListener("change", updateCustomPalette);
inputColourBackground.addEventListener("change", updateCustomPalette);
checkboxDither.addEventListener("change", () => { checkboxDither.addEventListener("change", () => {
set_dither(checkboxDither.checked); set_dither(checkboxDither.checked);
loadPreview(); loadPreview();

View File

@ -16,11 +16,22 @@ use colour_map::ColourMap;
const SD: u32 = 8; const SD: u32 = 8;
enum SelectedPalette {
None,
Existing {
id: String,
},
New {
background: bitsy_parser::Colour,
foreground: bitsy_parser::Colour,
}
}
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: Option<String>, palette: SelectedPalette,
dither: bool, dither: bool,
brightness: i32, brightness: i32,
} }
@ -31,7 +42,7 @@ lazy_static! {
game: None, game: None,
image: None, image: None,
room_name: None, room_name: None,
palette: None, palette: SelectedPalette::None,
dither: true, dither: true,
brightness: 0, brightness: 0,
} }
@ -49,8 +60,13 @@ fn tile_name(prefix: &Option<String>, index: &u32) -> Option<String> {
#[wasm_bindgen] #[wasm_bindgen]
pub fn load_default_game() { pub fn load_default_game() {
let mut state = STATE.lock().unwrap(); let mut state = STATE.lock().unwrap();
state.game = Some(bitsy_parser::mock::game_default()); state.game = Some(bitsy_parser::mock::game_default());
state.palette = Some(bitsy_parser::mock::game_default().palette_ids()[0].clone())
// yes, this will probably always just be "0", but to be safe…
state.palette = SelectedPalette::Existing {
id: bitsy_parser::mock::game_default().palette_ids()[0].clone()
}
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -63,12 +79,12 @@ pub fn load_game(game_data: String) -> String {
Ok((game, _errs)) => { Ok((game, _errs)) => {
let palette_id = game.palette_ids()[0].clone(); let palette_id = game.palette_ids()[0].clone();
state.game = Some(game); state.game = Some(game);
state.palette = Some(palette_id); state.palette = SelectedPalette::Existing { id: palette_id };
format!("Loaded game") format!("Loaded game")
}, },
_ => { _ => {
state.game = None; state.game = None;
state.palette = None; state.palette = SelectedPalette::None;
format!("{}", result.err().unwrap()) format!("{}", result.err().unwrap())
} }
} }
@ -110,12 +126,16 @@ pub fn set_dither(dither: bool) {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn set_palette(palette_id: String) { pub fn set_palette(palette_id: &str, background: String, foreground: String) {
let mut state = STATE.lock().unwrap(); let mut state = STATE.lock().unwrap();
match palette_id.is_empty() { state.palette = match palette_id {
true => { state.palette = None }, "NEW_PALETTE" => SelectedPalette::New {
false => { state.palette = Some(palette_id) }, background: bitsy_parser::Colour::from_hex(&background).unwrap(),
foreground: bitsy_parser::Colour::from_hex(&foreground).unwrap(),
},
"" => SelectedPalette::None,
_ => SelectedPalette::Existing { id: palette_id.to_string() },
} }
} }
@ -166,10 +186,22 @@ fn image_to_base64(image: &DynamicImage) -> String {
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_rgba();
let palette_id = state.palette.as_ref().unwrap();
let palette = *&state.game.as_ref().unwrap().get_palette(palette_id).unwrap();
let colour_map = crate::ColourMap::from(palette);
let palette = match &state.palette {
SelectedPalette::None => 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 {
id: "0".to_string(),
name: None,
colours: vec![
background.clone(), foreground.clone(), Colour { red: 0, green: 0, blue: 0 }
],
},
};
let colour_map = crate::ColourMap::from(&palette);
// adjust brightness
let mut buffer = image::imageops::brighten(&mut buffer, state.brightness); let mut buffer = image::imageops::brighten(&mut buffer, state.brightness);
if state.dither { if state.dither {
@ -186,17 +218,6 @@ fn render_preview(state: &State) -> DynamicImage {
.expect("indexed color out-of-range") .expect("indexed color out-of-range")
.into() .into()
}); });
let mut indexed = buffer.clone();
for (i, pixel) in buffer.pixels().enumerate() {
// todo get rid of magic numbers! what about Bitsy HD?
let mut pixel = image::Rgba::from(pixel.0);
colour_map.map_color(&mut pixel);
indexed.put_pixel(
(i % 128) as u32,
(i / 128) as u32,
pixel
);
}
} }
image::DynamicImage::ImageRgba8(buffer) image::DynamicImage::ImageRgba8(buffer)