Public
Edited
Sep 11, 2023
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function maskedFiberTexture(mask, options = {}) {
let {
width = 800,
height = 600,
linewidth = 1,
coarseness = 10,
waviness = 0.2,
background = "white",
backgroundOpacity = 1,
color = "black",
colorVariation = 0,
threadLen = 10,
opacity = 0.2,
randomSeed = 0
} = options;
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext("2d");
const baseColor = d3.hsl(color);
const sep = coarseness;
const avgLen = sep * 2;
const rand = makeRandFunction(randomSeed);
const pds = new Poisson(
{
shape: [width, height],
minDistance: sep * 0.8,
maxDistance: sep / 0.8,
tries: 15
},
rand
);
background = d3.color(background);
background.opacity = backgroundOpacity;
ctx.fillStyle = background.formatRgb();
ctx.fillRect(0, 0, width, height);
ctx.lineWidth = linewidth;
const [minv, maxv] = [
Math.max(0, 1 - colorVariation),
Math.min(1.5, 1 + colorVariation)
];
for (let [x, y] of pds.fill()) {
let angle;
if (mask) {
if (mask.getPixel(x, y)[3] != 255) continue;
const [gx, gy] = mask.getGradient(x, y);
const g = Math.min(1, Math.hypot(gx, gy) / 10);
angle =
g * (Math.atan2(gy, gx) + Math.PI * (0.5 + ~~rand(2))) +
(1 - g) * rand(-Math.PI, Math.PI);
} else {
angle = rand(-Math.PI, Math.PI);
}
let [px, py] = [x, y];
ctx.beginPath();
let pts = [[x, y]];
for (let i = 0; i < threadLen * 3; i++) {
let wav = waviness;
let ang = angle;
for (let tries = 5; tries >= 0; tries--) {
const len = sep * 0.33 * rand(0.7, 1 / 0.7);
const ang = angle + Math.PI * 0.25 * wav * rand(-1, 1);
const p = [px + len * Math.cos(ang), py + len * Math.sin(ang)];
if (!mask || mask.getPixel(...p)[3] == 255) {
[px, py] = p;
angle = ang;
pts.push(p);
break;
}
wav *= 1.5;
}
}
let color = d3.hsl(
(baseColor.h + 180 * colorVariation * rand(-1, 1)) % 360,
Math.max(0, Math.min(1, baseColor.s + colorVariation * rand(-1, 1))),
Math.max(0, Math.min(1, baseColor.l + colorVariation * rand(-1, 1)))
);
color.opacity = opacity;
ctx.strokeStyle = color.formatRgb();
for (let p of makeCurve(pts)) ctx.lineTo(...p);
ctx.stroke();
}
return canvas;
}
Insert cell
sampleImage = sampleText(demoText, { fontSize: "400px" })
Insert cell
sampleImageGradient = imageGradient(sampleImage)
Insert cell
Insert cell
imageGradient = function (image) {
const { width, height } = image;
const srcImgData = image.getContext("2d").getImageData(0, 0, width, height);
const getPixel = (x, y) => {
address = (width * Math.round(y) + Math.round(x)) * 4;
return srcImgData.data.slice(address, address + 4);
};
const blur = 5;
const canvas = DOM.canvas(width, height);
const ctx = canvas.getContext("2d");
canvas.style.background = "gray";
ctx.filter = `blur(${blur}px)`;
ctx.drawImage(image, 0, 0);
const imageData = canvas.getContext("2d").getImageData(0, 0, width, height);
const rowSize = width * 4;
let gx, gy, address;
const getGradient = (x, y) => {
x = Math.round(x);
y = Math.round(y);
if (x == 0 || x + 1 == width || y == 0 || y + 1 == height) return [0, 0];
address = (width * y + x) * 4 + 3;
[gx, gy] = [
imageData.data[address + 4] - imageData.data[address - 4],
imageData.data[address + rowSize] - imageData.data[address - rowSize]
];
return [gx, gy];
};
canvas.getGradient = getGradient;
canvas.getPixel = getPixel;
return canvas;
}
Insert cell
Insert cell
Insert cell
function makeCurve(pts) {
if (pts.length < 3) return pts;
for (let i = 0; i < 3; i++) {
pts = lr4(pts, 3, false);
}
return pts;
}
Insert cell
Poisson = require("https://bundle.run/poisson-disk-sampling@2.2.2")
Insert cell
import { lr, lr4, fourPoint } from "@esperanc/lane-riesenfeld-subdivision"
Insert cell
import { presetMenu } from "@esperanc/input-presets"
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