Published
Edited
Jul 10, 2020
Importers
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
function getCirculations (imgdata) {
let [w,h] = [imgdata.width,imgdata.height];
let data = imgdata.data;
function getRuns (x) {
let last = false;
let run = null;
let runs = [];
for (let y = 0; y < h; y++) {
let pixel = data[(w*y+x)*4+3];
let current = pixel > 0;
if (current) {
if (run != null) run.ymax = y+1;
else {
run = { x:x, ymin: y, ymax:y+1 };
runs.push (run);
}
}
else {
run = null
}
}
return runs
}
function runDifference (runs0,runs1) {
runs0 = runs0.slice(0);
if (runs0.length == 0) return runs0;
if (runs1.length == 0) return runs0;
let result = [];
let [i0,i1] = [0,0];
while ( i0 < runs0.length && i1 < runs1.length ) {
let [r0,r1] = [runs0[i0], runs1[i1]];
if (r0.ymax <= r1.ymin) {
result.push(r0);
i0++;
} else if (r1.ymax <= r0.ymin) {
i1++;
} else {
if (r0.ymin < r1.ymin) {
result.push ({x:r0.x, ymin: r0.ymin, ymax:r1.ymin});
r0 = {x:r0.x, ymin:r1.ymin, ymax:r0.ymax};
} else {
r0 = {x:r0.x, ymin:r1.ymax, ymax:r0.ymax};
}
if (r0.ymax <= r0.ymin) i0++;
else runs0[i0] = r0;
}
}
result.push.apply(result,runs0.slice(i0))
return result;
}
let prevRuns = [];
let connected = new ConnectedComponents();
let vEdge = (x) => { return (run) => [Vec(x,run.ymin),Vec(x,run.ymax)] };
for (let x= 0; x <= w; x++) {
let runs = x == w ? [] : getRuns(x);
let currMinusPrev = runDifference(runs, prevRuns);
let prevMinusCurr = runDifference(prevRuns, runs);
let verticalEdges = currMinusPrev.map(vEdge(x)).concat(prevMinusCurr.map(vEdge(x)));
let horizontalEdges = [];
for (let r of runs) {
horizontalEdges.push ([Vec(r.x,r.ymin),Vec(r.x+1,r.ymin)]);
horizontalEdges.push ([Vec(r.x,r.ymax),Vec(r.x+1,r.ymax)]);
}
for (let vEdge of verticalEdges) {
connected.addVerticalEdge (vEdge);
}
for (let hEdge of horizontalEdges) {
connected.addHorizontalEdge (hEdge);
}

prevRuns = runs
}
return connected;
}
Insert cell
Insert cell
function isDark (maxValue = 127) {
let rgbaWord = new Uint32Array(1);
let rgba = new Uint8Array(rgbaWord.buffer);
return (pixel) => {
rgbaWord[0] = pixel;
return rgba[0]*0.2126+rgba[1]*0.7152+rgba[2]*0.0722 <= maxValue
}
}
Insert cell
Insert cell
function segmentImage(imgData, isForeground = isDark()) {
let [w,h] = [imgData.width,imgData.height]
let mask = new ImageData(w,h);
let src = new Uint32Array (imgData.data.buffer);
let dst = new Uint32Array (mask.data.buffer);
for (let i = 0; i < src.length; i++) {
if (isForeground(src[i])) dst[i] = 0xff000000 // opaque black
}
return mask
}
Insert cell
Insert cell
function drawCirculations (ctx, connected, stroke = 'red', fill = ['black','lightGray']) {
let comps = connected.closed.slice(0);
comps.reverse();
let maxLevel = Math.max(...comps.map((c)=>c.inside.length))+1;
for (let comp of comps) {
ctx.beginPath();
let curve = new Curve(...comp.points)//.subsample(0.7)
let {x,y} = curve[0];
ctx.moveTo(x,y);
for (let {x,y} of curve) {
ctx.lineTo(x,y)
}
if (fill) {
ctx.fillStyle = fill[comp.inside.length % fill.length];
ctx.fill()
}
if (stroke) {
ctx.strokeStyle = stroke
ctx.stroke()
}
}
}
Insert cell
Insert cell
Insert cell
Insert cell
segmented = {
let [w,h] = [img.width,img.height]
let ctx = new DOM.context2d(w,h,1);
ctx.drawImage(img,0,0);
let imgData = ctx.getImageData(0,0,img.width,img.height);
let mask = segmentImage(imgData,isDark(darkLevel))
ctx.putImageData(mask,0,0)
return ctx.canvas
}
Insert cell
Insert cell
{
let [w,h] = [segmented.width,segmented.height]
let ctx = new DOM.context2d(w,h,1);
ctx.drawImage(segmented,0,0);
let mask = ctx.getImageData(0,0,w,h);
ctx.clearRect(0,0,w,h)
let connected = getCirculations(mask);
ctx.lineWidth = viewof strokeThickness.value;
drawCirculations (ctx, connected,
strokeThickness == "none" ? null : strokeColor,
fillStyle == 'none' ? null :
fillStyle == 'b/w' ? ['black','white'] :
['black','darkGray','gray','lightGray','white'])
return ctx.canvas
}
Insert cell
Insert cell
canvas = {
let [w,h] = [width, ~~(width*9/16)]
let ctx = DOM.context2d(w,h,1)
let imageData = ctx.getImageData(0,0,w,h);
let paint = function (event) {
let [x,y] = [event.offsetX,event.offsetY]
ctx.fillStyle = `rgb(0,0,0)`;
if (event.buttons) {
if (event.buttons & 1) ctx.globalCompositeOperation = 'source-over'
else ctx.globalCompositeOperation = 'destination-out'
ctx.beginPath();
ctx.arc (x, y, viewof brushRadius.value, 0, 2*Math.PI)
ctx.fill();
ctx.globalCompositeOperation = 'source-over'
}
}
ctx.canvas._reset = function () {
ctx.clearRect(0,0,w,h)
imageData = ctx.getImageData(0,0,w,h);
}
ctx.canvas._refresh = function() {
ctx.clearRect(0,0,w,h);
ctx.lineWidth = viewof strokeThickness.value;
drawCirculations(ctx,
getCirculations(imageData),
viewof strokeThickness.value == "none" ? null : viewof strokeColor.value,
viewof fillStyle.value == 'none' ? null :
viewof fillStyle.value == 'b/w' ? ['black','white'] :
['black','darkGray','gray','lightGray','white'])
}
ctx.canvas.oncontextmenu = (e) => { e.preventDefault() }
ctx.canvas.onmousedown = (e) => {
ctx.putImageData(imageData,0,0);
paint(e)
}
ctx.canvas.onmouseup = (e) => {
imageData = ctx.getImageData(0,0,w,h);
ctx.canvas._refresh()
}
ctx.canvas.onmousemove = paint
return ctx.canvas
}
Insert cell
Insert cell
Insert cell
Insert cell
viewof showNesting = new View(false)
Insert cell
viewof brushRadius = new View (10)
Insert cell
viewof darkLevel = new View(127)
Insert cell
viewof fillStyle = new View('none')
Insert cell
viewof strokeThickness = new View('1')
Insert cell
viewof strokeColor = new View('#ff0000')
Insert cell
drawUI = () => html`<b>Circulation drawing style</b>:<br>
Border: thickness ${bindSelect({options:["none","1","2","3","4"]},viewof strokeThickness)} &nbsp; color ${bindColor({},viewof strokeColor)}<br>
Fill: ${bindSelect({options:["none", "b/w", "grays"]}, viewof fillStyle)}`
Insert cell
{
strokeColor;
strokeThickness;
fillStyle;
canvas._refresh()
}
Insert cell
viewof imageName = new View("celtic")
Insert cell
images = ({"carpet":FileAttachment("carpet.jpg"),
"staircase":FileAttachment("staircase.jpg"),
"wire":FileAttachment("wire.jpg"),
"fort":FileAttachment("forte.png"),
"celtic":FileAttachment("celtic_cross.png")})
Insert cell
Insert cell
Insert cell
Insert cell
import { bindSliderNumber, bindRadioGroup, bindSelect, bindCheckGroup, bindCheckbox, bindFile, bindColor, View } from "@esperanc/inline-inputs"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more