Published
Edited
Apr 30, 2021
Fork of Vector fill
2 forks
9 stars
Insert cell
Insert cell
Insert cell
graphic = {
const text = await FileAttachment("8.svg").text();
const document = (new DOMParser).parseFromString(text, "image/svg+xml");
const svgdoc = document.querySelector('svg');
return svg `${svgdoc}`;
}
Insert cell
// viewbox = graphic.getAttribute("viewBox");
Insert cell
height = graphic.getAttribute("height");
Insert cell
width = graphic.getAttribute("width");
Insert cell
md`Now convert vector to raster in the browser`
Insert cell
vectorToRaster = {
let data = new XMLSerializer().serializeToString(graphic);
let blob = new Blob([data], {type: 'image/svg+xml'});
let url = URL.createObjectURL(blob);
invalidation.then(() => URL.revokeObjectURL(url));
let img = new Image();
img.src = url;
await new Promise(resolve => img.onload = resolve);
return img;
}
Insert cell
md`Play with this parameter. I decided to have it set to 2 to get more precise outline of the filled area`
Insert cell
quality = 2
Insert cell
md`Perform raster fill using q-floodfill package`
Insert cell
mutable mousePos = ({x: width - 100, y: height / 2})
Insert cell
ff = {
const tolerance = 0.4;
const context = DOM.context2d(width * quality, height * quality, 1);
context.drawImage(vectorToRaster, 0, 0, width * quality, height * quality);
const imgData = context.getImageData(0, 0, width * quality, height * quality);
const ff = new floodfill.default(imgData);
ff.collectModifiedPixels = true;
try {
ff.fill(
"#0000FF",
Math.floor(mousePos.x * quality),
Math.floor(mousePos.y * quality),
tolerance
);
} catch (err) {
console.log(err);
}
return ff;
}
Insert cell
md`Display raster canvas with filled pixels`
Insert cell
filledCanvas = {
const filledContext = DOM.context2d(width * quality, height * quality, 1);
filledContext.fillStyle = 'white';
filledContext.fillRect(0, 0, width * quality, height * quality);
filledContext.putImageData(ff.imageData, 0, 0);
filledContext.canvas.onclick = (mouse) => {
let {left: cx, top: cy} = filledContext.canvas.getBoundingClientRect();
let {x: mx, y: my} = mouse;
mx -= cx;
my -= cy;
mutable mousePos = {x: mx / quality, y: my / quality};
}
return filledContext.canvas;
}
Insert cell
md`Now only display the filled part. This will be used for raster -> vector conversion using Potrace-like algorithm`
Insert cell
tempCanvas = {
const tempContext = DOM.context2d(width * quality, height * quality, 1);
tempContext.fillStyle = 'white';
tempContext.fillRect(0, 0, width * quality, height * quality);
const tempCanvasImage = tempContext.getImageData(0, 0, width * quality, height* quality);
let tempCanvasImageData = tempCanvasImage.data;
ff.modifiedPixels.forEach((val) => {
const [x, y] = val.split('|');
const off = y * (width * quality * 4) + x * 4;
tempCanvasImageData[off] = 255;
tempCanvasImageData[off + 1] = 0;
tempCanvasImageData[off + 2] = 0;
tempCanvasImageData[off + 3] = 255;
});
tempContext.putImageData(tempCanvasImage, 0, 0);
return tempContext.canvas;
}
Insert cell
svgpaths = potrace.traceCanvas(tempCanvas)
Insert cell
hatchedSVG = {
return html`<svg width="${width * quality}" height="${height * quality}">
${hatches.map(seg => `<line stroke="blue" x1="${seg.a.x}" y1="${seg.a.y}" x2="${seg.b.x}" y2="${seg.b.y}" />`).join('\n')}
</svg>`
}
Insert cell
filledSVG = {
const svgElement = graphic.cloneNode(true)
svgElement.innerHTML += hatches.map(seg => `<path stroke="blue" d="M${seg.a.x} ${seg.a.y} L ${seg.b.x} ${seg.b.y}" />`).join('\n')
// This is just for interactive UI. Click on the SVG areas to fill them!
svgElement.onclick = (mouse) => {
let {left: cx, top: cy} = svgElement.getBoundingClientRect();
let {clientX: mx, clientY: my} = mouse;
console.log(mx-cx, my-cy);

mx -= cx;
my -= cy;
mutable mousePos = {x: mx, y: my};
}
return svgElement
}
Insert cell
hatches = {
const w = width* quality
const h = height * quality
// TODO : scale paths back by the quality parameter
const paths = getSVG(svgpaths, 1.0 / quality, 'curve')
const angle = Math.PI / 4
const spacing = 5
const alternate = false
return hatchMultiplePolygons(paths.map((path) => {
return Polygon.fromPath(path)
}), angle, spacing, alternate)
}
Insert cell
function getSVG(pathList, size) {
let paths = []
function path(curve) {

function bezier(i) {
let b = 'C ' + (curve.c[i * 3 + 0].x * size).toFixed(3) + ' ' +
(curve.c[i * 3 + 0].y * size).toFixed(3) + ',';
b += (curve.c[i * 3 + 1].x * size).toFixed(3) + ' ' +
(curve.c[i * 3 + 1].y * size).toFixed(3) + ',';
b += (curve.c[i * 3 + 2].x * size).toFixed(3) + ' ' +
(curve.c[i * 3 + 2].y * size).toFixed(3) + ' ';
return b;
}

function segment(i) {
let s = 'L ' + (curve.c[i * 3 + 1].x * size).toFixed(3) + ' ' +
(curve.c[i * 3 + 1].y * size).toFixed(3) + ' ';
s += (curve.c[i * 3 + 2].x * size).toFixed(3) + ' ' +
(curve.c[i * 3 + 2].y * size).toFixed(3) + ' ';
return s;
}

let n = curve.n;
let p = 'M' + (curve.c[(n - 1) * 3 + 2].x * size).toFixed(3) +
' ' + (curve.c[(n - 1) * 3 + 2].y * size).toFixed(3) + ' ';
for (let i = 0; i < n; i++) {
if (curve.tag[i] === "CURVE") {
p += bezier(i);
} else if (curve.tag[i] === "CORNER") {
p += segment(i);
}
}
return p;
}
let len = pathList.length
for (let i = 0; i < len; i++) {
paths.push(path(pathList[i].curve));
}
return paths
}
Insert cell
floodfill = require('q-floodfill')
Insert cell
md`This is my package that's based on another package. Essentially Potrace in the browser.`
Insert cell
potrace = require("potrace-js-dev@0.0.3/lib/dist/index.js")
Insert cell
md`Could use StackBlur to blur the canvas in order to get various effects on the outlines`
Insert cell
// StackBlur = require(await FileAttachment("stack-blur.js").url())
Insert cell
import { hatchMultiplePolygons, Polygon } from '@makio135/utilities'
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