Commit bb4d0806 by PLN (Algolia)

impression: the full wall — thirteen canvases

parent 4fe2289a
...@@ -593,6 +593,14 @@ function localRng(p) { ...@@ -593,6 +593,14 @@ function localRng(p) {
}; };
} }
// NOTE on the water: bleed() is deliberately kept for the occasional hero
// touch (a handful of calls per painting — a capped budget is affordable).
// It must NOT ride the per-stroke hot path: a canvas-wide ctx.filter costs
// ~25ms PER STROKE at full canvas area in software rendering, and dense
// mouille freezes hung chrome for minutes (house bug, three painter reports).
// The wet hand's stroke loop lays its water with realizeWet's dry-laid wet
// marks instead — the blur baked into the mark geometry, no filter ops.
/** /**
* bleed — paint paintFn inside a water blur: wet-on-wet edges, colours * bleed — paint paintFn inside a water blur: wet-on-wet edges, colours
* merging. Graceful fallback: a ctx without filter support just paints dry. * merging. Graceful fallback: a ctx without filter support just paints dry.
...@@ -787,16 +795,41 @@ function realizeWet(ctx, rng, hand, p) { ...@@ -787,16 +795,41 @@ function realizeWet(ctx, rng, hand, p) {
const sq = clamp(p.squash == null ? 1 : p.squash, 0.15, 1); const sq = clamp(p.squash == null ? 1 : p.squash, 0.15, 1);
const col = p.c; const col = p.c;
const ux = Math.cos(ang), uy = Math.sin(ang); const ux = Math.cos(ang), uy = Math.sin(ang);
bleed(ctx, clamp(w * 0.35, 0.8, 3.5), (g) => { // THE WET, LAID DRY. The old path set a canvas-wide ctx.filter per stroke —
// the touch proper, plus a halo the colour bleeds into // ~25ms PER STROKE at full canvas area (house bug: dense mouille freezes
// hung chrome for minutes; touch-sized offscreen stamps still cost ~10ms
// each on a lived-in canvas — every scratch round-trip resolves the big
// surface). So the water is laid with no filter ops and no canvas
// round-trips: Gaussian blur is linear over transparency, so blurring the
// touch's two marks equals compositing the two blurred marks; and blurring
// a soft gradient mark widens its falloff by the blur and dips its peak as
// the ink spreads (badly for small marks, barely for large ones). `wet()`
// bakes exactly that into each mark: radius + ~1.2 sigma, the rim softened
// by the blur's own transition width, alpha faded by the spread. The rng
// beat is unchanged — one halo draw, same position in the stream.
const sig = clamp(w * 0.35, 0.8, 3.5) / 2; // css blur(px) -> gaussian sigma
// blurred radius and hardness for a mark of radius r (defining axis),
// falloff today = (1-hardness)*r widened by the blur's ~2 sigma transition
const wetH = (r, hardness) => {
const r2 = r + 1.2 * sig;
return clamp(1 - ((1 - hardness) * r + 2 * sig) / r2, 0.05, 0.95);
};
// peak dip as the ink spreads: ~nothing once the flat top dwarfs sigma
const wetA = (r, hardness) =>
a * clamp(1 - 0.75 * Math.exp(-Math.pow(hardness * r / (2 * sig), 2)), 0.05, 1);
if (len > w * 1.3) { if (len > w * 1.3) {
softStreak(g, p.x, p.y, (len / 2) * 1.1, (w / 2) * 1.5, col, a, ang, 0.3); const haloW = 1.7 + rng() * 0.4;
softStreak(g, p.x + ux * len * 0.1, p.y + uy * len * 0.1, (len / 2) * 1.25, (w / 2) * (1.7 + rng() * 0.4), col, a * 0.45, ang, 0.15); softStreak(ctx, p.x, p.y, (len / 2) * 1.1 + 1.2 * sig, (w / 2) * 1.5 + 1.2 * sig,
col, wetA((w / 2) * 1.5, 0.3), ang, wetH((len / 2) * 1.1, 0.3));
softStreak(ctx, p.x + ux * len * 0.1, p.y + uy * len * 0.1,
(len / 2) * 1.25 + 1.2 * sig, (w / 2) * haloW + 1.2 * sig,
col, wetA((w / 2) * haloW, 0.15) * 0.45, ang, wetH((len / 2) * 1.25, 0.15));
} else { } else {
softEllipse(g, p.x, p.y, w * 0.72, col, a, ang, sq, 0.30); const halo = 1.05 + rng() * 0.3;
softEllipse(g, p.x + ux * len * 0.18, p.y + uy * len * 0.18, w * (1.05 + rng() * 0.3), col, a * 0.45, ang, sq * 0.9, 0.18); softEllipse(ctx, p.x, p.y, w * 0.72 + 1.2 * sig, col, wetA(w * 0.72, 0.3), ang, sq, wetH(w * 0.72, 0.3));
softEllipse(ctx, p.x + ux * len * 0.18, p.y + uy * len * 0.18, w * halo + 1.2 * sig,
col, wetA(w * halo, 0.18) * 0.45, ang, sq * 0.9, wetH(w * halo, 0.18));
} }
});
} }
const REALIZERS = { const REALIZERS = {
......
...@@ -3,5 +3,14 @@ import soleil from './soleil.js'; ...@@ -3,5 +3,14 @@ import soleil from './soleil.js';
import coquelicots from './coquelicots.js'; import coquelicots from './coquelicots.js';
import nuit from './nuit.js'; import nuit from './nuit.js';
import vallee from './vallee.js'; import vallee from './vallee.js';
import skyline from './skyline.js';
import cafe from './cafe.js';
import jardin from './jardin.js';
import pluie from './pluie.js';
import mer from './mer.js';
import bled from './bled.js';
import nympheas from './nympheas.js';
import neige from './neige.js';
import tournesols from './tournesols.js';
export default [soleil, coquelicots, nuit, vallee]; export default [soleil, coquelicots, nuit, vallee, skyline, cafe, jardin, pluie, mer, bled, nympheas, neige, tournesols];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment