Compare commits
66 Commits
72dbff8d5e
...
f135e184e4
Author | SHA1 | Date |
---|---|---|
Max Bradbury | f135e184e4 | |
Max Bradbury | 3e7d6eeaa5 | |
Max Bradbury | f6308110be | |
Max Bradbury | 2d73963aa0 | |
Max Bradbury | 6f8e00130c | |
Max Bradbury | b478b1e3ee | |
Max Bradbury | 055928eb7b | |
Max Bradbury | b3690c4dd7 | |
Max Bradbury | da04534fd9 | |
Max Bradbury | 3d1129c613 | |
Max Bradbury | 3b851975d0 | |
Max Bradbury | afc626bae0 | |
Max Bradbury | 6e43249d64 | |
Max Bradbury | 458604cd1a | |
Max Bradbury | bb0b970281 | |
Max Bradbury | f62202cb74 | |
Max Bradbury | 745b18dfc0 | |
Max Bradbury | e5a87f854e | |
Max Bradbury | c2787db422 | |
Max Bradbury | 7d274bb3c2 | |
Max Bradbury | fbe40fb866 | |
Max Bradbury | 7ff67e51f8 | |
Max Bradbury | 80df4a9a6a | |
Max Bradbury | e76ff57053 | |
Max Bradbury | 0ef2d2acd9 | |
Max Bradbury | f9b0f6b6db | |
Max Bradbury | 8c92b05b74 | |
Max Bradbury | 512f386c25 | |
Max Bradbury | 63cb971aca | |
Max Bradbury | 03ce88e015 | |
Max Bradbury | 8a8b861e5d | |
Max Bradbury | c1c4fca1db | |
Max Bradbury | e5da032236 | |
Max Bradbury | d04eaf892b | |
Max Bradbury | daf9b21096 | |
Max Bradbury | 34053a1327 | |
Max Bradbury | 21eb632d22 | |
Max Bradbury | 9eb090924a | |
Max Bradbury | 9261c41cd8 | |
Max Bradbury | 6a58e8c003 | |
Max Bradbury | aea19fcd6c | |
Max Bradbury | db3cdf42ff | |
Max Bradbury | 31d7ff52ca | |
Max Bradbury | 45ef41c366 | |
Max Bradbury | 3a8e349aac | |
Max Bradbury | 080d0853eb | |
Max Bradbury | 0150a5ca33 | |
Max Bradbury | ec44370d7e | |
Max Bradbury | 013e1e1c8c | |
Max Bradbury | 37cde1713d | |
Max Bradbury | 0780bc8267 | |
Max Bradbury | fd5f141f19 | |
Max Bradbury | 5916c4af17 | |
Max Bradbury | 69a3b482be | |
Max Bradbury | 4bd896f05a | |
Max Bradbury | 03588c4c55 | |
Max Bradbury | 0cb4f3af8d | |
Max Bradbury | 1b3505929b | |
Max Bradbury | 6b679c3bbc | |
Max Bradbury | ca93a00eda | |
Max Bradbury | 4c958373ba | |
Max Bradbury | 65257b3886 | |
Max Bradbury | 196ec06c88 | |
Max Bradbury | 4dbcd6ac66 | |
Max Bradbury | 8a8743de93 | |
Max Bradbury | bf17c08909 |
|
@ -1,17 +0,0 @@
|
||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
# Custom for Visual Studio
|
|
||||||
*.cs diff=csharp
|
|
||||||
|
|
||||||
# Standard to msysgit
|
|
||||||
*.doc diff=astextplain
|
|
||||||
*.DOC diff=astextplain
|
|
||||||
*.docx diff=astextplain
|
|
||||||
*.DOCX diff=astextplain
|
|
||||||
*.dot diff=astextplain
|
|
||||||
*.DOT diff=astextplain
|
|
||||||
*.pdf diff=astextplain
|
|
||||||
*.PDF diff=astextplain
|
|
||||||
*.rtf diff=astextplain
|
|
||||||
*.RTF diff=astextplain
|
|
|
@ -1,6 +1,3 @@
|
||||||
node_modules
|
|
||||||
package-lock.json
|
|
||||||
|
|
||||||
# Windows image file caches
|
# Windows image file caches
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
|
@ -53,8 +50,10 @@ Temporary Items
|
||||||
*.zip
|
*.zip
|
||||||
itch*
|
itch*
|
||||||
|
|
||||||
# node
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
# IDE stuff
|
# IDE stuff
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
/Cargo.lock
|
||||||
|
/dist/
|
||||||
|
/index.html
|
||||||
|
/includes/style.css
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "pixsy"
|
||||||
|
version = "0.72.7"
|
||||||
|
description = "convert images to Bitsy rooms"
|
||||||
|
authors = ["Max Bradbury <max@tinybird.info>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://tinybird.dev/max/image-to-bitsy"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "^0.12.3"
|
||||||
|
bitsy-parser = "^0.72.5"
|
||||||
|
image = "^0.23.7"
|
||||||
|
json = "^0.12.4"
|
||||||
|
lazy_static = "^1.4.0"
|
||||||
|
wasm-bindgen = "=0.2.64" # newer versions are bugged...
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Max Bradbury
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
||||||
|
# todo
|
||||||
|
|
||||||
|
* tile reuse
|
||||||
|
* noise reduction (remove lonely pixels)
|
||||||
|
* implement Atkinson and Bayer dithering options
|
||||||
|
* fix weird problem with pixels flipping (see test::example)
|
|
@ -1 +0,0 @@
|
||||||
theme: jekyll-theme-dinky
|
|
3
build.sh
3
build.sh
|
@ -1,4 +1,5 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
pug index.pug index.html
|
pug index.pug
|
||||||
lessc includes/style.less includes/style.css
|
lessc includes/style.less includes/style.css
|
||||||
|
wasm-pack build --target web
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
zip -r image-to-bitsy.zip readme.md index.html includes/
|
rm -rf dist
|
||||||
butler push image-to-bitsy.zip ruin/image-to-bitsy:html
|
mkdir dist
|
||||||
|
cp -r README.md LICENSE index.html script.js pkg includes old dist
|
||||||
|
butler push dist ruin/pixsy:html
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,144 +1,133 @@
|
||||||
@font-face {
|
@background: #57506a;
|
||||||
font-family: 'rubikregular';
|
@page-background: #968eb5;
|
||||||
src: url('rubik-regular-webfont.woff2') format('woff2'),
|
@accent: #ec6d7d;
|
||||||
url('rubik-regular-webfont.woff') format('woff');
|
@text: #464256;
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@light: #d3cbd0;
|
|
||||||
@dark: #594a54;
|
|
||||||
@accent: #f69f8f; // pink
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: "rubikregular", sans-serif;
|
color: @text;
|
||||||
|
margin: 0 auto 0.5em auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
background-color: @dark;
|
background-color: @background;
|
||||||
color: @light;
|
font-size: 3vmin;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
button {
|
||||||
|
padding: 1em;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&.pagination:not(.normal) {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5vmin;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
&.prev {
|
||||||
|
left: 5vmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.next, &.start {
|
||||||
|
right: 5vmin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header * {
|
||||||
color: @accent;
|
color: @accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
h1 {
|
||||||
image-rendering: -moz-crisp-edges;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="color"] {
|
|
||||||
&[disabled] {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
width: 2em;
|
|
||||||
height: 2em;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
h3 {
|
||||||
padding: 1em 0;
|
font-size: 0.9em;
|
||||||
width: 256px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], button {
|
input {
|
||||||
width: 14em;
|
width: 100%;
|
||||||
margin: 1em;
|
|
||||||
padding: 0.25em;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
&[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 1em;
|
||||||
|
position: relative;
|
||||||
|
top: 0.25em;
|
||||||
|
left: 0.25em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
img {
|
||||||
text-align: right;
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 0 auto 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
.box256;
|
height: 15em;
|
||||||
|
padding: 0.5em;
|
||||||
font-family: monospace;
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea, input {
|
.background {
|
||||||
|
background-color: @background;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkboxes label {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropper-tools {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.half {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: left;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
height: 46vh;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box256 {
|
.page {
|
||||||
|
height: 80vmin;
|
||||||
|
width: 80vmin;
|
||||||
|
|
||||||
|
background-color: @page-background;
|
||||||
|
color: @text;
|
||||||
|
border-radius: 5vmin;
|
||||||
|
box-shadow: @accent 1vmin 1vmin;
|
||||||
|
padding: 5vmin;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview {
|
||||||
|
width: 256px;
|
||||||
height: 256px;
|
height: 256px;
|
||||||
width: 256px;
|
image-rendering: pixelated;
|
||||||
}
|
image-rendering: crisp-edges;
|
||||||
|
|
||||||
.centre {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.croppie-container {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container {
|
|
||||||
background-color: @light;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make this just for desktop view?
|
|
||||||
// put sections in a single column on mobile/etc?
|
|
||||||
.section {
|
|
||||||
.centre;
|
|
||||||
|
|
||||||
background-color: @light;
|
|
||||||
color: @dark;
|
|
||||||
width: 256px;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#brightness {
|
|
||||||
width: 256px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#threshold {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#brightness, #threshold {
|
|
||||||
+ label{
|
|
||||||
.centre;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo make this match the croppie slider or vice versa
|
|
||||||
}
|
|
||||||
|
|
||||||
#palettes {
|
|
||||||
.box256;
|
|
||||||
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
#preview, #room-output {
|
|
||||||
.centre;
|
|
||||||
|
|
||||||
width: 256px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#save {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
140
index.pug
140
index.pug
|
@ -1,96 +1,96 @@
|
||||||
doctype html
|
doctype html
|
||||||
html
|
html(lang="en-gb")
|
||||||
head
|
head
|
||||||
meta(charset="utf-8")
|
meta(charset="utf-8")
|
||||||
title image to bitsy
|
title pixsy
|
||||||
|
link(rel="stylesheet" href="includes/style.css")
|
||||||
// lodash
|
|
||||||
script(src="includes/lodash.min.js")
|
|
||||||
|
|
||||||
// jquery
|
|
||||||
script(src="includes/jquery.min.js")
|
|
||||||
|
|
||||||
// croppie
|
|
||||||
link(rel="stylesheet" href="includes/croppie.css")
|
link(rel="stylesheet" href="includes/croppie.css")
|
||||||
script(src="includes/croppie.js")
|
script(src="includes/croppie.min.js")
|
||||||
|
|
||||||
// main stuff
|
|
||||||
link(rel="stylesheet" type="text/css" href="includes/style.css")
|
|
||||||
script(src="includes/script.js")
|
|
||||||
body
|
body
|
||||||
header
|
header
|
||||||
h1 image-to-bitsy
|
h1
|
||||||
p convert any image to a #[a(href="https://ledoux.itch.io/bitsy") bitsy] room
|
| pixsy
|
||||||
|
//img(alt="pixsy" src="includes/pixsy.png")
|
||||||
p.
|
p.
|
||||||
#[a(href="https://github.com/synth-ruiner/bitsy-image-to-room") about]
|
convert images to Bitsy rooms
|
||||||
|
|
|
||||||
|
#[a(href="./old/") old version]
|
||||||
|
|
|
|
||||||
please contact me if you have any issues:
|
|
||||||
#[a(href="https://twitter.com/synth_ruiner") twitter],
|
|
||||||
#[a(href="mailto:max@tinybird.info") email]
|
#[a(href="mailto:max@tinybird.info") email]
|
||||||
|
|
|
||||||
.flex-container
|
#[a(href="https://twitter.com/synth_ruiner") twitter]
|
||||||
#game-data.section
|
.pages
|
||||||
|
.page#start
|
||||||
|
button.normal.pagination.next#new create a new bitsy game
|
||||||
|
button.normal.pagination.next#load load an existing bitsy game
|
||||||
|
.page.game-data
|
||||||
h2 game data
|
h2 game data
|
||||||
|
|
||||||
textarea#bitsy-data(placeholder="Bitsy data or html")
|
input#game(type="file" autocomplete="off")
|
||||||
include includes/default.bitsy
|
br
|
||||||
|
|
||||||
p
|
textarea#game-data(
|
||||||
input.game-data(type="file")
|
placeholder="Paste your game data here or use the file chooser button above"
|
||||||
p paste or upload your game data (or html) here
|
autocomplete="off"
|
||||||
p (maybe make a backup first)
|
)
|
||||||
|
|
||||||
#image.section
|
button.pagination.prev previous
|
||||||
|
button.pagination.next#game-data-next(disabled=true) next
|
||||||
|
.page.image#page-image
|
||||||
h2 image
|
h2 image
|
||||||
|
|
||||||
#croppie
|
.image-container
|
||||||
|
input#image(type="file" accept="image/*")
|
||||||
|
#crop(style="display: none;")
|
||||||
|
|
||||||
input#imageUpload(type="file" accepts="image/*")
|
button.pagination.prev previous
|
||||||
|
button.pagination.next#image-next(disabled=true) next
|
||||||
|
.page.room#page-room
|
||||||
|
h2 room
|
||||||
|
|
||||||
#palette.section
|
|
||||||
h2 palette
|
|
||||||
|
|
||||||
form#palettes
|
|
||||||
table
|
table
|
||||||
tbody
|
tbody
|
||||||
|
tr
|
||||||
#crop.section
|
td(style="width: 60%")
|
||||||
h2 preview
|
img#preview(alt="preview")
|
||||||
|
|
||||||
canvas#preview(width=128, height=128)
|
|
||||||
|
|
||||||
input#brightness(type="range" min=-255 max=255 value=0)
|
|
||||||
|
|
||||||
label(for="brightness") brightness
|
|
||||||
|
|
||||||
//- to do
|
|
||||||
input#dithering(type="checkbox")
|
|
||||||
label(for="dithering") dithering
|
|
||||||
|
|
||||||
//- to do
|
|
||||||
input#smoothing(type="checkbox")
|
|
||||||
label(for="smoothing") smoothing
|
|
||||||
|
|
||||||
#output.section
|
|
||||||
h2 output
|
|
||||||
|
|
||||||
canvas#room-output(width=128, height=128)
|
|
||||||
|
|
||||||
label#never never
|
|
||||||
input#threshold(type="range" min=0 max=64 value=64)
|
|
||||||
label#always always
|
|
||||||
br
|
|
||||||
label(for="threshold") create new tiles
|
|
||||||
br
|
br
|
||||||
|
|
||||||
input#roomName(type="text", placeholder="room name")
|
label
|
||||||
|
| brightness
|
||||||
|
input#brightness(type="range" min=-96 max=96 value=0)
|
||||||
|
td(style="vertical-align: top;")
|
||||||
|
label
|
||||||
|
| name (optional)
|
||||||
|
input#room-name(type="text" placeholder="e.g. 'bedroom'" autocomplete="off")
|
||||||
|
|
||||||
button#save write to game data
|
label
|
||||||
|
| palette
|
||||||
|
select#palette
|
||||||
|
|
||||||
//- to do
|
#new-palette(style="display: none;")
|
||||||
|
.half
|
||||||
//-label favour broad strokes | favour details
|
input#colour-background(type="color" value="#000000")
|
||||||
|
.half
|
||||||
|
input#colour-foreground(type="color" value="#ffffff")
|
||||||
|
|
||||||
|
label
|
||||||
|
input#dither(type="checkbox" checked=true)
|
||||||
|
| dither
|
||||||
br
|
br
|
||||||
|
|
||||||
//-button Download
|
button.pagination.prev#back-to-image previous
|
||||||
|
button.pagination.next#room-next add room
|
||||||
|
.page.download
|
||||||
|
p#added
|
||||||
|
|
||||||
|
h2 download
|
||||||
|
|
||||||
|
textarea#output(autocomplete="off")
|
||||||
|
br
|
||||||
|
|
||||||
|
button#download download
|
||||||
|
|
||||||
|
button.pagination.prev#add add another image
|
||||||
|
button.pagination.start#reset start again
|
||||||
|
script(type="module")
|
||||||
|
include script.js
|
||||||
|
|
|
@ -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: '↻';
|
||||||
|
}
|
|
@ -100,7 +100,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBitsyGameData() {
|
function handleBitsyGameData() {
|
||||||
let input = $bitsyData.val();
|
let input = $('#bitsy-data').val();
|
||||||
|
|
||||||
if ( ! input) {
|
if ( ! input) {
|
||||||
return;
|
return;
|
||||||
|
@ -488,7 +488,7 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#save').on('click touchend', function() {
|
$('#save').on('click touchend', function() {
|
||||||
let $textArea = $('textarea');
|
$textArea = $('textarea');
|
||||||
|
|
||||||
let newGameData = $textArea.val();
|
let newGameData = $textArea.val();
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
|
@ -0,0 +1,144 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'rubikregular';
|
||||||
|
src: url('rubik-regular-webfont.woff2') format('woff2'),
|
||||||
|
url('rubik-regular-webfont.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@light: #d3cbd0;
|
||||||
|
@dark: #594a54;
|
||||||
|
@accent: #f69f8f; // pink
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "rubikregular", sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
background-color: @dark;
|
||||||
|
color: @light;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: @accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
image-rendering: -moz-crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="color"] {
|
||||||
|
&[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
padding: 1em 0;
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], button {
|
||||||
|
width: 14em;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0.25em;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
.box256;
|
||||||
|
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea, input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box256 {
|
||||||
|
height: 256px;
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centre {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppie-container {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container {
|
||||||
|
background-color: @light;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make this just for desktop view?
|
||||||
|
// put sections in a single column on mobile/etc?
|
||||||
|
.section {
|
||||||
|
.centre;
|
||||||
|
|
||||||
|
background-color: @light;
|
||||||
|
color: @dark;
|
||||||
|
width: 256px;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#brightness {
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#threshold {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#brightness, #threshold {
|
||||||
|
+ label{
|
||||||
|
.centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo make this match the croppie slider or vice versa
|
||||||
|
}
|
||||||
|
|
||||||
|
#palettes {
|
||||||
|
.box256;
|
||||||
|
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview, #room-output {
|
||||||
|
.centre;
|
||||||
|
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#save {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
14
package.json
14
package.json
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"name": "bitsy-image-to-room",
|
|
||||||
"description": "Tool to convert images to Bitsy rooms. Can work as an offline web application",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"devDependencies": {
|
|
||||||
"pug": "latest",
|
|
||||||
"pug-cli": "latest",
|
|
||||||
"less": "latest"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"croppie": "^2.5.1",
|
|
||||||
"jquery": "^3.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,273 @@
|
||||||
|
import init, {
|
||||||
|
add_room,
|
||||||
|
get_palettes,
|
||||||
|
get_preview,
|
||||||
|
load_image,
|
||||||
|
load_game,
|
||||||
|
load_default_game,
|
||||||
|
output,
|
||||||
|
set_brightness,
|
||||||
|
set_dither,
|
||||||
|
set_palette,
|
||||||
|
set_room_name,
|
||||||
|
} from './pkg/pixsy.js';
|
||||||
|
|
||||||
|
if (typeof WebAssembly !== "object") {
|
||||||
|
window.location = "./old/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// stolen from https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server
|
||||||
|
function download(filename, text) {
|
||||||
|
let element = document.createElement('a');
|
||||||
|
|
||||||
|
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||||
|
element.setAttribute('download', filename);
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
function el(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pagination(e) {
|
||||||
|
const parent = e.target.parentNode;
|
||||||
|
|
||||||
|
parent.style.display = "none";
|
||||||
|
|
||||||
|
if (e.target.classList.contains("next")) {
|
||||||
|
parent.nextSibling.style.display = "block";
|
||||||
|
} else if (e.target.classList.contains("prev")) {
|
||||||
|
parent.previousSibling.style.display = "block";
|
||||||
|
} else if (e.target.classList.contains("start")) {
|
||||||
|
document.getElementById("start").style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFile(input, callback, type = "text") {
|
||||||
|
if (input.files && input.files[0]) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = callback;
|
||||||
|
|
||||||
|
if (type === "text") {
|
||||||
|
reader.readAsText(input.files[0]);
|
||||||
|
} else {
|
||||||
|
reader.readAsDataURL(input.files[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
await init();
|
||||||
|
|
||||||
|
const buttonAddImage = el("add");
|
||||||
|
const buttonBackToImage = el("back-to-image");
|
||||||
|
const buttonDownload = el("download");
|
||||||
|
const buttonGameDataProceed = el("game-data-next");
|
||||||
|
const buttonImageProceed = el("image-next");
|
||||||
|
const buttonRoomProceed = el("room-next");
|
||||||
|
const buttonLoadGame = el("load");
|
||||||
|
const buttonNewGame = el("new");
|
||||||
|
const buttonReset = el("reset");
|
||||||
|
const checkboxDither = el("dither");
|
||||||
|
const divCroppie = el("crop");
|
||||||
|
const divNewPalette = el("new-palette");
|
||||||
|
const inputBrightness = el("brightness");
|
||||||
|
const inputColourBackground = el("colour-background");
|
||||||
|
const inputColourForeground = el("colour-foreground");
|
||||||
|
const inputRoomName = el("room-name");
|
||||||
|
const selectPalette = el("palette");
|
||||||
|
const textareaGameDataInput = el("game-data");
|
||||||
|
const textareaGameDataOutput = el("output");
|
||||||
|
|
||||||
|
const croppie = new Croppie(divCroppie, {
|
||||||
|
viewport: {width: 128, height: 128, type: 'square'},
|
||||||
|
boundary: {width: 256, height: 256},
|
||||||
|
enableZoom: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// hide all pages except start page
|
||||||
|
for (let page of document.getElementsByClassName('page')) {
|
||||||
|
page.style.display = "none";
|
||||||
|
}
|
||||||
|
el("start").style.display = "block";
|
||||||
|
|
||||||
|
for (let pageButton of document.getElementsByClassName("pagination")) {
|
||||||
|
pageButton.addEventListener('click', 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() {
|
||||||
|
load_default_game();
|
||||||
|
textareaGameDataInput.value = output();
|
||||||
|
checkGameData();
|
||||||
|
// we don't need to look at the default game data, so skip ahead to the image page
|
||||||
|
buttonGameDataProceed.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_game() {
|
||||||
|
textareaGameDataInput.value = "";
|
||||||
|
checkGameData();
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonNewGame.addEventListener("click", new_game);
|
||||||
|
buttonNewGame.addEventListener("touchend", new_game);
|
||||||
|
buttonLoadGame.addEventListener("click", clear_game);
|
||||||
|
buttonLoadGame.addEventListener("touchend", clear_game);
|
||||||
|
|
||||||
|
// handle game data and image
|
||||||
|
|
||||||
|
el("game").addEventListener("change", function() {
|
||||||
|
readFile(this, function (e) {
|
||||||
|
textareaGameDataInput.value = e.target.result;
|
||||||
|
console.log(load_game(e.target.result));
|
||||||
|
checkGameData();
|
||||||
|
}, "text");
|
||||||
|
});
|
||||||
|
|
||||||
|
function setPaletteDropdown() {
|
||||||
|
const palettes = JSON.parse(get_palettes());
|
||||||
|
console.debug(palettes);
|
||||||
|
|
||||||
|
selectPalette.innerHTML = "";
|
||||||
|
|
||||||
|
palettes.push({
|
||||||
|
id: "NEW_PALETTE",
|
||||||
|
name: "new palette"
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let palette of palettes) {
|
||||||
|
let option = document.createElement("option");
|
||||||
|
|
||||||
|
option.value = palette.id;
|
||||||
|
option.innerText = palette.name;
|
||||||
|
|
||||||
|
selectPalette.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGameData() {
|
||||||
|
if (textareaGameDataInput.value.length > 0) {
|
||||||
|
buttonGameDataProceed.removeAttribute("disabled");
|
||||||
|
setPaletteDropdown();
|
||||||
|
} else {
|
||||||
|
buttonGameDataProceed.setAttribute("disabled", "disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textareaGameDataInput.addEventListener("change", checkGameData);
|
||||||
|
textareaGameDataInput.addEventListener("keyup", checkGameData);
|
||||||
|
checkGameData();
|
||||||
|
|
||||||
|
el('image').addEventListener('change', function () {
|
||||||
|
readFile(this, function (e) {
|
||||||
|
croppie.bind({url: e.target.result, zoom: 0});
|
||||||
|
divCroppie.style.display = "block";
|
||||||
|
buttonImageProceed.removeAttribute("disabled");
|
||||||
|
}, "image");
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadPreview() {
|
||||||
|
el("preview").setAttribute("src", get_preview());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImage() {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonImageProceed.addEventListener("click", handleImage);
|
||||||
|
buttonImageProceed.addEventListener("touchend", handleImage);
|
||||||
|
|
||||||
|
selectPalette.addEventListener("change", () => {
|
||||||
|
set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value);
|
||||||
|
|
||||||
|
if (selectPalette.value === "NEW_PALETTE") {
|
||||||
|
divNewPalette.style.display = "block";
|
||||||
|
} else {
|
||||||
|
divNewPalette.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateCustomPalette() {
|
||||||
|
set_palette(selectPalette.value, inputColourBackground.value, inputColourForeground.value);
|
||||||
|
loadPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
inputColourForeground.addEventListener("change", updateCustomPalette);
|
||||||
|
inputColourBackground.addEventListener("change", updateCustomPalette);
|
||||||
|
|
||||||
|
checkboxDither.addEventListener("change", () => {
|
||||||
|
set_dither(checkboxDither.checked);
|
||||||
|
loadPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
inputRoomName.addEventListener("change", () => {
|
||||||
|
set_room_name(inputRoomName.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
inputBrightness.addEventListener("input", () => {
|
||||||
|
set_brightness(inputBrightness.value);
|
||||||
|
loadPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
function addRoom() {
|
||||||
|
el("added").innerText = add_room();
|
||||||
|
textareaGameDataOutput.value = output();
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonRoomProceed.addEventListener("click", addRoom);
|
||||||
|
buttonRoomProceed.addEventListener("touchend", addRoom);
|
||||||
|
|
||||||
|
function handleDownload() {
|
||||||
|
download("output.bitsy", textareaGameDataOutput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonDownload.addEventListener("click", handleDownload);
|
||||||
|
buttonDownload.addEventListener("touchend", handleDownload);
|
||||||
|
|
||||||
|
function addImage() {
|
||||||
|
textareaGameDataInput.value = textareaGameDataOutput.value;
|
||||||
|
textareaGameDataOutput.value = "";
|
||||||
|
buttonBackToImage.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonAddImage.addEventListener("click", addImage);
|
||||||
|
buttonAddImage.addEventListener("touchend", addImage);
|
||||||
|
|
||||||
|
// would it be easier just to reload the page? lol
|
||||||
|
function reset() {
|
||||||
|
clear_game();
|
||||||
|
// todo clear file inputs
|
||||||
|
inputBrightness.value = 0;
|
||||||
|
inputRoomName.value = "";
|
||||||
|
selectPalette.innerHTML = "";
|
||||||
|
divNewPalette.style.display = "none";
|
||||||
|
inputColourBackground.value = "#000000";
|
||||||
|
inputColourForeground.value = "#ffffff";
|
||||||
|
checkboxDither.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonReset.addEventListener("click", reset);
|
||||||
|
buttonReset.addEventListener("touchend", reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
|
@ -0,0 +1,74 @@
|
||||||
|
use image::Rgba;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ColourMap {
|
||||||
|
background: Rgba<u8>,
|
||||||
|
foreground: Rgba<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColourMap {
|
||||||
|
pub(crate) fn from(palette: &bitsy_parser::Palette) -> ColourMap {
|
||||||
|
let background = Rgba::from([
|
||||||
|
palette.colours[0].red,
|
||||||
|
palette.colours[0].green,
|
||||||
|
palette.colours[0].blue,
|
||||||
|
255,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let foreground = Rgba::from([
|
||||||
|
palette.colours[1].red,
|
||||||
|
palette.colours[1].green,
|
||||||
|
palette.colours[1].blue,
|
||||||
|
255,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ColourMap { background, foreground }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(a: &Rgba<u8>, b:&Rgba<u8>) -> u32 {
|
||||||
|
let diff_red = (a[0] as i16 - b[0] as i16).abs();
|
||||||
|
let diff_green= (a[1] as i16 - b[1] as i16).abs();
|
||||||
|
let diff_blue = (a[2] as i16 - b[2] as i16).abs();
|
||||||
|
|
||||||
|
(diff_red + diff_green + diff_blue) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl image::imageops::colorops::ColorMap for ColourMap {
|
||||||
|
type Color = Rgba<u8>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn index_of(&self, color: &Self::Color) -> usize {
|
||||||
|
let diff_background = diff(color, &self.background);
|
||||||
|
let diff_foreground = diff(color, &self.foreground);
|
||||||
|
|
||||||
|
if diff_foreground <= diff_background { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn lookup(&self, idx: usize) -> Option<Self::Color> {
|
||||||
|
match idx {
|
||||||
|
0 => Some(self.background.into()),
|
||||||
|
1 => Some(self.foreground.into()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicate NeuQuant implements `lookup`.
|
||||||
|
fn has_lookup(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn map_color(&self, color: &mut Self::Color) {
|
||||||
|
let closest = match self.index_of(color) {
|
||||||
|
1 => self.foreground,
|
||||||
|
_ => self.background,
|
||||||
|
};
|
||||||
|
|
||||||
|
color[0] = closest[0];
|
||||||
|
color[1] = closest[1];
|
||||||
|
color[2] = closest[2];
|
||||||
|
color[3] = closest[3];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,395 @@
|
||||||
|
#![feature(clamp)]
|
||||||
|
|
||||||
|
use bitsy_parser::game::Game;
|
||||||
|
use bitsy_parser::image::Image;
|
||||||
|
use bitsy_parser::tile::Tile;
|
||||||
|
use image::{GenericImageView, Pixel, DynamicImage};
|
||||||
|
use image::imageops::ColorMap;
|
||||||
|
use image::imageops::FilterType::CatmullRom;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
mod colour_map;
|
||||||
|
|
||||||
|
use colour_map::ColourMap;
|
||||||
|
|
||||||
|
const SD: u32 = 8;
|
||||||
|
|
||||||
|
enum SelectedPalette {
|
||||||
|
None,
|
||||||
|
Existing {
|
||||||
|
id: String,
|
||||||
|
},
|
||||||
|
New {
|
||||||
|
background: bitsy_parser::Colour,
|
||||||
|
foreground: bitsy_parser::Colour,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
game: Option<Game>,
|
||||||
|
image: Option<DynamicImage>,
|
||||||
|
room_name: Option<String>,
|
||||||
|
palette: SelectedPalette,
|
||||||
|
dither: bool,
|
||||||
|
brightness: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref STATE: Mutex<State> = Mutex::new(
|
||||||
|
State {
|
||||||
|
game: None,
|
||||||
|
image: None,
|
||||||
|
room_name: None,
|
||||||
|
palette: SelectedPalette::None,
|
||||||
|
dither: true,
|
||||||
|
brightness: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tile_name(prefix: &Option<String>, x: &u32, y: &u32) -> Option<String> {
|
||||||
|
if let Some(prefix) = prefix {
|
||||||
|
Some(format!("{} ({},{})", prefix, x, y))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn load_default_game() {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
state.game = Some(bitsy_parser::mock::game_default());
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
pub fn load_game(game_data: String) -> String {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
let result = Game::from(game_data);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok((game, _errs)) => {
|
||||||
|
let palette_id = game.palette_ids()[0].clone();
|
||||||
|
state.game = Some(game);
|
||||||
|
state.palette = SelectedPalette::Existing { id: palette_id };
|
||||||
|
format!("Loaded game")
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
state.game = None;
|
||||||
|
state.palette = SelectedPalette::None;
|
||||||
|
format!("{}", result.err().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn load_image(image_base64: String) -> String {
|
||||||
|
let mut state = STATE.lock().expect("Couldn't lock application state");
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
match base64::decode(image_base64) {
|
||||||
|
Ok(image) => {
|
||||||
|
match image::load_from_memory(image.as_ref()) {
|
||||||
|
Ok(image) => {
|
||||||
|
let size = format!("{}×{}", image.width(), image.height());
|
||||||
|
// todo get rid of magic numbers! what about Bitsy HD?
|
||||||
|
let image = image.resize(128, 128, CatmullRom);
|
||||||
|
state.image = Some(image);
|
||||||
|
size
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
state.image = None;
|
||||||
|
"Error: Couldn't load image".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
state.image = None;
|
||||||
|
"Error: Couldn't decode image".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_dither(dither: bool) {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
state.dither = dither;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_palette(palette_id: &str, background: String, foreground: String) {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
state.palette = match palette_id {
|
||||||
|
"NEW_PALETTE" => SelectedPalette::New {
|
||||||
|
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() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_room_name(room_name: String) {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
match room_name.is_empty() {
|
||||||
|
true => { state.room_name = None },
|
||||||
|
false => { state.room_name = Some(room_name) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn set_brightness(brightness: i32) {
|
||||||
|
let mut state = STATE.lock().unwrap();
|
||||||
|
state.brightness = brightness;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn get_palettes() -> String {
|
||||||
|
let state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
let mut palette_objects = json::JsonValue::new_array();
|
||||||
|
|
||||||
|
for palette in &state.game.as_ref().unwrap().palettes {
|
||||||
|
let mut object = json::JsonValue::new_object();
|
||||||
|
|
||||||
|
object.insert("id", palette.id.clone()).unwrap();
|
||||||
|
|
||||||
|
object.insert(
|
||||||
|
"name",
|
||||||
|
palette.name.clone().unwrap_or(
|
||||||
|
format!("Palette {}", palette.id))
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
palette_objects.push(object).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
json::stringify(palette_objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_to_base64(image: &DynamicImage) -> String {
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
image.write_to(&mut bytes, image::ImageOutputFormat::Png).unwrap();
|
||||||
|
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 {
|
||||||
|
let mut buffer = state.image.as_ref().unwrap().clone().into_rgba();
|
||||||
|
|
||||||
|
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_from(background, foreground),
|
||||||
|
};
|
||||||
|
|
||||||
|
let colour_map = crate::ColourMap::from(&palette);
|
||||||
|
|
||||||
|
// adjust brightness
|
||||||
|
let mut buffer = image::imageops::brighten(&mut buffer, state.brightness);
|
||||||
|
|
||||||
|
if state.dither {
|
||||||
|
image::imageops::dither(&mut buffer, &colour_map);
|
||||||
|
} else {
|
||||||
|
// just do colour indexing
|
||||||
|
let indices = image::imageops::colorops::index_colors(&mut buffer, &colour_map);
|
||||||
|
|
||||||
|
// todo get rid of magic numbers! what about Bitsy HD?
|
||||||
|
buffer = image::ImageBuffer::from_fn(128, 128, |x, y| -> image::Rgba<u8> {
|
||||||
|
let p = indices.get_pixel(x, y);
|
||||||
|
|
||||||
|
colour_map
|
||||||
|
.lookup(p.0[0] as usize)
|
||||||
|
.expect("indexed colour out-of-range")
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
image::DynamicImage::ImageRgba8(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn get_preview() -> String {
|
||||||
|
let state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
match &state.image.is_some() {
|
||||||
|
true => image_to_base64(&render_preview(&state)),
|
||||||
|
false => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn add_room() -> String {
|
||||||
|
let mut state = STATE.lock().expect("Couldn't lock application state");
|
||||||
|
|
||||||
|
if state.game.is_none() {
|
||||||
|
return "No game data loaded".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
match &state.palette {
|
||||||
|
SelectedPalette::None => { return "No palette selected".to_string(); },
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut game = state.game.clone().unwrap();
|
||||||
|
|
||||||
|
if state.image.is_none() {
|
||||||
|
return "No image loaded".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let palette_id = Some(match &state.palette {
|
||||||
|
SelectedPalette::None => bitsy_parser::mock::game_default().palettes[0].id.clone(),
|
||||||
|
SelectedPalette::Existing { id } => id.clone(),
|
||||||
|
SelectedPalette::New { background, foreground } => {
|
||||||
|
game.add_palette(palette_from(background, foreground))
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let foreground = &game.palettes
|
||||||
|
.iter()
|
||||||
|
.find(|&palette| &palette.id == palette_id.as_ref().unwrap())
|
||||||
|
.unwrap().colours[1].clone();
|
||||||
|
|
||||||
|
let preview = render_preview(&state);
|
||||||
|
|
||||||
|
let width = 128;
|
||||||
|
let height = 128;
|
||||||
|
let columns = (width as f64 / SD as f64).floor() as u32;
|
||||||
|
let rows = (height as f64 / SD as f64).floor() as u32;
|
||||||
|
|
||||||
|
let mut tile_ids = Vec::new();
|
||||||
|
let initial_tile_count = game.tiles.len();
|
||||||
|
|
||||||
|
for row in 0..rows {
|
||||||
|
for column in 0..columns {
|
||||||
|
let mut pixels = Vec::with_capacity(64);
|
||||||
|
|
||||||
|
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 }
|
||||||
|
};
|
||||||
|
|
||||||
|
for y in (row * SD)..((row + 1) * SD) {
|
||||||
|
for x in (column * SD)..((column + 1) * SD) {
|
||||||
|
let pixel = preview.get_pixel(x, y).to_rgb();
|
||||||
|
pixels.push(colour_match(&pixel, foreground));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tile = Tile {
|
||||||
|
// "0" will get overwritten to a new, safe tile ID
|
||||||
|
id: "0".to_string(),
|
||||||
|
name: tile_name(&state.room_name, &column, &row),
|
||||||
|
wall: None,
|
||||||
|
animation_frames: vec![Image { pixels }],
|
||||||
|
colour_id: None
|
||||||
|
};
|
||||||
|
|
||||||
|
let tile_id = if game.tiles.contains(&tile) {
|
||||||
|
game.tiles.iter().find(|&t| t == &tile).unwrap().id.clone()
|
||||||
|
} else {
|
||||||
|
game.add_tile(tile)
|
||||||
|
};
|
||||||
|
|
||||||
|
tile_ids.push(tile_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game.add_room(bitsy_parser::Room {
|
||||||
|
id: "0".to_string(),
|
||||||
|
palette_id,
|
||||||
|
name: state.room_name.clone(),
|
||||||
|
tiles: tile_ids,
|
||||||
|
items: vec![],
|
||||||
|
exits: vec![],
|
||||||
|
endings: vec![],
|
||||||
|
walls: None
|
||||||
|
});
|
||||||
|
|
||||||
|
game.dedupe_tiles();
|
||||||
|
|
||||||
|
let new_tile_count = game.tiles.len();
|
||||||
|
|
||||||
|
state.game = Some(game.to_owned());
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"Added room \"{}\" with {} new tiles",
|
||||||
|
&state.room_name.as_ref().unwrap_or(&"untitled".to_string()),
|
||||||
|
new_tile_count - initial_tile_count
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn output() -> String {
|
||||||
|
let state = STATE.lock().unwrap();
|
||||||
|
|
||||||
|
match &state.game {
|
||||||
|
Some(game) => game.to_string(),
|
||||||
|
None => "No game loaded".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{add_room, load_image, load_default_game, output, get_preview, set_room_name};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn image_to_base64() {
|
||||||
|
let image = image::load_from_memory(include_bytes!("test-resources/test.png")).unwrap();
|
||||||
|
let output = crate::image_to_base64(&image);
|
||||||
|
let expected = include_str!("test-resources/test.png.base64").trim();
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_palettes() {
|
||||||
|
load_default_game();
|
||||||
|
let output = crate::get_palettes();
|
||||||
|
let expected = "[{\"id\":\"0\",\"name\":\"blueprint\"}]";
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_preview() {
|
||||||
|
load_default_game();
|
||||||
|
load_image(include_str!("test-resources/colour_input.png.base64").trim().to_string());
|
||||||
|
let output = get_preview();
|
||||||
|
let expected = include_str!("test-resources/colour_input.png.base64.greyscale").trim();
|
||||||
|
assert_eq!(output, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example() {
|
||||||
|
load_default_game();
|
||||||
|
load_image(include_str!("test-resources/test.png.base64").trim().to_string());
|
||||||
|
set_room_name("test".to_string());
|
||||||
|
println!("add_room(): {}", add_room());
|
||||||
|
// todo what? why are extraneous pixels appearing in the output tiles?
|
||||||
|
assert_eq!(output(), include_str!("test-resources/expected.bitsy"));
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,159 @@
|
||||||
|
Write your game's title here
|
||||||
|
|
||||||
|
# BITSY VERSION 7.2
|
||||||
|
|
||||||
|
! ROOM_FORMAT 1
|
||||||
|
|
||||||
|
PAL 0
|
||||||
|
NAME blueprint
|
||||||
|
0,82,204
|
||||||
|
128,159,255
|
||||||
|
255,255,255
|
||||||
|
|
||||||
|
ROOM 0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,0,0,0,0,0,0,0,0,0,0,0,0,a,0
|
||||||
|
0,a,a,a,a,a,a,a,a,a,a,a,a,a,a,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
NAME example room
|
||||||
|
PAL 0
|
||||||
|
|
||||||
|
ROOM 1
|
||||||
|
a,a,1,1,2,2,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
NAME test
|
||||||
|
PAL 0
|
||||||
|
|
||||||
|
TIL a
|
||||||
|
11111111
|
||||||
|
10000001
|
||||||
|
10000001
|
||||||
|
10011001
|
||||||
|
10011001
|
||||||
|
10000001
|
||||||
|
10000001
|
||||||
|
11111111
|
||||||
|
NAME block
|
||||||
|
|
||||||
|
TIL 1
|
||||||
|
00011000
|
||||||
|
00111000
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00111100
|
||||||
|
NAME test (2,0)
|
||||||
|
|
||||||
|
TIL 2
|
||||||
|
00111100
|
||||||
|
01100110
|
||||||
|
01100110
|
||||||
|
00001100
|
||||||
|
00011000
|
||||||
|
00110000
|
||||||
|
01100000
|
||||||
|
01111110
|
||||||
|
NAME test (4,0)
|
||||||
|
|
||||||
|
TIL 3
|
||||||
|
00111100
|
||||||
|
01100110
|
||||||
|
01100110
|
||||||
|
00001100
|
||||||
|
00001100
|
||||||
|
01100110
|
||||||
|
01100110
|
||||||
|
00111100
|
||||||
|
NAME test (6,0)
|
||||||
|
|
||||||
|
SPR A
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00011000
|
||||||
|
00111100
|
||||||
|
01111110
|
||||||
|
10111101
|
||||||
|
00100100
|
||||||
|
00100100
|
||||||
|
POS 0 4,4
|
||||||
|
|
||||||
|
SPR a
|
||||||
|
00000000
|
||||||
|
00000000
|
||||||
|
01010001
|
||||||
|
01110001
|
||||||
|
01110010
|
||||||
|
01111100
|
||||||
|
00111100
|
||||||
|
00100100
|
||||||
|
NAME cat
|
||||||
|
DLG 0
|
||||||
|
POS 0 8,12
|
||||||
|
|
||||||
|
ITM 0
|
||||||
|
00000000
|
||||||
|
00000000
|
||||||
|
00000000
|
||||||
|
00111100
|
||||||
|
01100100
|
||||||
|
00100100
|
||||||
|
00011000
|
||||||
|
00000000
|
||||||
|
NAME tea
|
||||||
|
DLG 1
|
||||||
|
|
||||||
|
ITM 1
|
||||||
|
00000000
|
||||||
|
00111100
|
||||||
|
00100100
|
||||||
|
00111100
|
||||||
|
00010000
|
||||||
|
00011000
|
||||||
|
00010000
|
||||||
|
00011000
|
||||||
|
NAME key
|
||||||
|
DLG 2
|
||||||
|
|
||||||
|
DLG 0
|
||||||
|
I'm a cat
|
||||||
|
NAME cat dialog
|
||||||
|
|
||||||
|
DLG 1
|
||||||
|
You found a nice warm cup of tea
|
||||||
|
NAME tea dialog
|
||||||
|
|
||||||
|
DLG 2
|
||||||
|
A key! {wvy}What does it open?{wvy}
|
||||||
|
NAME key dialog
|
||||||
|
|
||||||
|
VAR a
|
||||||
|
42
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 523 B |
|
@ -0,0 +1 @@
|
||||||
|
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAcJJREFUeJzt19FqgzAAQFEz+v+/nD1t2NLWsQgW7jlP2ogVvUYdc865LRhj/C7/7Gr/29678cfDeNxmdZznbtv2vwu2H39cPtr26H/mnNuc8259ZZzXvs7a0dHJdjE+022/sjKFrzh6jKyO89ppM8AZrgqw7C6AMcbbk3s0vmL1BfLZewDHPmIGcOdfZ5z1GbjymfbqEI7u5nf7EMvfjG3bzJVhH/EI4DoCiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHEfQM+SHkFb+/YEwAAAABJRU5ErkJggg==
|
Loading…
Reference in New Issue