Published
Edited
Jul 15, 2021
1 fork
2 stars
Insert cell
# Image Processing Sandbox, v1
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
variance = {
return renderWithTransform({
columns: 50,
sample: 0.06,
func: ({ cells }) => {
const munged = cells.map((d) => {
const variance = d3.variance(d.pixels, (d) => d.lightness);
return {
...d,
variance
};
});
const extent = d3.extent(munged, (d) => d.variance);
const quantiles = d3
.scaleQuantile(
munged.map((d) => d.variance),
Array.from({ length: 10 })
)
.quantiles();
const varianceScale = d3.scaleLinear([0, d3.max(quantiles)], [255, 0]);
return munged.map((cell) => {
// const avg = d3.mean(cell.pixels, (d) => d.lightness);
const color = varianceScale(cell.variance);
const fillStyle = d3.rgb(color, color, color);
return { ...cell, fillStyle, color };
});
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const radiusScale = d3.scaleLinear([0, 255], [10, 0]).clamp(true);
const cells = varianceCells
.filter((d) => d.color < 250)
.map((d) => {
const { x, y, width, height, color, grayscale } = d;

const radius = radiusScale(color);
return htl.svg`<circle r=${radius} cx=${x} cy=${y} fill=${grayscale} stroke="black" />`;
});
return htl.html`
<svg viewBox=${`0 0 ${imageWidth} ${imageHeight}`} style="width: 400px; border: 1px dashed black;">${cells}</svg>
`;
}
Insert cell
Insert cell
{
const radiusScale = d3.scaleLinear([0, 255], [10, 0]).clamp(true);
const filtered = varianceCells.filter((d) => d.color > 220);
const cells = filtered.map((d) => {
const { x, y, width, height, color, grayscale } = d;
const radius = radiusScale(color);
return htl.svg`<circle r=${radius} cx=${x} cy=${y} fill=${grayscale} stroke="black" />`;
});
const voronoiCells = getVoronoiCells(
filtered.filter((d) => d.x < 1400).map((d) => [d.x, d.y]),
{ width: imageWidth, height: imageHeight }
);
const voronoiPaths = voronoiCells
.map((d) => {
return htl.svg`<path fill="none" stroke="black" d=${d.pathDefinition} />`;
})
.filter((d) => d.getTotalLength() > 120);
return htl.html`
<svg viewBox=${`0 0 ${imageWidth} ${imageHeight}`} style="width: 400px; border: 1px dashed black;">
${voronoiPaths}
</svg>
`;
}
Insert cell
{
const context = DOM.context2d(imageWidth, imageHeight, 1);
context.drawImage(image, 0, 0, imageWidth, imageHeight);
const imageData = context.getImageData(0, 0, imageWidth, imageHeight);
const traceData = imagetracer.imagedataToTracedata(imageData, {
colorsampling: 0,
colorquantcycles: 1,
numberofcolors: 7,
linefilter: true,
// viewbox: true,
ltres: 0.0001
});
const linesData = traceData.layers
.flat()
.map((d) => d.segments)
.flat()
.filter((d) => d.type === "L")
.sort((a, b) => d3.ascending(a.y1, b.y1));
// const maxDistance = 100;
// const connected = linesData
// .map((d, i, arr) => {
// const prev = arr[i - 1];
// let type = "M";
// if (prev) {
// const distanceX = Math.abs(prev.x1 - d.x1);
// const distanceY = Math.abs(prev.y1 - d.y1);
// const dist = (distanceX ** 2 + distanceY ** 2) ** 0.5;
// if (dist < maxDistance && dist > 0) {
// // console.log({ d, prev, dist });
// type = "L";
// }
// }
// return {
// ...d,
// type
// };
// })
// .map((d) => `${d.type} ${d.x1} ${d.y1} L ${d.x2} ${d.y2}`)
// .join(" ");
// const onePath = htl.svg`<path d=${`M 0 0 ${connected}`} fill="none" stroke="black" />`;
const lines = linesData.map(
(d) => htl.svg`<line ${{ ...d }} stroke="black" />`
);
const voronoiCells = getVoronoiCells(
linesData.map((d) => [d.x1, d.y1]).filter(() => Math.random() < 0.2),
{ width: imageWidth, height: imageHeight }
);
const voronoiPaths = voronoiCells
.map(
({ pathDefinition }) =>
htl.svg`<path fill="none" stroke="black" d=${pathDefinition} />`
)
.filter((d) => d.getTotalLength() > 90);
const points = linesData.map(
(d) =>
htl.svg`<circle r="5" stroke="black" fill="none" cx=${d.x1} cy=${d.y1} />`
);
return htl.html`<svg width="400px" viewBox=${`0 0 ${imageWidth} ${imageHeight}`}>
${voronoiPaths}
</svg>`;
// return 23;

// LOoks cool
// const svgMarkup = imagetracer.imagedataToSVG(imageData, {
// colorsampling: 0,
// colorquantcycles: 1,
// numberofcolors: 7,
// linefilter: true,
// viewbox: true,
// ltres: 0.0001
// });
// const svgContainer = d3.select(html`${svgMarkup}`).node();

// const paths = [...svgContainer.querySelectorAll("path")];

// const filteredPaths = paths
// .filter((d) => d.getTotalLength() > 10e3)
// .map((pathElement) => {
// const pathDef = pathElement.getAttribute("d");
// return htl.svg`<path stroke="black" fill="none" d=${pathDef} />`;
// });

// return htl.html`<svg width="400px" viewBox=${`0 0 ${imageWidth} ${imageHeight}`}>${filteredPaths}</svg>`;

// console.log("paths", paths);

// return svgContainer;
}
Insert cell
Insert cell
imagetracer = require("imagetracerjs")
Insert cell
function getVoronoiCells(points, { width = 100, height = 100 } = {}) {
const voronoi = d3.Delaunay.from(points).voronoi([0, 0, width, height]);
const cells = points.map((point, i) => {
const pathDefinition = voronoi.renderCell(i);
return { pathDefinition, point };
});
return cells;
}
Insert cell
function getPixels2({ x, y, width, height }) {
return allPixels.filter(
(d) => d.x >= x && d.y >= y && d.x <= x + width && d.y <= y + height
);
}
Insert cell
function getPixels({ context, x, y, width, height, sample = 0.5 }) {
const imageData = context.getImageData(x, y, width, height);
const pixels = imageData.data;
const out = [];
const keys = ["r", "g", "b", "a"];
let current = {};
pixels.forEach((d, i) => {
const mod = i % 4;
const key = keys[mod];
current[key] = d;
if (mod === 3) {
if (Math.random() > sample) {
current = {};
return;
}
const pixelIndex = Math.floor(i / 4);
const column = pixelIndex % width;
const row = Math.floor(pixelIndex / width);
Object.assign(current, {
index: pixelIndex,
column,
row,
x: x + column,
y: y + row
});
const { r, g, b } = current;
const lightness = Math.floor((r + g + b) / 3);
const grayscale = `rgb(${lightness}, ${lightness}, ${lightness})`;
out.push({ ...current, lightness, grayscale });
current = {};
}
});
return out;
}
Insert cell
function getCells(columns = 50) {
const aspectRatio = imageWidth / imageHeight;
const rows = Math.ceil(columns / aspectRatio);
const cellWidth = Math.ceil(imageWidth / columns);
const cellHeight = Math.ceil(imageHeight / rows);
const numCells = columns * rows;
const cells = Array.from({ length: numCells }).map((d, i) => {
const column = i % columns;
const row = Math.floor(i / columns);
return {
x: column * cellWidth,
y: row * cellHeight,
column,
row,
width: cellWidth,
height: cellHeight
};
});
return cells;
}
Insert cell
function renderWithTransform({ func, columns, sample }) {
const context = DOM.context2d(imageWidth, imageHeight, 1);
context.canvas.style.width = "400px";
context.canvas.style.border = "1px solid black";
context.drawImage(image, 0, 0, imageWidth, imageHeight);

const outContext = DOM.context2d(imageWidth, imageHeight, 1);
outContext.canvas.style.width = context.canvas.style.width;

const withPixels = getCells(columns).map((cell, index) => {
const pixels = getPixels({
...cell,
context,
sample
});
return {
...cell,
pixels: pixels
};
});

const transformed = func({ cells: withPixels });

transformed.forEach((cell, index) => {
const { x, y, width, height, fillStyle } = cell;
outContext.beginPath();
outContext.fillStyle = fillStyle;
outContext.fillRect(x, y, width, height);
});

outContext.canvas.cells = transformed;

return outContext.canvas;
}
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