Compare commits
3 Commits
31d7ff52ca
...
6a58e8c003
Author | SHA1 | Date |
---|---|---|
Max Bradbury | 6a58e8c003 | |
Max Bradbury | aea19fcd6c | |
Max Bradbury | db3cdf42ff |
12
Cargo.toml
12
Cargo.toml
|
@ -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...
|
||||||
|
|
|
@ -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
|
||||||
|
|
25
script.js
25
script.js
|
@ -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();
|
||||||
|
|
67
src/lib.rs
67
src/lib.rs
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue