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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more