20 Commits

Author SHA1 Message Date
3e7d6eeaa5 include old version 2020-11-08 21:18:51 +00:00
f6308110be try again 2020-11-08 20:53:52 +00:00
2d73963aa0 black and white palette 2020-11-08 20:52:29 +00:00
6f8e00130c update deploy script 2020-11-08 20:50:55 +00:00
b478b1e3ee update deploy script 2020-11-08 20:50:42 +00:00
055928eb7b todo 2020-11-08 20:48:04 +00:00
b3690c4dd7 black and white palette 2020-11-08 20:46:58 +00:00
da04534fd9 fix crop shit 2020-11-08 20:45:10 +00:00
3d1129c613 log 2020-11-08 20:07:48 +00:00
3b851975d0 room stats 2020-11-08 20:07:42 +00:00
afc626bae0 undo skipping crop on 128² 2020-11-08 20:07:26 +00:00
6e43249d64 done 2020-11-08 20:06:08 +00:00
458604cd1a display number of tiles added 2020-11-08 17:36:24 +00:00
bb0b970281 bump 2020-11-08 17:36:11 +00:00
f62202cb74 style header differently 2020-11-08 17:36:00 +00:00
745b18dfc0 styling tweaks 2020-11-08 17:35:28 +00:00
e5a87f854e this would break things 2020-11-08 17:35:08 +00:00
c2787db422 better load_image errors 2020-11-08 17:34:48 +00:00
7d274bb3c2 this function doesn't return anything 2020-11-08 17:33:39 +00:00
fbe40fb866 dedupe palette function 2020-11-08 17:33:15 +00:00
11 changed files with 332 additions and 195 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "pixsy" name = "pixsy"
version = "0.72.6" version = "0.72.7"
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"

View File

@@ -1,8 +1,6 @@
# todo # todo
* if image is exactly 128×128, *don't* crop
* tile reuse * tile reuse
* noise reduction (remove lonely pixels) * noise reduction (remove lonely pixels)
* implement Atkinson and Bayer dithering options * implement Atkinson and Bayer dithering options
* stats for added room (number of tiles) * fix weird problem with pixels flipping (see test::example)
* dedupe "palette from custom colours" functionality

View File

@@ -1,7 +1,6 @@
#! /usr/bin/env bash #! /usr/bin/env bash
./build.sh
rm -rf dist rm -rf dist
mkdir dist mkdir dist
cp -r README.md LICENSE index.html script.js background.png pkg includes dist cp -r README.md LICENSE index.html script.js pkg includes old dist
# butler push dist ruin/pixsy:html butler push dist ruin/pixsy:html

View File

@@ -1,122 +0,0 @@
.slider[type='range'] {
-webkit-appearance: none;
margin: 9px 0;
width: 100%
}
.slider[type='range']:focus {
outline: 0
}
.slider[type='range']::-webkit-slider-runnable-track {
cursor: pointer;
height: 5px;
width: 100%;
background: #008ecc;
border: none
}
.slider[type='range']::-webkit-slider-thumb {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 50%;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.12);
cursor: pointer;
height: 18px;
width: 18px;
-webkit-appearance: none;
margin-top: -6.5px
}
.slider[type='range']::-moz-range-track {
cursor: pointer;
height: 5px;
width: 100%;
background: #008ecc;
border: none
}
.slider[type='range']::-moz-range-thumb {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 50%;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.12);
cursor: pointer;
height: 18px;
width: 18px
}
.slider[type='range']::-ms-track {
cursor: pointer;
height: 5px;
width: 100%;
background: transparent;
border-color: transparent;
border-width: 9px 0;
color: transparent
}
.slider[type='range']::-ms-fill-lower {
background: #008ecc;
border: none
}
.slider[type='range']::-ms-fill-upper {
background: #008ecc;
border: none
}
.slider[type='range']::-ms-thumb {
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 50%;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.12);
cursor: pointer;
height: 18px;
width: 18px;
margin-top: 0
}
.cropper {
display: inline-block
}
.cropper canvas {
border-radius: 3px
}
.cropper canvas:hover {
cursor: move
}
.cropper-tools {
margin-top: 15px;
text-align: center
}
.cropper-zoom {
display: inline-block
}
.cropper-zoom .slider {
margin: 0 10px;
width: 225px
}
.cropper-zoom .icon {
margin-top: 2px;
font-size: 18px
}
.cropper-zoom .icon:last-child {
font-size: 24px
}
.cropper .icon {
display: inline-block;
width: 1em;
height: 1em;
fill: rgba(0, 0, 0, .54);
vertical-align: middle;
}

File diff suppressed because one or more lines are too long

250
includes/croppie.css Normal file
View File

