blur layers

This commit is contained in:
2026-05-21 05:59:44 -04:00
parent 3983cf2e0d
commit 687a968522
42 changed files with 2895070 additions and 920162 deletions

View File

@@ -200,57 +200,67 @@ function drawShock(c, shock, tx, ty, scale, u, P, params, sprite) {
const px = tx(shock.x), py = ty(shock.y), R = shock.r * scale;
const useBubbles = params.diskBubbles !== false;
// when softening, draw the whole disk to its own offscreen and composite it
// back through a single Gaussian blur — softens just this layer's edges.
const soften = (params.diskSoften || 0) * u;
let T = c, tmp = null;
if (soften > 0) {
tmp = document.createElement('canvas');
tmp.width = c.canvas.width; tmp.height = c.canvas.height;
T = tmp.getContext('2d');
}
// disk body: dark annulus that keeps a lighter, detailed centre.
// softer when the line work is bubbles (the bubbles carry the density).
const bodyK = useBubbles ? 0.6 : 1.0;
const core = c.createRadialGradient(px, py, 0, px, py, R);
const core = T.createRadialGradient(px, py, 0, px, py, R);
core.addColorStop(0.0, `rgba(${r},${g},${b},${0.06 * shock.intensity * bodyK})`);
core.addColorStop(0.35, `rgba(${r},${g},${b},${0.18 * shock.intensity * bodyK})`);
core.addColorStop(0.72, `rgba(${r},${g},${b},${0.38 * shock.intensity * bodyK})`);
core.addColorStop(0.94, `rgba(${r},${g},${b},${0.34 * shock.intensity * bodyK})`);
core.addColorStop(1, `rgba(${r},${g},${b},0)`);
c.fillStyle = core;
c.beginPath(); c.arc(px, py, R, 0, Math.PI * 2); c.fill();
T.fillStyle = core;
T.beginPath(); T.arc(px, py, R, 0, Math.PI * 2); T.fill();
if (useBubbles && shock.bubbleStrokes) {
// describe striations / rings / rim / core with the particle method
const dRng = makeRng(params.seed, 'diskbubbles');
for (const stroke of shock.bubbleStrokes) {
const bubs = sampleBubbles(stroke, params, dRng);
c.globalAlpha = Math.min(1, 0.45 + stroke.weight * 0.5);
T.globalAlpha = Math.min(1, 0.45 + stroke.weight * 0.5);
for (const bb of bubs) {
const rr = Math.max(bb.r * scale, 0.45);
const d = rr * 2.4;
c.drawImage(sprite, tx(bb.x) - d / 2, ty(bb.y) - d / 2, d, d);
T.drawImage(sprite, tx(bb.x) - d / 2, ty(bb.y) - d / 2, d, d);
}
}
c.globalAlpha = 1;
T.globalAlpha = 1;
} else {
// legacy: clean vector strokes
c.lineCap = 'round';
// clean vector strokes
T.lineCap = 'round';
for (const s of shock.striations) {
const ix = px + Math.cos(s.a) * s.inner * scale, iy = py + Math.sin(s.a) * s.inner * scale;
const ox = px + Math.cos(s.a) * s.outer * scale, oy = py + Math.sin(s.a) * s.outer * scale;
const mx = (ix + ox) / 2 + Math.cos(s.a + Math.PI / 2) * s.wobble * scale;
const my = (iy + oy) / 2 + Math.sin(s.a + Math.PI / 2) * s.wobble * scale;
c.strokeStyle = `rgba(${r},${g},${b},${s.opacity})`;
c.lineWidth = s.width * u;
c.beginPath(); c.moveTo(ix, iy); c.quadraticCurveTo(mx, my, ox, oy); c.stroke();
T.strokeStyle = `rgba(${r},${g},${b},${s.opacity})`;
T.lineWidth = s.width * u;
T.beginPath(); T.moveTo(ix, iy); T.quadraticCurveTo(mx, my, ox, oy); T.stroke();
}
for (const ring of shock.rings) {
c.strokeStyle = `rgba(${r},${g},${b},${ring.opacity})`;
c.lineWidth = ring.width * u;
c.beginPath(); c.arc(px, py, ring.rr * scale, 0, Math.PI * 2); c.stroke();
T.strokeStyle = `rgba(${r},${g},${b},${ring.opacity})`;
T.lineWidth = ring.width * u;
T.beginPath(); T.arc(px, py, ring.rr * scale, 0, Math.PI * 2); T.stroke();
}
for (const seg of (shock.rimSegs || [])) {
c.strokeStyle = `rgba(${r},${g},${b},${seg.opacity})`;
c.lineWidth = seg.width * u;
c.beginPath(); c.arc(px, py, shock.r * scale, seg.a0, seg.a1); c.stroke();
T.strokeStyle = `rgba(${r},${g},${b},${seg.opacity})`;
T.lineWidth = seg.width * u;
T.beginPath(); T.arc(px, py, shock.r * scale, seg.a0, seg.a1); T.stroke();
}
for (const k of shock.core) {
c.strokeStyle = `rgba(${r},${g},${b},${k.opacity})`;
c.lineWidth = k.width * u;
c.beginPath(); c.moveTo(tx(k.x1), ty(k.y1)); c.lineTo(tx(k.x2), ty(k.y2)); c.stroke();
T.strokeStyle = `rgba(${r},${g},${b},${k.opacity})`;
T.lineWidth = k.width * u;
T.beginPath(); T.moveTo(tx(k.x1), ty(k.y1)); T.lineTo(tx(k.x2), ty(k.y2)); T.stroke();
}
}
@@ -258,32 +268,41 @@ function drawShock(c, shock, tx, ty, scale, u, P, params, sprite) {
if (shock.stains) {
for (const st of shock.stains) {
const sr = st.r * scale;
const grad = c.createRadialGradient(tx(st.x), ty(st.y), 0, tx(st.x), ty(st.y), sr);
const grad = T.createRadialGradient(tx(st.x), ty(st.y), 0, tx(st.x), ty(st.y), sr);
if (st.dark) {
c.globalCompositeOperation = 'source-over';
T.globalCompositeOperation = 'source-over';
grad.addColorStop(0, `rgba(${r},${g},${b},${st.opacity})`);
grad.addColorStop(1, `rgba(${r},${g},${b},0)`);
c.fillStyle = grad;
T.fillStyle = grad;
} else {
c.globalCompositeOperation = 'destination-out';
T.globalCompositeOperation = 'destination-out';
grad.addColorStop(0, `rgba(0,0,0,${st.opacity * 1.4})`);
grad.addColorStop(1, 'rgba(0,0,0,0)');
c.fillStyle = grad;
T.fillStyle = grad;
}
c.beginPath(); c.arc(tx(st.x), ty(st.y), sr, 0, Math.PI * 2); c.fill();
T.beginPath(); T.arc(tx(st.x), ty(st.y), sr, 0, Math.PI * 2); T.fill();
}
c.globalCompositeOperation = 'source-over';
T.globalCompositeOperation = 'source-over';
}
// keep a bright, detailed centre by clearing a soft hole in the ink
// keep a bright, detailed centre by clearing a soft hole
if (shock.bright) {
c.save();
c.globalCompositeOperation = 'destination-out';
const hole = c.createRadialGradient(px, py, 0, px, py, shock.bright * scale);
T.save();
T.globalCompositeOperation = 'destination-out';
const hole = T.createRadialGradient(px, py, 0, px, py, shock.bright * scale);
hole.addColorStop(0, 'rgba(0,0,0,0.85)');
hole.addColorStop(1, 'rgba(0,0,0,0)');
c.fillStyle = hole;
c.beginPath(); c.arc(px, py, shock.bright * scale, 0, Math.PI * 2); c.fill();
T.fillStyle = hole;
T.beginPath(); T.arc(px, py, shock.bright * scale, 0, Math.PI * 2); T.fill();
T.restore();
}
// composite the (optionally blurred) disk back onto the ink layer
if (tmp) {
c.save();
c.filter = `blur(${soften.toFixed(2)}px)`;
c.drawImage(tmp, 0, 0);
c.filter = 'none';
c.restore();
}
}