Skip to content

Instantly share code, notes, and snippets.

@bazzargh
Created December 30, 2022 14:29
Show Gist options
  • Save bazzargh/2b2ac2c14ba9e2592991cf143f2d9a66 to your computer and use it in GitHub Desktop.
Save bazzargh/2b2ac2c14ba9e2592991cf143f2d9a66 to your computer and use it in GitHub Desktop.
rudimentary drawing program, that outputs bbc basic code to reproduce the image
<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