@@ -0,0 +1,250 @@
.croppie-container {
width: 100%;
height: 100%;
}
.croppie-container .cr-image {
z-index: -1;
position: absolute;
top: 0;
left: 0;
transform-origin: 0 0;
max-height: none;
max-width: none;
}
.croppie-container .cr-boundary {
position: relative;
overflow: hidden;
margin: 0 auto;
z-index: 1;
width: 100%;
height: 100%;
}
.croppie-container .cr-viewport,
.croppie-container .cr-resizer {
position: absolute;
border: 2px solid #fff;
margin: auto;
top: 0;
bottom: 0;
right: 0;
left: 0;
box-shadow: 0 0 2000px 2000px rgba(0, 0, 0, 0.5);
z-index: 0;
}
.croppie-container .cr-resizer {
z-index: 2;
box-shadow: none;
pointer-events: none;
}
.croppie-container .cr-resizer-vertical,
.croppie-container .cr-resizer-horisontal {
position: absolute;
pointer-events: all;
}
.croppie-container .cr-resizer-vertical::after,
.croppie-container .cr-resizer-horisontal::after {
display: block;
position: absolute;
box-sizing: border-box;
border: 1px solid black;
background: #fff;
width: 10px;
height: 10px;
content: '';
}
.croppie-container .cr-resizer-vertical {
bottom: -5px;
cursor: row-resize;
width: 100%;
height: 10px;
}
.croppie-container .cr-resizer-vertical::after {
left: 50%;
margin-left: -5px;
}
.croppie-container .cr-resizer-horisontal {
right: -5px;
cursor: col-resize;
width: 10px;
height: 100%;
}
.croppie-container .cr-resizer-horisontal::after {
top: 50%;
margin-top: -5px;
}
.croppie-container .cr-original-image {
display: none;
}
.croppie-container .cr-vp-circle {
border-radius: 50%;
}
.croppie-container .cr-overlay {
z-index: 1;
position: absolute;
cursor: move;
touch-action: none;
}
.croppie-container .cr-slider-wrap {
width: 75%;
margin: 15px auto;
text-align: center;
}
.croppie-result {
position: relative;
overflow: hidden;
}
.croppie-result img {
position: absolute;
}
.croppie-container .cr-image,
.croppie-container .cr-overlay,
.croppie-container .cr-viewport {
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
}
/*************************************/
/***** STYLING RANGE INPUT ***********/
/*************************************/
/*http://brennaobrien.com/blog/2014/05/style-input-type-range-in-every-browser.html */
/*************************************/
.cr-slider {
-webkit-appearance: none;
/*removes default webkit styles*/
/*border: 1px solid white; *//*fix for FF unable to apply focus style bug */
width: 300px;
/*required for proper track sizing in FF*/
max-width: 100%;
padding-top: 8px;
padding-bottom: 8px;
background-color: transparent;
}
.cr-slider::-webkit-slider-runnable-track {
width: 100%;
height: 3px;
background: rgba(0, 0, 0, 0.5);
border: 0;
border-radius: 3px;
}
.cr-slider::-webkit-slider-thumb {
-webkit-appearance: none;
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #ddd;
margin-top: -6px;
}
.cr-slider:focus {
outline: none;
}
/*
.cr-slider:focus::-webkit-slider-runnable-track {
background: #ccc;
}
*/
.cr-slider::-moz-range-track {
width: 100%;
height: 3px;
background: rgba(0, 0, 0, 0.5);
border: 0;
border-radius: 3px;
}
.cr-slider::-moz-range-thumb {
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #ddd;
margin-top: -6px;
}
/*hide the outline behind the border*/
.cr-slider:-moz-focusring {
outline: 1px solid white;
outline-offset: -1px;
}
.cr-slider::-ms-track {
width: 100%;
height: 5px;
background: transparent;
/*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead */
border-color: transparent;/*leave room for the larger thumb to overflow with a transparent border */
border-width: 6px 0;
color: transparent;/*remove default tick marks*/
}
.cr-slider::-ms-fill-lower {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
}
.cr-slider::-ms-fill-upper {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
}
.cr-slider::-ms-thumb {
border: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: #ddd;
margin-top:1px;
}
.cr-slider:focus::-ms-fill-lower {
background: rgba(0, 0, 0, 0.5);
}
.cr-slider:focus::-ms-fill-upper {
background: rgba(0, 0, 0, 0.5);
}
/*******************************************/
/***********************************/
/* Rotation Tools */
/***********************************/
.cr-rotate-controls {
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
}
.cr-rotate-controls button {
border: 0;
background: none;
}
.cr-rotate-controls i:before {
display: inline-block;
font-style: normal;
font-weight: 900;
font-size: 22px;
}
.cr-rotate-l i:before {
content: '↺';
}
.cr-rotate-r i:before {
content: '↻';
}

