Upload app.js
Browse files- static/app.js +34 -7
static/app.js
CHANGED
|
@@ -25,6 +25,7 @@ const CLICK_T = 2; // … over 2 seconds
|
|
| 25 |
const STROKES_PER_FRAME = 18;
|
| 26 |
const CYCLE_MS = 5000;
|
| 27 |
const N = 5; // pairs in the ring (3 visible)
|
|
|
|
| 28 |
|
| 29 |
const SLOTS = [
|
| 30 |
{ y: 0, op: 1, scale: 1 }, // r0 mid · active
|
|
@@ -41,6 +42,10 @@ let mouse = { down: false, x: 0, y: 0, target: null, startSize: GLOBAL_START };
|
|
| 41 |
const expSize = (s0, s1, t, T) => Math.max(1, Math.round(s0 * Math.pow(s1 / s0, Math.min(t, T) / T)));
|
| 42 |
const globalSizeNow = () => expSize(GLOBAL_START, GLOBAL_END, (performance.now() - activeStart) / 1000, GLOBAL_T);
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
function makePair() {
|
| 45 |
const el = document.createElement("div");
|
| 46 |
el.className = "pair";
|
|
@@ -51,7 +56,9 @@ function makePair() {
|
|
| 51 |
const ctx = cv.getContext("2d");
|
| 52 |
ctx.fillStyle = "#161618";
|
| 53 |
ctx.fillRect(0, 0, CANVAS, CANVAS);
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
function proceduralFallback(p, seed) {
|
|
@@ -80,6 +87,7 @@ function loadSource(p) {
|
|
| 80 |
const seed = srcCursor++;
|
| 81 |
p.ctx.fillStyle = "#161618";
|
| 82 |
p.ctx.fillRect(0, 0, CANVAS, CANVAS);
|
|
|
|
| 83 |
const im = new Image();
|
| 84 |
im.crossOrigin = "anonymous";
|
| 85 |
im.onload = () => {
|
|
@@ -119,19 +127,38 @@ function tick() {
|
|
| 119 |
activeStart = performance.now();
|
| 120 |
}
|
| 121 |
|
|
|
|
|
|
|
| 122 |
function fillStroke(p, x, y, size) {
|
| 123 |
x = Math.max(0, Math.min(CANVAS - size, Math.round(x)));
|
| 124 |
y = Math.max(0, Math.min(CANVAS - size, Math.round(y)));
|
| 125 |
-
const
|
| 126 |
-
if (!
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
for (let yy = y; yy < y + size; yy++) {
|
| 129 |
for (let xx = x; xx < x + size; xx++) {
|
| 130 |
-
const
|
| 131 |
-
|
| 132 |
}
|
| 133 |
}
|
| 134 |
-
p.ctx.fillStyle = `rgb(${
|
| 135 |
p.ctx.fillRect(x, y, size, size);
|
| 136 |
}
|
| 137 |
|
|
|
|
| 25 |
const STROKES_PER_FRAME = 18;
|
| 26 |
const CYCLE_MS = 5000;
|
| 27 |
const N = 5; // pairs in the ring (3 visible)
|
| 28 |
+
const BG = [22, 22, 24]; // canvas seed color (#161618)
|
| 29 |
|
| 30 |
const SLOTS = [
|
| 31 |
{ y: 0, op: 1, scale: 1 }, // r0 mid · active
|
|
|
|
| 42 |
const expSize = (s0, s1, t, T) => Math.max(1, Math.round(s0 * Math.pow(s1 / s0, Math.min(t, T) / T)));
|
| 43 |
const globalSizeNow = () => expSize(GLOBAL_START, GLOBAL_END, (performance.now() - activeStart) / 1000, GLOBAL_T);
|
| 44 |
|
| 45 |
+
function resetBuf(p) {
|
| 46 |
+
for (let i = 0; i < p.buf.length; i += 3) { p.buf[i] = BG[0]; p.buf[i + 1] = BG[1]; p.buf[i + 2] = BG[2]; }
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
function makePair() {
|
| 50 |
const el = document.createElement("div");
|
| 51 |
el.className = "pair";
|
|
|
|
| 56 |
const ctx = cv.getContext("2d");
|
| 57 |
ctx.fillStyle = "#161618";
|
| 58 |
ctx.fillRect(0, 0, CANVAS, CANVAS);
|
| 59 |
+
const p = { el, img: el.querySelector("img"), canvas: cv, ctx, srcData: null, buf: new Uint8ClampedArray(CANVAS * CANVAS * 3) };
|
| 60 |
+
resetBuf(p);
|
| 61 |
+
return p;
|
| 62 |
}
|
| 63 |
|
| 64 |
function proceduralFallback(p, seed) {
|
|
|
|
| 87 |
const seed = srcCursor++;
|
| 88 |
p.ctx.fillStyle = "#161618";
|
| 89 |
p.ctx.fillRect(0, 0, CANVAS, CANVAS);
|
| 90 |
+
resetBuf(p);
|
| 91 |
const im = new Image();
|
| 92 |
im.crossOrigin = "anonymous";
|
| 93 |
im.onload = () => {
|
|
|
|
| 127 |
activeStart = performance.now();
|
| 128 |
}
|
| 129 |
|
| 130 |
+
// Places a stroke only if the source-area average lowers the error over that area
|
| 131 |
+
// (just like PBC discarding a stroke whose best multiplier is 0).
|
| 132 |
function fillStroke(p, x, y, size) {
|
| 133 |
x = Math.max(0, Math.min(CANVAS - size, Math.round(x)));
|
| 134 |
y = Math.max(0, Math.min(CANVAS - size, Math.round(y)));
|
| 135 |
+
const src = p.srcData, buf = p.buf;
|
| 136 |
+
if (!src) return;
|
| 137 |
+
|
| 138 |
+
let sR = 0, sG = 0, sB = 0, sq = 0, before = 0;
|
| 139 |
+
for (let yy = y; yy < y + size; yy++) {
|
| 140 |
+
for (let xx = x; xx < x + size; xx++) {
|
| 141 |
+
const si = (yy * CANVAS + xx) * 4, bi = (yy * CANVAS + xx) * 3;
|
| 142 |
+
const r = src[si], g = src[si + 1], b = src[si + 2];
|
| 143 |
+
const cr = buf[bi], cg = buf[bi + 1], cb = buf[bi + 2];
|
| 144 |
+
sR += r; sG += g; sB += b;
|
| 145 |
+
sq += r * r + g * g + b * b;
|
| 146 |
+
before += (r - cr) * (r - cr) + (g - cg) * (g - cg) + (b - cb) * (b - cb);
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
const n = size * size;
|
| 150 |
+
// error after filling with the area average == variance of the source area
|
| 151 |
+
const after = sq - (sR * sR + sG * sG + sB * sB) / n;
|
| 152 |
+
if (after >= before) return; // would not improve → skip
|
| 153 |
+
|
| 154 |
+
const mr = Math.round(sR / n), mg = Math.round(sG / n), mb = Math.round(sB / n);
|
| 155 |
for (let yy = y; yy < y + size; yy++) {
|
| 156 |
for (let xx = x; xx < x + size; xx++) {
|
| 157 |
+
const bi = (yy * CANVAS + xx) * 3;
|
| 158 |
+
buf[bi] = mr; buf[bi + 1] = mg; buf[bi + 2] = mb;
|
| 159 |
}
|
| 160 |
}
|
| 161 |
+
p.ctx.fillStyle = `rgb(${mr},${mg},${mb})`;
|
| 162 |
p.ctx.fillRect(x, y, size, size);
|
| 163 |
}
|
| 164 |
|