Compare commits
No commits in common. "f135e184e4d8cafb89c8ad1e66539401f5d8d8fc" and "72dbff8d5eb84feb4e4f0ee23e90e8fb8b7647c3" have entirely different histories.
f135e184e4
...
72dbff8d5e
|
@ -0,0 +1,17 @@
|
||||||
|
# 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,3 +1,6 @@
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# Windows image file caches
|
# Windows image file caches
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
|
@ -50,10 +53,8 @@ Temporary Items
|
||||||
*.zip
|
*.zip
|
||||||
itch*
|
itch*
|
||||||
|
|
||||||
|
# node
|
||||||
|
node_modules
|
||||||
|
|
||||||
# IDE stuff
|
# IDE stuff
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
/Cargo.lock
|
|
||||||
/dist/
|
|
||||||
/index.html
|
|
||||||
/includes/style.css
|
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -1,19 +0,0 @@
|
||||||
[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...
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
6
TODO.md
6
TODO.md
|
@ -1,6 +0,0 @@
|
||||||
# todo
|
|
||||||
|
|
||||||
* tile reuse
|
|
||||||
* noise reduction (remove lonely pixels)
|
|
||||||
* implement Atkinson and Bayer dithering options
|
|
||||||
* fix weird problem with pixels flipping (see test::example)
|
|
|
@ -0,0 +1 @@
|
||||||
|
theme: jekyll-theme-dinky
|
5
build.sh
5
build.sh
|
@ -1,5 +1,4 @@
|
||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
pug index.pug
|
pug index.pug index.html
|
||||||
lessc includes/style.less includes/style.css
|
lessc includes/style.less includes/style.css
|
||||||
wasm-pack build --target web
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#! /usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
rm -rf dist
|
zip -r image-to-bitsy.zip readme.md index.html includes/
|
||||||
mkdir dist
|
butler push image-to-bitsy.zip ruin/image-to-bitsy:html
|
||||||
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
|
@ -100,7 +100,7 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBitsyGameData() {
|
function handleBitsyGameData() {
|
||||||
let input = $('#bitsy-data').val();
|
let input = $bitsyData.val();
|
||||||
|
|
||||||
if ( ! input) {
|
if ( ! input) {
|
||||||
return;
|
return;
|
||||||
|
@ -488,7 +488,7 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#save').on('click touchend', function() {
|
$('#save').on('click touchend', function() {
|
||||||
$textArea = $('textarea');
|
let $textArea = $('textarea');
|
||||||
|
|
||||||
let newGameData = $textArea.val();
|
let newGameData = $textArea.val();
|
||||||
|
|
|
@ -1,133 +1,144 @@
|
||||||
@background: #57506a;
|
@font-face {
|
||||||
@page-background: #968eb5;
|
font-family: 'rubikregular';
|
||||||
@accent: #ec6d7d;
|
src: url('rubik-regular-webfont.woff2') format('woff2'),
|
||||||
@text: #464256;
|
url('rubik-regular-webfont.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@light: #d3cbd0;
|
||||||
|
@dark: #594a54;
|
||||||
|
@accent: #f69f8f; // pink
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: @text;
|
font-family: "rubikregular", sans-serif;
|
||||||
margin: 0 auto 0.5em auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
background-color: @background;
|
background-color: @dark;
|
||||||
font-size: 3vmin;
|
color: @light;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
a {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
canvas {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
input[type="file"] {
|
||||||
font-size: 0.9em;
|
padding: 1em 0;
|
||||||
|
width: 256px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input[type="text"], button {
|
||||||
width: 100%;
|
width: 14em;
|
||||||
text-align: left;
|
margin: 1em;
|
||||||
|
padding: 0.25em;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
&[type="checkbox"] {
|
table {
|
||||||
width: auto;
|
margin: 0 auto;
|
||||||
margin-right: 1em;
|
|
||||||
position: relative;
|
td, th {
|
||||||
top: 0.25em;
|
width: 50%;
|
||||||
left: 0.25em;
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
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 {
|
||||||
height: 15em;
|
.box256;
|
||||||
padding: 0.5em;
|
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea, input {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.box256 {
|
||||||
background-color: @background;
|
height: 256px;
|
||||||
padding: 0.5em;
|
width: 256px;
|
||||||
border-radius: 0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkboxes label {
|
.centre {
|
||||||
margin-right: 1em;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cropper-tools {
|
.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;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.half {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-container {
|
|
||||||
height: 46vh;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
image-rendering: crisp-edges;
|
|
||||||
}
|
|
||||||
|
|
138
index.pug
138
index.pug
|
@ -1,96 +1,96 @@
|
||||||
doctype html
|
doctype html
|
||||||
html(lang="en-gb")
|
html
|
||||||
head
|
head
|
||||||
meta(charset="utf-8")
|
meta(charset="utf-8")
|
||||||
title pixsy
|
title image to bitsy
|
||||||
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.min.js")
|
script(src="includes/croppie.js")
|
||||||
|
|
||||||
|
// main stuff
|
||||||
|
link(rel="stylesheet" type="text/css" href="includes/style.css")
|
||||||
|
script(src="includes/script.js")
|
||||||
body
|
body
|
||||||
header
|
header
|
||||||
h1
|
h1 image-to-bitsy
|
||||||
| pixsy
|
p convert any image to a #[a(href="https://ledoux.itch.io/bitsy") bitsy] room
|
||||||
//img(alt="pixsy" src="includes/pixsy.png")
|
|
||||||
p.
|
p.
|
||||||
convert images to Bitsy rooms
|
#[a(href="https://github.com/synth-ruiner/bitsy-image-to-room") about]
|
||||||
|
|
|
||||||
#[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]
|
||||||
|
|
|
||||||
#[a(href="https://twitter.com/synth_ruiner") twitter]
|
.flex-container
|
||||||
.pages
|
#game-data.section
|
||||||
.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
|
||||||
|
|
||||||
input#game(type="file" autocomplete="off")
|
textarea#bitsy-data(placeholder="Bitsy data or html")
|
||||||
br
|
include includes/default.bitsy
|
||||||
|
|
||||||
textarea#game-data(
|
p
|
||||||
placeholder="Paste your game data here or use the file chooser button above"
|
input.game-data(type="file")
|
||||||
autocomplete="off"
|
p paste or upload your game data (or html) here
|
||||||
)
|
p (maybe make a backup first)
|
||||||
|
|
||||||
button.pagination.prev previous
|
#image.section
|
||||||
button.pagination.next#game-data-next(disabled=true) next
|
|
||||||
.page.image#page-image
|
|
||||||
h2 image
|
h2 image
|
||||||
|
|
||||||
.image-container
|
#croppie
|
||||||
input#image(type="file" accept="image/*")
|
|
||||||
#crop(style="display: none;")
|
|
||||||
|
|
||||||
button.pagination.prev previous
|
input#imageUpload(type="file" accepts="image/*")
|
||||||
button.pagination.next#image-next(disabled=true) next
|
|
||||||
.page.room#page-room
|
|
||||||
h2 room
|
|
||||||
|
|
||||||
table
|
#palette.section
|
||||||
tbody
|
h2 palette
|
||||||
tr
|
|
||||||
td(style="width: 60%")
|
|
||||||
img#preview(alt="preview")
|
|
||||||
br
|
|
||||||
|
|
||||||
label
|
form#palettes
|
||||||
| brightness
|
table
|
||||||
input#brightness(type="range" min=-96 max=96 value=0)
|
tbody
|
||||||
td(style="vertical-align: top;")
|
|
||||||
label
|
|
||||||
| name (optional)
|
|
||||||
input#room-name(type="text" placeholder="e.g. 'bedroom'" autocomplete="off")
|
|
||||||
|
|
||||||
label
|
#crop.section
|
||||||
| palette
|
h2 preview
|
||||||
select#palette
|
|
||||||
|
|
||||||
#new-palette(style="display: none;")
|
canvas#preview(width=128, height=128)
|
||||||
.half
|
|
||||||
input#colour-background(type="color" value="#000000")
|
|
||||||
.half
|
|
||||||
input#colour-foreground(type="color" value="#ffffff")
|
|
||||||
|
|
||||||
label
|
input#brightness(type="range" min=-255 max=255 value=0)
|
||||||
input#dither(type="checkbox" checked=true)
|
|
||||||
| dither
|
|
||||||
br
|
|
||||||
|
|
||||||
button.pagination.prev#back-to-image previous
|
label(for="brightness") brightness
|
||||||
button.pagination.next#room-next add room
|
|
||||||
.page.download
|
|
||||||
p#added
|
|
||||||
|
|
||||||
h2 download
|
//- to do
|
||||||
|
input#dithering(type="checkbox")
|
||||||
|
label(for="dithering") dithering
|
||||||
|
|
||||||
textarea#output(autocomplete="off")
|
//- 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
|
||||||
|
|
||||||
button#download download
|
input#roomName(type="text", placeholder="room name")
|
||||||
|
|
||||||
button.pagination.prev#add add another image
|
button#save write to game data
|
||||||
button.pagination.start#reset start again
|
|
||||||
script(type="module")
|
//- to do
|
||||||
include script.js
|
|
||||||
|
//-label favour broad strokes | favour details
|
||||||
|
|
||||||
|
br
|
||||||
|
|
||||||
|
//-button Download
|
|
@ -1,250 +0,0 @@
|
||||||
.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: '↻';
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 86 KiB |
|
@ -1,144 +0,0 @@
|
||||||
@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;
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
273
script.js
273
script.js
|
@ -1,273 +0,0 @@
|
||||||
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();
|
|
|
@ -1,74 +0,0 @@
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
395
src/lib.rs
395
src/lib.rs
|
@ -1,395 +0,0 @@
|
||||||
#![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.
Before 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
|
@ -1,159 +0,0 @@
|
||||||
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.
Before Width: | Height: | Size: 523 B |
|
@ -1 +0,0 @@
|
||||||
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAcJJREFUeJzt19FqgzAAQFEz+v+/nD1t2NLWsQgW7jlP2ogVvUYdc865LRhj/C7/7Gr/29678cfDeNxmdZznbtv2vwu2H39cPtr26H/mnNuc8259ZZzXvs7a0dHJdjE+022/sjKFrzh6jKyO89ppM8AZrgqw7C6AMcbbk3s0vmL1BfLZewDHPmIGcOdfZ5z1GbjymfbqEI7u5nf7EMvfjG3bzJVhH/EI4DoCiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHEfQM+SHkFb+/YEwAAAABJRU5ErkJggg==
|
|
Loading…
Reference in New Issue