1
includes/croppie.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,7 @@
@background: #fff4d9; @background: #57506a;
@text: #ec6d7d; @page-background: #968eb5;
@accent: #a3c4ef; @accent: #ec6d7d;
@text: #464256;
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -36,6 +37,10 @@ button {
} }
} }
header * {
color: @accent;
}
h1 { h1 {
margin: 0; margin: 0;
} }
@@ -112,8 +117,8 @@ textarea {
height: 80vmin; height: 80vmin;
width: 80vmin; width: 80vmin;
background-color: @background; background-color: @page-background;
border: 2px solid @accent; color: @text;
border-radius: 5vmin; border-radius: 5vmin;
box-shadow: @accent 1vmin 1vmin; box-shadow: @accent 1vmin 1vmin;
padding: 5vmin; padding: 5vmin;

View File

@@ -4,9 +4,10 @@ html(lang="en-gb")
meta(charset="utf-8") meta(charset="utf-8")
title pixsy title pixsy
link(rel="stylesheet" href="includes/style.css") link(rel="stylesheet" href="includes/style.css")
link(rel="stylesheet" href="includes/cropper.css") link(rel="stylesheet" href="includes/croppie.css")
script(src="includes/cropper.min.js") script(src="includes/croppie.min.js")
body body
header
h1 h1
| pixsy | pixsy
//img(alt="pixsy" src="includes/pixsy.png") //img(alt="pixsy" src="includes/pixsy.png")
@@ -40,7 +41,7 @@ html(lang="en-gb")
.image-container .image-container
input#image(type="file" accept="image/*") input#image(type="file" accept="image/*")
#crop #crop(style="display: none;")
button.pagination.prev previous button.pagination.prev previous
button.pagination.next#image-next(disabled=true) next button.pagination.next#image-next(disabled=true) next
@@ -68,9 +69,9 @@ html(lang="en-gb")
#new-palette(style="display: none;") #new-palette(style="display: none;")
.half .half
input#colour-background(type="color" value="#2f4ac9") input#colour-background(type="color" value="#000000")
.half .half
input#colour-foreground(type="color" value="#8798fe") input#colour-foreground(type="color" value="#ffffff")
label label
input#dither(type="checkbox" checked=true) input#dither(type="checkbox" checked=true)
@@ -80,6 +81,8 @@ html(lang="en-gb")
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
p#added
h2 download h2 download
textarea#output(autocomplete="off") textarea#output(autocomplete="off")

View File

@@ -72,6 +72,7 @@ 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 divCroppie = el("crop");
const divNewPalette = el("new-palette"); const divNewPalette = el("new-palette");
const inputBrightness = el("brightness"); const inputBrightness = el("brightness");
const inputColourBackground = el("colour-background"); const inputColourBackground = el("colour-background");
@@ -81,8 +82,11 @@ async function run() {
const textareaGameDataInput = el("game-data"); const textareaGameDataInput = el("game-data");
const textareaGameDataOutput = el("output"); const textareaGameDataOutput = el("output");
const cropper = new Cropper({ width: 192, height: 192 }); const croppie = new Croppie(divCroppie, {
let cropperRendered = false; viewport: {width: 128, height: 128, type: 'square'},
boundary: {width: 256, height: 256},
enableZoom: true,
});
// hide all pages except start page // hide all pages except start page
for (let page of document.getElementsByClassName('page')) { for (let page of document.getElementsByClassName('page')) {
@@ -95,8 +99,12 @@ async function run() {
pageButton.addEventListener('touchend', pagination); pageButton.addEventListener('touchend', pagination);
} }
// croppie needs to be on screen to work;
// halt pagination until we're finished gathering the results
buttonImageProceed.removeEventListener("click", pagination);
function new_game() { function new_game() {
console.debug(load_default_game()); load_default_game();
textareaGameDataInput.value = output(); textareaGameDataInput.value = output();
checkGameData(); checkGameData();
// we don't need to look at the default game data, so skip ahead to the image page // we don't need to look at the default game data, so skip ahead to the image page
@@ -159,21 +167,9 @@ async function run() {
el('image').addEventListener('change', function () { el('image').addEventListener('change', function () {
readFile(this, function (e) { readFile(this, function (e) {
if ( ! cropperRendered) { croppie.bind({url: e.target.result, zoom: 0});
cropper.render("#crop"); divCroppie.style.display = "block";
cropperRendered = true;
buttonImageProceed.removeAttribute("disabled"); buttonImageProceed.removeAttribute("disabled");
}
if (load_image(e.target.result) === "128×128") {
// we can't just do `buttonImageProceed.click()`
// because this calls the handleImage() function, which we don't want here
el("page-image").style.display = "none";
el("page-room").style.display = "block";
loadPreview();
} else {
cropper.loadImage(e.target.result);
}
}, "image"); }, "image");
}); });
@@ -182,8 +178,18 @@ async function run() {
} }
function handleImage() { function handleImage() {
console.log(load_image(cropper.getCroppedImage())); croppie.result({
type: "base64",
size: "viewport",
format: "png",
}).then((result) => {
console.log("Loading image: " + load_image(result));
el("page-image").style.display = "none";
el("page-room" ).style.display = "block";
loadPreview(); loadPreview();
});
} }
buttonImageProceed.addEventListener("click", handleImage); buttonImageProceed.addEventListener("click", handleImage);
@@ -224,7 +230,7 @@ async function run() {
}); });
function addRoom() { function addRoom() {
console.log(add_room()); el("added").innerText = add_room();
textareaGameDataOutput.value = output(); textareaGameDataOutput.value = output();
} }
@@ -255,8 +261,8 @@ async function run() {
inputRoomName.value = ""; inputRoomName.value = "";
selectPalette.innerHTML = ""; selectPalette.innerHTML = "";
divNewPalette.style.display = "none"; divNewPalette.style.display = "none";
inputColourBackground.value = "#2f4ac9"; inputColourBackground.value = "#000000";
inputColourForeground.value = "#8798fe"; inputColourForeground.value = "#ffffff";
checkboxDither.checked = true; checkboxDither.checked = true;
} }

View File

@@ -95,6 +95,11 @@ pub fn load_image(image_base64: String) -> String {
let mut state = STATE.lock().expect("Couldn't lock application state"); let mut state = STATE.lock().expect("Couldn't lock application state");
let image_base64: Vec<&str> = image_base64.split("base64,").collect(); let image_base64: Vec<&str> = image_base64.split("base64,").collect();
if image_base64.len() < 2 {
return format!("Error: Badly-formatted base64: {}", image_base64.join(""));
}
let image_base64 = image_base64[1]; let image_base64 = image_base64[1];
match base64::decode(image_base64) { match base64::decode(image_base64) {
@@ -109,13 +114,13 @@ pub fn load_image(image_base64: String) -> String {
}, },
_ => { _ => {
state.image = None; state.image = None;
"Couldn't load image".to_string() "Error: Couldn't load image".to_string()
} }
} }
}, },
_ => { _ => {
state.image = None; state.image = None;
"Couldn't decode image".to_string() "Error: Couldn't decode image".to_string()
} }
} }
} }
@@ -185,19 +190,23 @@ fn image_to_base64(image: &DynamicImage) -> String {
format!("data:image/png;base64,{}", base64::encode(&bytes)) format!("data:image/png;base64,{}", base64::encode(&bytes))
} }
fn palette_from(bg: &bitsy_parser::Colour, fg: &bitsy_parser::Colour) -> bitsy_parser::Palette {
bitsy_parser::Palette {
id: "0".to_string(),
name: None,
colours: vec![
bg.clone(), fg.clone(), bitsy_parser::Colour { red: 0, green: 0, blue: 0 }
],
}
}
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 = 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(),
SelectedPalette::Existing { id } => state.game.as_ref().unwrap().get_palette(id).unwrap().clone(), SelectedPalette::Existing { id } => state.game.as_ref().unwrap().get_palette(id).unwrap().clone(),
SelectedPalette::New { background, foreground } => bitsy_parser::Palette { SelectedPalette::New { background, foreground } => palette_from(background, foreground),
id: "0".to_string(),
name: None,
colours: vec![
background.clone(), foreground.clone(), bitsy_parser::Colour { red: 0, green: 0, blue: 0 }
],
},
}; };
let colour_map = crate::ColourMap::from(&palette); let colour_map = crate::ColourMap::from(&palette);
@@ -258,15 +267,7 @@ pub fn add_room() -> String {
SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].id.clone(), SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].id.clone(),
SelectedPalette::Existing { id } => id.clone(), SelectedPalette::Existing { id } => id.clone(),
SelectedPalette::New { background, foreground } => { SelectedPalette::New { background, foreground } => {
game.add_palette(bitsy_parser::Palette { game.add_palette(palette_from(background, foreground))
id: "0".to_string(),
name: None,
colours: vec![
background.clone(),
foreground.clone(),
bitsy_parser::Colour { red: 0, green: 0, blue: 0 }
],
})
}, },
}); });
@@ -319,9 +320,6 @@ pub fn add_room() -> String {
} }
} }
// todo if player selected "create new game", delete room 0 here?
// that would probably break unless the avatar was also placed in the room
game.add_room(bitsy_parser::Room { game.add_room(bitsy_parser::Room {
id: "0".to_string(), id: "0".to_string(),
palette_id, palette_id,