Created
December 30, 2022 14:29
-
-
Save bazzargh/2b2ac2c14ba9e2592991cf143f2d9a66 to your computer and use it in GitHub Desktop.
rudimentary drawing program, that outputs bbc basic code to reproduce the image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<svg viewbox="0 0 255 255" id="svg_canvas" width="512" height="512" style="border: 1px solid black;"> | |
</svg> | |
Click to add points. | |
<form id="form"> | |
<input type="range" id="ppos__slider" min="0" max="0"><input type="text" id="ppos" enabled=false><label for="ppos">Point</label><br> | |
<input type="range" id="xpos__slider" min="0" max="255"><input type="text" id="xpos" enabled=false><label for="xpos">X</label><br> | |
<input type="range" id="ypos__slider" min="0" max="255"><input type="text" id="ypos" enabled=false><label for="ypos">Y</label><br> | |
<input type="range" id="cpos__slider" min="-32" max="32" value="0"><input type="text" id="cpos" enabled=false><label for="cpos">Curvature</label><br> | |
<input type="text" id="image" value=monalisa.jpg><label for="image">Image</label><br> | |
<input type="text" id="offsetx" value=0><label for="offsetx">X Offset</label><br> | |
<input type="text" id="offsety" value=0><label for="offsety">Y Offset</label><br> | |
<input type="text" id="width" value=256><label for="width">Width</label><br> | |
<input type="text" id="height" value=512><label for="height">Height</label><br> | |
<input type="button" id="delete" value="Del"><br> | |
</form> | |
<h2>Program (open in <a href="" target="_blank" id="owlet">owlet</a>)</h2> | |
<pre id="program"> | |
</pre> | |
<script> | |
let lines = [] | |
let image = document.getElementById("image") | |
let width = document.getElementById("width") | |
let height = document.getElementById("height") | |
let offsetx = document.getElementById("offsetx") | |
let offsety = document.getElementById("offsety") | |
let svg_canvas = document.getElementById("svg_canvas") | |
let ppos = document.getElementById('ppos') | |
let xpos = document.getElementById("xpos") | |
let ypos = document.getElementById("ypos") | |
let cpos = document.getElementById("cpos") | |
let ppos__slider = document.getElementById('ppos__slider') | |
let xpos__slider = document.getElementById("xpos__slider") | |
let ypos__slider = document.getElementById("ypos__slider") | |
let cpos__slider = document.getElementById("cpos__slider") | |
function safeChar(n){ | |
return String.fromCharCode((n < 32 || n > 126) ? n+256 : n); | |
} | |
function processNumber(n){ | |
if (isNaN(n)) { | |
return ""; | |
} | |
if (n==10) {return '[WARN: 10 cannot be encoded]';} | |
if (n==13) {return '[WARN: 13 cannot be encoded]';} | |
if (n==34) {return safeChar(n)+safeChar(n);} // escape " | |
return safeChar(n); | |
} | |
function toSafeString(lines) { | |
// the value here should be zero only if we set some flag for the line, but I just made any | |
// straight line return 0 (a line with curvature +/- 1 is pretty straight). There's room here | |
// to use this field for more flags - not just curvature, but fills, circles, colour, rects... | |
let nums = lines.map((e,i)=>[parseInt(e.c)==0?0:64+parseInt(e.c),Math.round(e.x),255-Math.round(e.y)]).flat() | |
nums.shift() | |
return nums.map(processNumber).join("") | |
} | |
function toProgram(lines) { | |
return `REM${toSafeString(lines)} | |
MODE0:GCOL0,129:GCOL0,0:CLG:VDU5,29,140;0;:P=PAGE+5 | |
A=RND(16)+4*?P:B=RND(16)+4*?(P+1) | |
FORI=P+2TOP+${lines.length*3-2}STEP3 | |
V=?I:X=RND(16)+4*?(I+1):Y=RND(16)+4*?(I+2):IFV=0GOTO90ELSEV=64-V | |
IFV<0C=A:D=B:A=X:B=Y ELSEC=X:D=Y | |
IFV<>0U=ABS(8/V-V/128):MOVEA+(C-A)/2-(D-B)*U,B+(D-B)/2+(C-A)*U | |
MOVEA,B:PLOT&A5,C,D | |
A=X:B=Y | |
NEXT` | |
} | |
function draw() { | |
if (lines.length == 0) { | |
ppos__slider.max = 0 | |
ppos__slider.value = 0 | |
xpos__slider.value = 0 | |
ypos__slider.value = 0 | |
cpos__slider.value = 0 | |
} else { | |
ppos__slider.max = lines.length - 1 | |
ppos__slider.value = Math.min(ppos__slider.max, ppos__slider.value) | |
xpos__slider.value = lines[ppos__slider.value].x | |
ypos__slider.value = lines[ppos__slider.value].y | |
cpos__slider.value = lines[ppos__slider.value].c | |
} | |
xpos.value = xpos__slider.value | |
ypos.value = ypos__slider.value | |
ppos.value = ppos__slider.value | |
cpos.value = cpos__slider.value | |
let path=lines.reduce((a, e, i) => { | |
if (i == 0) { | |
a.a = e.x | |
a.b = e.y | |
} else { | |
let r = e.c | |
let c = e.x | |
let d = e.y | |
a.s += `M ${a.a} ${a.b} ` | |
if (r == 0) { | |
a.s += `L ${c} ${d} ` | |
} else { | |
let q = 1/4*(32/r + r/32)*Math.sqrt((c-a.a)*(c-a.a)+(d-a.b)*(d-a.b)) | |
a.s += `A ${Math.abs(q)} ${Math.abs(q)} 0 0 ${(Math.sign(r)+1)/2} ${c} ${d} ` | |
} | |
a.a = c | |
a.b = d | |
} | |
return a | |
}, {"s": "", "a":0, "b":0}) | |
// use url history to make this bookmarkable, also used to save/restore drawings when | |
// I'm hacking on this code. | |
let params = new URLSearchParams() | |
params.set('lines', JSON.stringify(lines)) | |
params.set('image', image.value) | |
params.set('width', width.value) | |
params.set('height', height.value) | |
params.set('offsetx', offsetx.value) | |
params.set('offsety', offsety.value) | |
history.replaceState(null, null, `?${params}`) | |
let l = ppos__slider.value >= lines.length ? {"x":0, "y":0, "c": 0} : lines[ppos__slider.value] | |
svg_canvas.innerHTML = `<g><image href="${image.value}" width="${width.value}" height="${height.value}" x="${offsetx.value}" y="${offsety.value}" /><path fill="transparent" stroke="black" d="${path.s}" /><circle fill="transparent" r=5 cx="${l.x}" cy="${l.y}" stroke="red" /></g>` | |
let program = toProgram(lines) | |
document.getElementById('program').innerText = program | |
document.getElementById('owlet').setAttribute("href", `https://bbcmic.ro/#${encodeURIComponent(JSON.stringify({"v":1,"program":program}))}`) | |
} | |
svg_canvas.addEventListener("click", function(evt) { | |
let pt = DOMPoint.fromPoint(evt.target) | |
pt.x = evt.clientX; | |
pt.y = evt.clientY; | |
let cursorpt = pt.matrixTransform(evt.target.getScreenCTM().inverse()); | |
lines.splice(1 + parseInt(ppos__slider.value), 0, {"c":1, "x":Math.round(cursorpt.x), "y":Math.round(cursorpt.y)}) | |
ppos__slider.max = lines.length - 1 | |
ppos__slider.value = Math.min(ppos__slider.max, ppos__slider.value + 1) | |
draw() | |
}) | |
let f = document.getElementById('form') | |
f.addEventListener("input", function(evt) { | |
if (evt.target.type == "range") { | |
let text = document.getElementById(evt.target.id.substring(0,4)) | |
text.value = evt.target.value | |
} | |
if (evt.target != ppos__slider) { | |
lines[ppos__slider.value].c = cpos__slider.value | |
lines[ppos__slider.value].x = xpos__slider.value | |
lines[ppos__slider.value].y = ypos__slider.value | |
} | |
draw() | |
}) | |
let del = document.getElementById('delete') | |
del.addEventListener("mouseup", function(evt) { | |
lines.splice(ppos__slider.value, 1); | |
draw() | |
}) | |
// pairs up with this browser history code above; this restores | |
// the state of the drawing from the url parameters. | |
let params = new URLSearchParams(window.location.search); | |
lines = JSON.parse(params.get('lines') || '[]') || [] | |
image.value = params.get('image') || 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/804px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg' | |
width.value = params.get('width') || 256 | |
height.value = params.get('height') || 512 | |
offsetx.value = params.get('offsetx') || 0 | |
offsety.value = params.get('offsety') || -80 | |
draw() | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment