Enhanced to have PNG download and scaling

Original updates started by @ Pikapoo on the Pinecil community chat
Further improvements and png work by Robert Lipe <robertlipe@gmail.com>
This commit is contained in:
Ben V. Brown
2022-02-15 18:29:21 +11:00
parent feffc3025c
commit 340403656c

View File

@@ -1,282 +1,491 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="translations_commons.js"></script> <script src="translations_commons.js"></script>
<title>TS100 Bitmap Editor</title> <title>TS100 Bitmap Editor</title>
<style> <style id="styles">
.matrix { .matrix {
display: inline-block; display: inline-block;
padding: 0px 0px 1px 1px ; padding: 0px 0px 1px 1px;
background-color: #666; background-color: #666;
margin-top: 1em; margin-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.matrix * { .matrix * {
font-size:0; font-size: 0;
} }
.c { .r {
margin:1px 1px 0px 0px; white-space: nowrap;
display: inline-block; }
background-color: #fff; .c {
height:10px; margin: 1px 1px 0px 0px;
width: 10px; display: inline-block;
} background-color: #fff;
height: 10px;
width: 10px;
}
.x { .x {
background-color: #000; background-color: #000;
} }
.header { .header {
} }
.data input, .data textarea { .data input,
margin-top: 1em; .data textarea {
width: 100%; margin-top: 1em;
} width: 100%;
}
.actions { .actions {
} }
</style> </style>
<script> <script>
var ink, pressed, ev;
function mousedown(e) {
c = window.event.target;
classes = c.className.split(" ");
if (classes.indexOf("c") < 0) {
return;
}
ink = classes.indexOf("x") < 0;
pressed = true;
ev = e;
enter(e);
}
var ink, pressed, ev; function mouseup(e) {
function mousedown(e) { ev = e;
c = window.event.target; pressed = false;
classes = c.className.split(" "); }
if (classes.indexOf("c")<0) {
return;
}
ink = classes.indexOf("x")<0;
pressed = true;
ev = e;
enter(e);
}
function mouseup(e) { function enter(e) {
ev = e; if (!pressed) {
pressed = false; return;
} }
ev = e;
c = window.event.target;
paint(c, ink);
stringFromMatrix();
}
function enter(e) { function paint(c, ink) {
if (!pressed) { var cellInk = isInk(c);
return; if (ink) {
} if (!cellInk) {
ev = e; c.className += " x";
c = window.event.target; }
paint(c, ink); } else {
stringFromMatrix(); if (cellInk) {
} c.className = "c";
}
}
}
function paint(c, ink) { function isInk(c) {
var cellInk = isInk(c); try {
if (ink) { var classes = c.className.split(" ");
if (!cellInk) { return classes.indexOf("x") >= 0;
c.className += " x"; } catch (e) {
} return false;
} else { }
if (cellInk) { }
c.className = "c";
}
}
}
function isInk(c) { function getMatrix() {
try { return document.getElementById("matrix");
var classes = c.className.split(" "); }
return classes.indexOf("x") >= 0;
} catch(e) {
return false;
}
}
function getMatrix() { function getCoordinatesFromId(str) {
return document.getElementById("matrix"); i = str.indexOf("_");
} return {
row: parseInt(str.substring(1, i)),
col: parseInt(str.substring(i + 1)),
};
}
function getCoordinatesFromId(str) { function clearMatrix() {
i = str.indexOf('_'); for (var r = 0; r < app.matrix.rows; r++) {
return { for (var c = 0; c < app.matrix.cols; c++) {
row: parseInt(str.substring(1, i)), paint(getCell(r, c), false);
col: parseInt(str.substring(i+1)) }
} }
} }
function clearMatrix() { function invertMatrix() {
for (var r = 0; r < app.matrix.rows; r++) { for (var r = 0; r < app.matrix.rows; r++) {
for (var c = 0; c < app.matrix.cols; c++) { for (var c = 0; c < app.matrix.cols; c++) {
paint(getCell(r, c), false); cell = getCell(r, c);
} if (isInk(cell) == true) paint(cell, false);
} else paint(cell, true);
} }
}
stringFromMatrix();
}
function getCell(row, col) { function getCell(row, col) {
return document.getElementById("C"+row+"_"+col); return document.getElementById("C" + row + "_" + col);
} }
function toMatrix(str) { function toMatrix(str) {
app.encodedData = str; app.encodedData = str;
clearMatrix(); clearMatrix();
var strs = str.split(/[ ,]/); var strs = str.split(/[ ,]/);
var pair = false; var pair = false;
var c = 0; var c = 0;
var rs = 7; var rs = 7;
for (var i = 0; i<strs.length; i++) { for (var i = 0; i < strs.length; i++) {
var d = strs[i]; var d = strs[i];
if (d.length > 0) { if (d.length > 0) {
if (startsWith(d, "0x")) { if (startsWith(d, "0x")) {
v = parseInt(d.substring(2), 16); v = parseInt(d.substring(2), 16);
} else { } else {
v = parseInt(d); v = parseInt(d);
} }
sv = padLeft(v.toString(2), "0", 8); sv = padLeft(v.toString(2), "0", 8);
for (r = 0; r < 8; r++) { for (r = 0; r < 8; r++) {
paint(getCell(rs - r, c), sv.charAt(r) == '1'); paint(getCell(rs - r, c), sv.charAt(r) == "1");
} }
c++; c++;
if (c >= app.matrix.cols) { if (c >= app.matrix.cols) {
c = 0; c = 0;
rs += 8; rs += 8;
} }
} }
} }
stringFromMatrix(true, false); stringFromMatrix(true, false);
} }
function escapedToMatrix(str) { function escapedToMatrix(str) {
app.encodedEscapeSequence = str; app.encodedEscapeSequence = str;
clearMatrix(); clearMatrix();
var strs = str.split("\\x"); var strs = str.split("\\x");
var c = 0; var c = 0;
var rs = 7; var rs = 7;
for (var i = 0; i<strs.length; i++) { for (var i = 0; i < strs.length; i++) {
var d = strs[i]; var d = strs[i];
if (d.length > 0) { if (d.length > 0) {
v = parseInt(d, 16); v = parseInt(d, 16);
sv = padLeft(v.toString(2), "0", 8); sv = padLeft(v.toString(2), "0", 8);
for (r = 0; r < 8; r++) { for (r = 0; r < 8; r++) {
paint(getCell(rs - r, c), sv.charAt(r) == '1'); paint(getCell(rs - r, c), sv.charAt(r) == "1");
} }
c++; c++;
if (c >= app.matrix.cols) { if (c >= app.matrix.cols) {
c = 0; c = 0;
rs += 8; rs += 8;
} }
} }
} }
stringFromMatrix(false, true); stringFromMatrix(false, true);
} }
function stringFromMatrix(skipEncodedData, skipEncodedEscapeSequence) { // Rather than trying to figure these crazy cells/matrix, we just
var str = ""; // slurp up the encoded string at the end. It's updated on every
var strEscaped = ""; // pixl change, redraw, and load so just slipping into
var delim = ""; // stringFromMatrix is tacky, but seemss to catch all our refreshes.
var blocks = app.matrix.rows / 8; //
var rs = 7; // The string is CSV hex, with the first byte being the first column,
for (var block = 0; block < blocks; block++) { // and bit zero being the UL corner. Second byte is second column, etc.
for (var c = 0; c < app.matrix.cols; c++) { // app.matrix.{cols,rows} is set by the drawing code to size the
var b = 0; // image for a character, an icon, or the full screen image. This
for (var r = 0; r < 8; r++) { // code adapts resizing from that.
var cell = document.getElementById("C"+(rs-r)+"_"+c); // INVERT is handled by the code above us, our fill/clearRect handles
if (isInk(cell)) { // that.
b |= (1 << (7-r)); function updateCanvas(buf) {
} // Number of squared canvas pixels to image pixels;
} var scale = 1;
str += delim + "0x" + padLeft(b.toString(16).toUpperCase(), "0", 2);
strEscaped += "\\x" + padLeft(b.toString(16).toUpperCase(), "0", 2);
delim = ",";
}
rs += 8;
}
if (!skipEncodedData) {
app.encodedData = str;
}
if (!skipEncodedEscapeSequence) {
app.encodedEscapeSequence = strEscaped;
}
return str;
}
function start() { var c = document.getElementById("myCanvas");
app = new Vue({ var context = c.getContext("2d");
el : '#app', context.fillRect(0, 0, c.width, c.height);
data : {
matrix: {
cols: 12,
rows: 16
},
type: "big",
encodedData: "",
encodedEscapeSequence: "",
},
methods : {
VtoMatrix : function(val) {
toMatrix(val);
},
escapedToMatrix : function(val) {
escapedToMatrix(val);
},
VchangeSize : function() { if (c.width != app.matrix.cols || c.height != app.matrix.rows) {
if (app.type == "big") { c.width = app.matrix.cols * scale;
app.matrix.cols = 12; c.height = app.matrix.rows * scale;
app.matrix.rows = 16; }
} else if (app.type == "small") { context.clearRect(0, 0, c.width, c.height);
app.matrix.cols = 6;
app.matrix.rows = 8;
} else if (app.type == "icon") {
app.matrix.cols = 16;
app.matrix.rows = 16;
} else if (app.type == "icon24") {
app.matrix.cols = 24;
app.matrix.rows = 16;
} else if (app.type == "screen") {
app.matrix.cols = 84;
app.matrix.rows = 16;
} else if (app.type == "fullscreen") {
app.matrix.cols = 96;
app.matrix.rows = 16;
}
stringFromMatrix();
}
} var a = buf.split(",");
}); var x = 0;
toMatrix("0x00,0xF0,0x08,0x0E,0x02,0x02,0x02,0x02,0x0E,0x08,0xF0,0x00,0x00,0x3F,0x40,0x5C,0x5C,0x5C,0x5C,0x5C,0x5C,0x40,0x3F,0x00"); var y = 0;
}
window.onload=start; for (var e = 0; e < a.length; e++) {
</script> byte = parseInt(a[e], 16);
for (var bit = 0; bit < 8; bit++) {
// debug.innerHTML+= e + ": " + x + "/" + y + " " + a.length + "</br>";
// debug.innerHTML+= app.matrix.cols + "</br>";
if (x > c.cols) {
throw "write past right of canvas";
}
if (x > c.rows) {
throw "write past bottom of canvas";
}
if (byte & (1 << bit)) {
// FillRect give better B&W image
if (scale > 1) {
context.moveTo(x, y);
context.lineWidth = scale;
context.lineTo(x + scale, y);
} else {
context.beginPath();
context.fillRect(x, y, 1, 1);
context.fill();
}
}
y += scale;
}
y -= 8 * scale;
x += scale;
if (x == app.matrix.cols * scale) {
x = 0;
y = 8 * scale;
}
// debug.innerHTML+= x + " " + x/app.matrix.cols + " " + y + "</br>";
// debug.innerHTML+=byte + "</br>";
}
context.strokeStyle = "black";
context.stroke();
return c;
}
</head> function makePNG() {
<body> var canvas = document.getElementById("myCanvas");
<div id="app"> //var context = c.getContext("2d");
<div class="header"> //window.location = canvas.toDataURL("image/png");
<select v-model="type" v-on:change="VchangeSize()"> ///var image = canvas.toDataURL("image/png");
<option value="small">Small Font (6x8)</option> // document.write('<img src="'+image+'"/>');
<option value="big">Big Font (12x16)</option> var image = canvas
<option value="icon">Icon (16x16)</option> .toDataURL("image/png")
<option value="icon24">Icon (24x16)</option> .replace("image/png", "image/octet-stream");
<option value="screen">Screen (84x16)</option> window.location.href = image;
<option value="fullscreen">Full Screen (96x16)</option> }
</select>
</div>
<div id="matrix" class="matrix" onmousedown="mousedown(this)" onmouseup="mouseup(this)" ondragstart="return false">
<div :id="'R'+(r-1)" class="r" v-for="r in matrix.rows">
<div :id="'C'+(r-1)+'_'+(c-1)" class="c" onmouseenter="enter(this)" v-for="c in matrix.cols"></div>
</div>
</div>
<div class="actions">
<input type="button" value="Clear" onclick="clearMatrix();stringFromMatrix()">
</div>
<div class="data">
<textarea v-model="encodedData" style="width:100%" v-on:change="VtoMatrix(encodedData)" rows=5></textarea>
<textarea v-model="encodedEscapeSequence" style="width:100%" v-on:change="escapedToMatrix(encodedEscapeSequence)" rows=5></textarea>
</div>
</div>
function stringFromMatrix(skipEncodedData, skipEncodedEscapeSequence) {
var str = "";
var strEscaped = "";
var delim = "";
var blocks = app.matrix.rows / 8;
var rs = 7;
for (var block = 0; block < blocks; block++) {
for (var c = 0; c < app.matrix.cols; c++) {
var b = 0;
for (var r = 0; r < 8; r++) {
var cell = document.getElementById("C" + (rs - r) + "_" + c);
if (isInk(cell)) {
b |= 1 << (7 - r);
}
}
str += delim + "0x" + padLeft(b.toString(16).toUpperCase(), "0", 2);
strEscaped += "\\x" + padLeft(b.toString(16).toUpperCase(), "0", 2);
delim = ",";
}
rs += 8;
}
if (!skipEncodedData) {
app.encodedData = str;
}
if (!skipEncodedEscapeSequence) {
app.encodedEscapeSequence = strEscaped;
}
updateCanvas(str);
return str;
}
</body> function start() {
app = new Vue({
el: "#app",
data: {
matrix: {
cols: 12,
rows: 16,
},
type: "big",
encodedData: "",
encodedEscapeSequence: "",
},
methods: {
VtoMatrix: function (val) {
toMatrix(val);
},
escapedToMatrix: function (val) {
escapedToMatrix(val);
},
VchangeSize: function () {
if (app.type == "big") {
app.matrix.cols = 12;
app.matrix.rows = 16;
} else if (app.type == "small") {
app.matrix.cols = 6;
app.matrix.rows = 8;
} else if (app.type == "icon") {
app.matrix.cols = 16;
app.matrix.rows = 16;
} else if (app.type == "icon24") {
app.matrix.cols = 24;
app.matrix.rows = 16;
} else if (app.type == "screen") {
app.matrix.cols = 84;
app.matrix.rows = 16;
} else if (app.type == "fullscreen") {
app.matrix.cols = 96;
app.matrix.rows = 16;
}
stringFromMatrix();
},
},
});
toMatrix(
"0x00,0xF0,0x08,0x0E,0x02,0x02,0x02,0x02,0x0E,0x08,0xF0,0x00,0x00,0x3F,0x40,0x5C,0x5C,0x5C,0x5C,0x5C,0x5C,0x40,0x3F,0x00"
);
}
var margins = 1;
function changesize(x) {
var cursize = x;
var mg;
if (x < 6) mg = 0;
else mg = 1;
// var elements = document.getElementsByClassName('c');
// for (var i=0; i<elements.length;i++){
// elements.item(i).style="height: "+x+"px; width: "+x+"px;"+mg;
// }
styles.sheet.rules[3].style.height = x + "px";
styles.sheet.rules[3].style.width = x + "px";
styles.sheet.rules[3].style.marginRight = mg + "px";
styles.sheet.rules[3].style.marginTop = mg + "px";
styles.sheet.rules[0].style.paddingLeft = mg + "px";
styles.sheet.rules[0].style.paddingBottom = mg + "px";
}
function importFile() {
var input, file, fr;
input = document.getElementById("fileinput");
if (input.files[0]) {
file = input.files[0];
fr = new FileReader();
fr.onload = processData;
fr.readAsBinaryString(file);
}
function processData() {
var pushy, data, aB, bS;
pushy = [];
// bodyAppend("p","processing data");
data = fr.result;
for (i = 297; i < 297 + 192; i += 2) {
aB = data.charCodeAt(i + 1);
bS = aB.toString(16);
if (bS.length < 2) bS = "0" + bS;
pushy.push(bS);
aB = data.charCodeAt(i);
bS = aB.toString(16);
if (bS.length < 2) bS = "0" + bS;
pushy.push(bS);
}
escapedToMatrix("\\x" + pushy.join("\\x"));
// bodyAppend("p","\\x"+pushy.join("\\x"));
}
}
function bodyAppend(tagName, innerHTML) {
var elm;
elm = document.createElement(tagName);
elm.innerHTML = innerHTML;
document.body.appendChild(elm);
}
window.onload = start;
</script>
</head>
<body>
<div id="app">
<div class="header">
<select v-model="type" v-on:change="VchangeSize()">
<option value="small">Small Font (6x8)</option>
<option value="big">Big Font (12x16)</option>
<option value="icon">Icon (16x16)</option>
<option value="icon24">Icon (24x16)</option>
<option value="screen">Screen (84x16)</option>
<option value="fullscreen">Full Screen (96x16)</option>
</select>
<a href="#" onclick="changesize(1);">1x</a>
<a href="#" onclick="changesize(2);">2x</a>
<a href="#" onclick="changesize(4);">4x</a>
<a href="#" onclick="changesize(8);">8x</a>
<a href="#" onclick="changesize(10);">10x</a>
<a href="#" onclick="changesize(12);">12x</a>
<a href="#" onclick="changesize(16);">16x</a>
<a href="#" onclick="changesize(32);">32x</a>
<a href="#" onclick="invertMatrix();">INVERT!</a>
</div>
<div
id="matrix"
class="matrix"
onmousedown="mousedown(this)"
onmouseup="mouseup(this)"
ondragstart="return false"
>
<div :id="'R'+(r-1)" class="r" v-for="r in matrix.rows">
<div
:id="'C'+(r-1)+'_'+(c-1)"
class="c"
onmouseenter="enter(this)"
v-for="c in matrix.cols"
></div>
</div>
</div>
<div class="actions">
<input
type="button"
value="Clear"
onclick="clearMatrix();stringFromMatrix()"
/>
</div>
<div class="data">
<textarea
v-model="encodedData"
style="width: 100%"
v-on:change="VtoMatrix(encodedData)"
rows="5"
></textarea>
<textarea
v-model="encodedEscapeSequence"
style="width: 100%"
v-on:change="escapedToMatrix(encodedEscapeSequence)"
rows="5"
></textarea>
</div>
<form action="#" onsubmit="return false;">
<input type="file" id="fileinput" />
<input
type="button"
id="btnLoad"
value="Import"
onclick="importFile();"
/>
(Remember to set correct canvas size before importing)
</form>
<br />
<canvas
id="myCanvas"
width="96"
height="16"
style="border: 1px dotted #000000; padding: 10px"
>
</canvas>
<form>
<input type="button" value="Make PNG" onclick="makePNG();" />
</form>
<div id="debug"></div>
</div>
</body>
</html> </html>