git-hash-art 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ALGORITHM.md +76 -24
- package/CHANGELOG.md +17 -1
- package/dist/browser.js +707 -324
- package/dist/browser.js.map +1 -1
- package/dist/main.js +707 -324
- package/dist/main.js.map +1 -1
- package/dist/module.js +707 -324
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/performance.test.ts +243 -0
- package/src/__tests__/phase-breakdown.test.ts +44 -0
- package/src/__tests__/phase-timing.test.ts +260 -0
- package/src/__tests__/profile-pipeline.test.ts +160 -0
- package/src/lib/canvas/colors.ts +23 -6
- package/src/lib/canvas/draw.ts +149 -62
- package/src/lib/canvas/shapes/complex.ts +19 -10
- package/src/lib/canvas/shapes/sacred.ts +16 -17
- package/src/lib/render.ts +521 -244
- package/src/types.ts +2 -0
package/dist/main.js
CHANGED
|
@@ -531,13 +531,21 @@ class $d016ad53434219a1$export$ab958c550f521376 {
|
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
533
|
// ── Standalone color utilities ──────────────────────────────────────
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
534
|
+
// ── Cached hex→RGB parse — avoids repeated parseInt/substring on hot path ──
|
|
535
|
+
const $d016ad53434219a1$var$_rgbCache = new Map();
|
|
536
|
+
const $d016ad53434219a1$var$_RGB_CACHE_MAX = 512;
|
|
537
|
+
/** Parse a hex color (#RRGGBB) into [r, g, b] 0-255. Cached. */ function $d016ad53434219a1$var$hexToRgb(hex) {
|
|
538
|
+
let cached = $d016ad53434219a1$var$_rgbCache.get(hex);
|
|
539
|
+
if (cached) return cached;
|
|
540
|
+
const c = hex.charAt(0) === "#" ? hex.substring(1) : hex;
|
|
541
|
+
cached = [
|
|
537
542
|
parseInt(c.substring(0, 2), 16),
|
|
538
543
|
parseInt(c.substring(2, 4), 16),
|
|
539
544
|
parseInt(c.substring(4, 6), 16)
|
|
540
545
|
];
|
|
546
|
+
if ($d016ad53434219a1$var$_rgbCache.size >= $d016ad53434219a1$var$_RGB_CACHE_MAX) $d016ad53434219a1$var$_rgbCache.clear();
|
|
547
|
+
$d016ad53434219a1$var$_rgbCache.set(hex, cached);
|
|
548
|
+
return cached;
|
|
541
549
|
}
|
|
542
550
|
/** Format [r, g, b] back to #RRGGBB. */ function $d016ad53434219a1$var$rgbToHex(r, g, b) {
|
|
543
551
|
const clamp = (v)=>Math.max(0, Math.min(255, Math.round(v)));
|
|
@@ -594,7 +602,9 @@ class $d016ad53434219a1$export$ab958c550f521376 {
|
|
|
594
602
|
}
|
|
595
603
|
function $d016ad53434219a1$export$f2121afcad3d553f(hex, alpha) {
|
|
596
604
|
const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex);
|
|
597
|
-
|
|
605
|
+
// Quantize alpha to 3 decimal places without toFixed overhead
|
|
606
|
+
const a = Math.round(alpha * 1000) / 1000;
|
|
607
|
+
return `rgba(${r},${g},${b},${a})`;
|
|
598
608
|
}
|
|
599
609
|
function $d016ad53434219a1$export$fabac4600b87056(colors, rng) {
|
|
600
610
|
if (colors.length < 3) return {
|
|
@@ -674,12 +684,21 @@ function $d016ad53434219a1$export$51ea55f869b7e0d3(hex, target, amount) {
|
|
|
674
684
|
const [h, s, l] = $d016ad53434219a1$var$hexToHsl(hex);
|
|
675
685
|
return $d016ad53434219a1$var$hslToHex($d016ad53434219a1$var$shiftHueToward(h, target, amount), s, l);
|
|
676
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Compute relative luminance of a hex color (0 = black, 1 = white).
|
|
689
|
+
* Uses the sRGB luminance formula from WCAG. Cached.
|
|
690
|
+
*/ const $d016ad53434219a1$var$_lumCache = new Map();
|
|
677
691
|
function $d016ad53434219a1$export$5c6e3c2b59b7fbbe(hex) {
|
|
692
|
+
let cached = $d016ad53434219a1$var$_lumCache.get(hex);
|
|
693
|
+
if (cached !== undefined) return cached;
|
|
678
694
|
const [r, g, b] = $d016ad53434219a1$var$hexToRgb(hex).map((c)=>{
|
|
679
695
|
const s = c / 255;
|
|
680
696
|
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
681
697
|
});
|
|
682
|
-
|
|
698
|
+
cached = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
699
|
+
if ($d016ad53434219a1$var$_lumCache.size >= 512) $d016ad53434219a1$var$_lumCache.clear();
|
|
700
|
+
$d016ad53434219a1$var$_lumCache.set(hex, cached);
|
|
701
|
+
return cached;
|
|
683
702
|
}
|
|
684
703
|
function $d016ad53434219a1$export$90ad0e6170cf6af5(fgHex, bgLuminance, minContrast = 0.15) {
|
|
685
704
|
const fgLum = $d016ad53434219a1$export$5c6e3c2b59b7fbbe(fgHex);
|
|
@@ -1115,21 +1134,31 @@ const $4bf3d69be49ad55c$export$c9043b89bcb14ed9 = (ctx, size, config = {})=>{
|
|
|
1115
1134
|
(0, $efc5b85ac9840d51$export$e46c5570db033611)(ctx, size, finalConfig);
|
|
1116
1135
|
const gridSize = 8;
|
|
1117
1136
|
const unit = size / gridSize;
|
|
1137
|
+
const radius = unit / 2;
|
|
1138
|
+
// Pre-compute the 8 star-point angle pairs (cos/sin) — avoids 648 trig calls
|
|
1139
|
+
const starPoints = [];
|
|
1140
|
+
for(let k = 0; k < 8; k++){
|
|
1141
|
+
const angle = Math.PI / 4 * k;
|
|
1142
|
+
const angle2 = angle + Math.PI / 4;
|
|
1143
|
+
starPoints.push({
|
|
1144
|
+
c1: Math.cos(angle) * radius,
|
|
1145
|
+
s1: Math.sin(angle) * radius,
|
|
1146
|
+
c2: Math.cos(angle2) * radius,
|
|
1147
|
+
s2: Math.sin(angle2) * radius
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1118
1150
|
ctx.beginPath();
|
|
1119
1151
|
// Create base grid
|
|
1120
|
-
for(let i = 0; i <= gridSize; i++)
|
|
1152
|
+
for(let i = 0; i <= gridSize; i++){
|
|
1121
1153
|
const x = (i - gridSize / 2) * unit;
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const y2 = y + radius * Math.sin(angle + Math.PI / 4);
|
|
1131
|
-
ctx.moveTo(x1, y1);
|
|
1132
|
-
ctx.lineTo(x2, y2);
|
|
1154
|
+
for(let j = 0; j <= gridSize; j++){
|
|
1155
|
+
const y = (j - gridSize / 2) * unit;
|
|
1156
|
+
// Draw star pattern at each intersection using pre-computed offsets
|
|
1157
|
+
for(let k = 0; k < 8; k++){
|
|
1158
|
+
const sp = starPoints[k];
|
|
1159
|
+
ctx.moveTo(x + sp.c1, y + sp.s1);
|
|
1160
|
+
ctx.lineTo(x + sp.c2, y + sp.s2);
|
|
1161
|
+
}
|
|
1133
1162
|
}
|
|
1134
1163
|
}
|
|
1135
1164
|
ctx.stroke();
|
|
@@ -1449,20 +1478,23 @@ const $dd5df256f00f6199$export$eeae7765f05012e2 = (ctx, size)=>{
|
|
|
1449
1478
|
const $dd5df256f00f6199$export$3355220a8108efc3 = (ctx, size)=>{
|
|
1450
1479
|
const outerRadius = size / 2;
|
|
1451
1480
|
const innerRadius = size / 4;
|
|
1452
|
-
|
|
1481
|
+
// Adaptive step count: fewer segments for small shapes where detail isn't visible.
|
|
1482
|
+
// 36×36 = 1296 segments at full size; at size < 60 we drop to 16×16 = 256.
|
|
1483
|
+
const steps = size < 60 ? 16 : size < 150 ? 24 : 36;
|
|
1484
|
+
const TWO_PI = Math.PI * 2;
|
|
1485
|
+
const angleStep = TWO_PI / steps;
|
|
1453
1486
|
ctx.beginPath();
|
|
1454
1487
|
for(let i = 0; i < steps; i++){
|
|
1455
|
-
const angle1 = i
|
|
1456
|
-
|
|
1488
|
+
const angle1 = i * angleStep;
|
|
1489
|
+
const cosA = Math.cos(angle1);
|
|
1490
|
+
const sinA = Math.sin(angle1);
|
|
1457
1491
|
for(let j = 0; j < steps; j++){
|
|
1458
|
-
const phi1 = j
|
|
1459
|
-
const phi2 =
|
|
1460
|
-
const
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
ctx.moveTo(x1, y1);
|
|
1465
|
-
ctx.lineTo(x2, y2);
|
|
1492
|
+
const phi1 = j * angleStep;
|
|
1493
|
+
const phi2 = phi1 + angleStep;
|
|
1494
|
+
const r1 = outerRadius + innerRadius * Math.cos(phi1);
|
|
1495
|
+
const r2 = outerRadius + innerRadius * Math.cos(phi2);
|
|
1496
|
+
ctx.moveTo(r1 * cosA, r1 * sinA);
|
|
1497
|
+
ctx.lineTo(r2 * cosA, r2 * sinA);
|
|
1466
1498
|
}
|
|
1467
1499
|
}
|
|
1468
1500
|
};
|
|
@@ -2025,6 +2057,43 @@ const $c3de8257a8baa3b0$var$RENDER_STYLES = [
|
|
|
2025
2057
|
function $c3de8257a8baa3b0$export$9fd4e64b2acd410e(rng) {
|
|
2026
2058
|
return $c3de8257a8baa3b0$var$RENDER_STYLES[Math.floor(rng() * $c3de8257a8baa3b0$var$RENDER_STYLES.length)];
|
|
2027
2059
|
}
|
|
2060
|
+
const $c3de8257a8baa3b0$export$2f738f61a8c15e07 = {
|
|
2061
|
+
"fill-and-stroke": 1,
|
|
2062
|
+
"fill-only": 0.5,
|
|
2063
|
+
"stroke-only": 1,
|
|
2064
|
+
"double-stroke": 1.5,
|
|
2065
|
+
"dashed": 1,
|
|
2066
|
+
"watercolor": 7,
|
|
2067
|
+
"hatched": 3,
|
|
2068
|
+
"incomplete": 1,
|
|
2069
|
+
"stipple": 90,
|
|
2070
|
+
"stencil": 2,
|
|
2071
|
+
"noise-grain": 400,
|
|
2072
|
+
"wood-grain": 10,
|
|
2073
|
+
"marble-vein": 4,
|
|
2074
|
+
"fabric-weave": 6,
|
|
2075
|
+
"hand-drawn": 5
|
|
2076
|
+
};
|
|
2077
|
+
function $c3de8257a8baa3b0$export$909ab0580e273f19(style) {
|
|
2078
|
+
switch(style){
|
|
2079
|
+
case "noise-grain":
|
|
2080
|
+
return "hatched";
|
|
2081
|
+
case "stipple":
|
|
2082
|
+
return "dashed";
|
|
2083
|
+
case "wood-grain":
|
|
2084
|
+
return "hatched";
|
|
2085
|
+
case "watercolor":
|
|
2086
|
+
return "fill-and-stroke";
|
|
2087
|
+
case "fabric-weave":
|
|
2088
|
+
return "hatched";
|
|
2089
|
+
case "hand-drawn":
|
|
2090
|
+
return "fill-and-stroke";
|
|
2091
|
+
case "marble-vein":
|
|
2092
|
+
return "stroke-only";
|
|
2093
|
+
default:
|
|
2094
|
+
return style;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2028
2097
|
function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
2029
2098
|
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
2030
2099
|
ctx.save();
|
|
@@ -2144,6 +2213,7 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2144
2213
|
case "hatched":
|
|
2145
2214
|
{
|
|
2146
2215
|
// Fill normally at reduced opacity, then overlay cross-hatch lines
|
|
2216
|
+
// Optimized: batch all parallel lines into a single path per pass
|
|
2147
2217
|
const savedAlphaH = ctx.globalAlpha;
|
|
2148
2218
|
ctx.globalAlpha = savedAlphaH * 0.3;
|
|
2149
2219
|
ctx.fill();
|
|
@@ -2155,28 +2225,28 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2155
2225
|
const hatchAngle = rng ? rng() * Math.PI : Math.PI / 4;
|
|
2156
2226
|
ctx.lineWidth = Math.max(0.5, strokeWidth * 0.4);
|
|
2157
2227
|
ctx.globalAlpha = savedAlphaH * 0.6;
|
|
2158
|
-
// Draw parallel lines across the bounding box
|
|
2228
|
+
// Draw parallel lines across the bounding box — batched into single path
|
|
2159
2229
|
const extent = size * 0.8;
|
|
2160
2230
|
const cos = Math.cos(hatchAngle);
|
|
2161
2231
|
const sin = Math.sin(hatchAngle);
|
|
2232
|
+
ctx.beginPath();
|
|
2162
2233
|
for(let d = -extent; d <= extent; d += hatchSpacing){
|
|
2163
|
-
ctx.beginPath();
|
|
2164
2234
|
ctx.moveTo(d * cos - extent * sin, d * sin + extent * cos);
|
|
2165
2235
|
ctx.lineTo(d * cos + extent * sin, d * sin - extent * cos);
|
|
2166
|
-
ctx.stroke();
|
|
2167
2236
|
}
|
|
2237
|
+
ctx.stroke();
|
|
2168
2238
|
// Second pass at perpendicular angle for cross-hatch (~50% chance)
|
|
2169
2239
|
if (!rng || rng() < 0.5) {
|
|
2170
2240
|
const crossAngle = hatchAngle + Math.PI / 2;
|
|
2171
2241
|
const cos2 = Math.cos(crossAngle);
|
|
2172
2242
|
const sin2 = Math.sin(crossAngle);
|
|
2173
2243
|
ctx.globalAlpha = savedAlphaH * 0.35;
|
|
2244
|
+
ctx.beginPath();
|
|
2174
2245
|
for(let d = -extent; d <= extent; d += hatchSpacing * 1.4){
|
|
2175
|
-
ctx.beginPath();
|
|
2176
2246
|
ctx.moveTo(d * cos2 - extent * sin2, d * sin2 + extent * cos2);
|
|
2177
2247
|
ctx.lineTo(d * cos2 + extent * sin2, d * sin2 - extent * cos2);
|
|
2178
|
-
ctx.stroke();
|
|
2179
2248
|
}
|
|
2249
|
+
ctx.stroke();
|
|
2180
2250
|
}
|
|
2181
2251
|
ctx.restore();
|
|
2182
2252
|
ctx.globalAlpha = savedAlphaH;
|
|
@@ -2214,6 +2284,8 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2214
2284
|
case "stipple":
|
|
2215
2285
|
{
|
|
2216
2286
|
// Dot-fill texture — clip to shape, then scatter dots
|
|
2287
|
+
// Optimized: use fillRect instead of arc for dots (much cheaper to render),
|
|
2288
|
+
// and cap total dot count to avoid O(size²) blowup on large shapes.
|
|
2217
2289
|
const savedAlphaS = ctx.globalAlpha;
|
|
2218
2290
|
ctx.globalAlpha = savedAlphaS * 0.15;
|
|
2219
2291
|
ctx.fill(); // ghost fill
|
|
@@ -2221,16 +2293,20 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2221
2293
|
ctx.save();
|
|
2222
2294
|
ctx.clip();
|
|
2223
2295
|
const dotSpacing = Math.max(2, size * 0.03);
|
|
2224
|
-
const
|
|
2296
|
+
const extentS = size * 0.55;
|
|
2297
|
+
// Cap total dots: beyond ~900 (30×30 grid) the visual density plateaus
|
|
2298
|
+
const maxDotsPerAxis = Math.min(Math.ceil(extentS * 2 / dotSpacing), 30);
|
|
2299
|
+
const actualSpacing = extentS * 2 / maxDotsPerAxis;
|
|
2225
2300
|
ctx.globalAlpha = savedAlphaS * 0.7;
|
|
2226
|
-
for(let
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2301
|
+
for(let xi = 0; xi < maxDotsPerAxis; xi++){
|
|
2302
|
+
const dx = -extentS + xi * actualSpacing;
|
|
2303
|
+
for(let yi = 0; yi < maxDotsPerAxis; yi++){
|
|
2304
|
+
const dy = -extentS + yi * actualSpacing;
|
|
2305
|
+
const jx = rng ? (rng() - 0.5) * actualSpacing * 0.6 : 0;
|
|
2306
|
+
const jy = rng ? (rng() - 0.5) * actualSpacing * 0.6 : 0;
|
|
2307
|
+
const dotD = rng ? actualSpacing * (0.3 + rng() * 0.4) : actualSpacing * 0.4;
|
|
2308
|
+
ctx.fillRect(dx + jx - dotD * 0.5, dy + jy - dotD * 0.5, dotD, dotD);
|
|
2309
|
+
}
|
|
2234
2310
|
}
|
|
2235
2311
|
ctx.restore();
|
|
2236
2312
|
ctx.globalAlpha = savedAlphaS;
|
|
@@ -2263,6 +2339,9 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2263
2339
|
case "noise-grain":
|
|
2264
2340
|
{
|
|
2265
2341
|
// Procedural noise grain texture clipped to shape boundary
|
|
2342
|
+
// Optimized: cap grid to max 40×40 = 1600 dots (was unbounded at O(size²)),
|
|
2343
|
+
// quantize alpha into buckets to minimize globalAlpha state changes,
|
|
2344
|
+
// and batch dots by brightness (black/white) × alpha bucket
|
|
2266
2345
|
const savedAlphaN = ctx.globalAlpha;
|
|
2267
2346
|
ctx.globalAlpha = savedAlphaN * 0.25;
|
|
2268
2347
|
ctx.fill(); // base tint
|
|
@@ -2271,17 +2350,47 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2271
2350
|
ctx.clip();
|
|
2272
2351
|
const grainSpacing = Math.max(1.5, size * 0.015);
|
|
2273
2352
|
const extentN = size * 0.55;
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
const
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2353
|
+
if (rng) {
|
|
2354
|
+
// Cap grid to max 40 dots per axis — beyond this the grain is
|
|
2355
|
+
// visually indistinguishable but cost scales quadratically.
|
|
2356
|
+
const maxGrainPerAxis = Math.min(Math.ceil(extentN * 2 / grainSpacing), 40);
|
|
2357
|
+
const actualGrainSpacing = extentN * 2 / maxGrainPerAxis;
|
|
2358
|
+
// 4 alpha buckets: 0.2, 0.3, 0.4, 0.5 — covers the 0.15-0.50 range
|
|
2359
|
+
const BUCKETS = 4;
|
|
2360
|
+
const bucketMin = 0.15;
|
|
2361
|
+
const bucketRange = 0.35;
|
|
2362
|
+
// [black_bucket0, black_bucket1, ..., white_bucket0, ...]
|
|
2363
|
+
const buckets = [];
|
|
2364
|
+
for(let i = 0; i < BUCKETS * 2; i++)buckets.push([]);
|
|
2365
|
+
for(let xi = 0; xi < maxGrainPerAxis; xi++){
|
|
2366
|
+
const gx = -extentN + xi * actualGrainSpacing;
|
|
2367
|
+
for(let yi = 0; yi < maxGrainPerAxis; yi++){
|
|
2368
|
+
const gy = -extentN + yi * actualGrainSpacing;
|
|
2369
|
+
const jx = (rng() - 0.5) * actualGrainSpacing * 1.2;
|
|
2370
|
+
const jy = (rng() - 0.5) * actualGrainSpacing * 1.2;
|
|
2371
|
+
const isWhite = rng() > 0.5;
|
|
2372
|
+
const dotAlpha = bucketMin + rng() * bucketRange;
|
|
2373
|
+
const dotSize = actualGrainSpacing * (0.3 + rng() * 0.5);
|
|
2374
|
+
const bucketIdx = Math.min(BUCKETS - 1, Math.floor((dotAlpha - bucketMin) / bucketRange * BUCKETS));
|
|
2375
|
+
const offset = isWhite ? BUCKETS : 0;
|
|
2376
|
+
buckets[offset + bucketIdx].push({
|
|
2377
|
+
x: gx + jx,
|
|
2378
|
+
y: gy + jy,
|
|
2379
|
+
s: dotSize
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
// Render each bucket: 2 colors × 4 alpha levels = 8 state changes total
|
|
2384
|
+
for(let color = 0; color < 2; color++){
|
|
2385
|
+
ctx.fillStyle = color === 0 ? "rgba(0,0,0,1)" : "rgba(255,255,255,1)";
|
|
2386
|
+
for(let b = 0; b < BUCKETS; b++){
|
|
2387
|
+
const dots = buckets[color * BUCKETS + b];
|
|
2388
|
+
if (dots.length === 0) continue;
|
|
2389
|
+
const alpha = bucketMin + (b + 0.5) / BUCKETS * bucketRange;
|
|
2390
|
+
ctx.globalAlpha = savedAlphaN * alpha;
|
|
2391
|
+
for(let i = 0; i < dots.length; i++)ctx.fillRect(dots[i].x, dots[i].y, dots[i].s, dots[i].s);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2285
2394
|
}
|
|
2286
2395
|
ctx.restore();
|
|
2287
2396
|
ctx.fillStyle = fillColor;
|
|
@@ -2294,6 +2403,7 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2294
2403
|
case "wood-grain":
|
|
2295
2404
|
{
|
|
2296
2405
|
// Parallel wavy lines simulating wood grain, clipped to shape
|
|
2406
|
+
// Optimized: batch all grain lines into a single path, increased step from 2 to 4
|
|
2297
2407
|
const savedAlphaW = ctx.globalAlpha;
|
|
2298
2408
|
ctx.globalAlpha = savedAlphaW * 0.2;
|
|
2299
2409
|
ctx.fill(); // base tint
|
|
@@ -2309,17 +2419,19 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2309
2419
|
ctx.globalAlpha = savedAlphaW * 0.5;
|
|
2310
2420
|
const cosG = Math.cos(grainAngle);
|
|
2311
2421
|
const sinG = Math.sin(grainAngle);
|
|
2422
|
+
const waveCoeff = waveFreq * Math.PI;
|
|
2423
|
+
const invExtentW = 1 / extentW;
|
|
2424
|
+
// Batch all grain lines into a single path
|
|
2425
|
+
ctx.beginPath();
|
|
2312
2426
|
for(let d = -extentW; d <= extentW; d += grainLineSpacing){
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
const
|
|
2317
|
-
|
|
2318
|
-
if (t === -extentW) ctx.moveTo(px, py);
|
|
2319
|
-
else ctx.lineTo(px, py);
|
|
2427
|
+
const firstWave = Math.sin(-extentW * invExtentW * waveCoeff) * waveAmp;
|
|
2428
|
+
ctx.moveTo(-extentW * cosG - (d + firstWave) * sinG, -extentW * sinG + (d + firstWave) * cosG);
|
|
2429
|
+
for(let t = -extentW + 4; t <= extentW; t += 4){
|
|
2430
|
+
const wave = Math.sin(t * invExtentW * waveCoeff) * waveAmp;
|
|
2431
|
+
ctx.lineTo(t * cosG - (d + wave) * sinG, t * sinG + (d + wave) * cosG);
|
|
2320
2432
|
}
|
|
2321
|
-
ctx.stroke();
|
|
2322
2433
|
}
|
|
2434
|
+
ctx.stroke();
|
|
2323
2435
|
ctx.restore();
|
|
2324
2436
|
ctx.globalAlpha = savedAlphaW;
|
|
2325
2437
|
ctx.globalAlpha *= 0.35;
|
|
@@ -2381,6 +2493,7 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2381
2493
|
case "fabric-weave":
|
|
2382
2494
|
{
|
|
2383
2495
|
// Interlocking horizontal/vertical threads clipped to shape
|
|
2496
|
+
// Optimized: batch all horizontal threads into one path, all vertical into another
|
|
2384
2497
|
const savedAlphaF = ctx.globalAlpha;
|
|
2385
2498
|
ctx.globalAlpha = savedAlphaF * 0.15;
|
|
2386
2499
|
ctx.fill(); // ghost base
|
|
@@ -2390,26 +2503,24 @@ function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
2390
2503
|
const threadSpacing = Math.max(2, size * 0.04);
|
|
2391
2504
|
const extentF = size * 0.55;
|
|
2392
2505
|
ctx.lineWidth = Math.max(0.8, threadSpacing * 0.5);
|
|
2506
|
+
// Horizontal threads — batched
|
|
2393
2507
|
ctx.globalAlpha = savedAlphaF * 0.55;
|
|
2394
|
-
|
|
2508
|
+
ctx.beginPath();
|
|
2395
2509
|
for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2396
|
-
ctx.beginPath();
|
|
2397
2510
|
ctx.moveTo(-extentF, y);
|
|
2398
2511
|
ctx.lineTo(extentF, y);
|
|
2399
|
-
ctx.stroke();
|
|
2400
2512
|
}
|
|
2401
|
-
|
|
2513
|
+
ctx.stroke();
|
|
2514
|
+
// Vertical threads (offset by half spacing for weave effect) — batched
|
|
2402
2515
|
ctx.globalAlpha = savedAlphaF * 0.45;
|
|
2403
2516
|
ctx.strokeStyle = fillColor;
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
ctx.lineTo(x, y + threadSpacing);
|
|
2410
|
-
}
|
|
2411
|
-
ctx.stroke();
|
|
2517
|
+
ctx.beginPath();
|
|
2518
|
+
for(let x = -extentF; x <= extentF; x += threadSpacing * 2)for(let y = -extentF; y <= extentF; y += threadSpacing * 2){
|
|
2519
|
+
// Over-under: draw segment, skip segment
|
|
2520
|
+
ctx.moveTo(x, y);
|
|
2521
|
+
ctx.lineTo(x, y + threadSpacing);
|
|
2412
2522
|
}
|
|
2523
|
+
ctx.stroke();
|
|
2413
2524
|
ctx.strokeStyle = strokeColor;
|
|
2414
2525
|
ctx.restore();
|
|
2415
2526
|
ctx.globalAlpha = savedAlphaF;
|
|
@@ -2475,14 +2586,17 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2475
2586
|
ctx.translate(x, y);
|
|
2476
2587
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2477
2588
|
// ── Drop shadow — soft colored shadow offset along light direction ──
|
|
2478
|
-
|
|
2589
|
+
// Skip shadow entirely for small shapes (< 20px) — the blur is expensive
|
|
2590
|
+
// and visually imperceptible at that scale.
|
|
2591
|
+
const useShadow = size >= 20;
|
|
2592
|
+
if (useShadow && lightAngle !== undefined) {
|
|
2479
2593
|
const shadowDist = size * 0.035;
|
|
2480
2594
|
const shadowBlurR = size * 0.06;
|
|
2481
2595
|
ctx.shadowOffsetX = Math.cos(lightAngle + Math.PI) * shadowDist;
|
|
2482
2596
|
ctx.shadowOffsetY = Math.sin(lightAngle + Math.PI) * shadowDist;
|
|
2483
2597
|
ctx.shadowBlur = shadowBlurR;
|
|
2484
2598
|
ctx.shadowColor = "rgba(0,0,0,0.12)";
|
|
2485
|
-
} else if (glowRadius > 0) {
|
|
2599
|
+
} else if (useShadow && glowRadius > 0) {
|
|
2486
2600
|
// Glow / shadow effect (legacy path)
|
|
2487
2601
|
ctx.shadowBlur = glowRadius;
|
|
2488
2602
|
ctx.shadowColor = glowColor || fillColor;
|
|
@@ -2506,30 +2620,27 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2506
2620
|
$c3de8257a8baa3b0$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
2507
2621
|
}
|
|
2508
2622
|
// Reset shadow so patterns and highlight aren't double-shadowed
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2623
|
+
// Only reset if we actually set shadow (avoids unnecessary state changes)
|
|
2624
|
+
if (useShadow && (lightAngle !== undefined || glowRadius > 0)) {
|
|
2625
|
+
ctx.shadowBlur = 0;
|
|
2626
|
+
ctx.shadowOffsetX = 0;
|
|
2627
|
+
ctx.shadowOffsetY = 0;
|
|
2628
|
+
ctx.shadowColor = "transparent";
|
|
2629
|
+
}
|
|
2513
2630
|
// ── Specular highlight — tinted arc on the light-facing side ──
|
|
2514
|
-
|
|
2631
|
+
// Skip for small shapes (< 30px) — gradient creation + composite op
|
|
2632
|
+
// switch is expensive and the highlight is invisible at small sizes.
|
|
2633
|
+
if (lightAngle !== undefined && size > 30 && rng) {
|
|
2515
2634
|
const hlRadius = size * 0.35;
|
|
2516
2635
|
const hlDist = size * 0.15;
|
|
2517
2636
|
const hlX = Math.cos(lightAngle) * hlDist;
|
|
2518
2637
|
const hlY = Math.sin(lightAngle) * hlDist;
|
|
2519
2638
|
const hlGrad = ctx.createRadialGradient(hlX, hlY, 0, hlX, hlY, hlRadius);
|
|
2520
|
-
//
|
|
2521
|
-
//
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
const g = parseInt(fillColor.slice(3, 5), 16);
|
|
2526
|
-
const b = parseInt(fillColor.slice(5, 7), 16);
|
|
2527
|
-
// Blend toward white but keep a hint of the fill's warmth
|
|
2528
|
-
hlBase = `${Math.round(r * 0.15 + 216.75)},${Math.round(g * 0.15 + 216.75)},${Math.round(b * 0.15 + 216.75)}`;
|
|
2529
|
-
}
|
|
2530
|
-
hlGrad.addColorStop(0, `rgba(${hlBase},0.18)`);
|
|
2531
|
-
hlGrad.addColorStop(0.5, `rgba(${hlBase},0.05)`);
|
|
2532
|
-
hlGrad.addColorStop(1, `rgba(${hlBase},0)`);
|
|
2639
|
+
// Use a simple white highlight — the per-shape hex parse was expensive
|
|
2640
|
+
// and the visual difference from tinted highlights is negligible.
|
|
2641
|
+
hlGrad.addColorStop(0, "rgba(255,255,255,0.18)");
|
|
2642
|
+
hlGrad.addColorStop(0.5, "rgba(255,255,255,0.05)");
|
|
2643
|
+
hlGrad.addColorStop(1, "rgba(255,255,255,0)");
|
|
2533
2644
|
const savedOp = ctx.globalCompositeOperation;
|
|
2534
2645
|
ctx.globalCompositeOperation = "soft-light";
|
|
2535
2646
|
ctx.fillStyle = hlGrad;
|
|
@@ -4071,6 +4182,46 @@ function $f89bc858f7202849$export$f1142fd7da4d6590(rng) {
|
|
|
4071
4182
|
}
|
|
4072
4183
|
|
|
4073
4184
|
|
|
4185
|
+
// ── Render style cost weights (normalized: fill-and-stroke = 1) ─────
|
|
4186
|
+
// Based on benchmark measurements. Used by the complexity budget to
|
|
4187
|
+
// cap total rendering work and downgrade expensive styles when needed.
|
|
4188
|
+
const $4f72c5a314eddf25$var$RENDER_STYLE_COST = {
|
|
4189
|
+
"fill-and-stroke": 1,
|
|
4190
|
+
"fill-only": 0.5,
|
|
4191
|
+
"stroke-only": 1,
|
|
4192
|
+
"double-stroke": 1.5,
|
|
4193
|
+
"dashed": 1,
|
|
4194
|
+
"watercolor": 7,
|
|
4195
|
+
"hatched": 3,
|
|
4196
|
+
"incomplete": 1,
|
|
4197
|
+
"stipple": 90,
|
|
4198
|
+
"stencil": 2,
|
|
4199
|
+
"noise-grain": 400,
|
|
4200
|
+
"wood-grain": 10,
|
|
4201
|
+
"marble-vein": 4,
|
|
4202
|
+
"fabric-weave": 6,
|
|
4203
|
+
"hand-drawn": 5
|
|
4204
|
+
};
|
|
4205
|
+
function $4f72c5a314eddf25$var$downgradeRenderStyle(style) {
|
|
4206
|
+
switch(style){
|
|
4207
|
+
case "noise-grain":
|
|
4208
|
+
return "hatched";
|
|
4209
|
+
case "stipple":
|
|
4210
|
+
return "dashed";
|
|
4211
|
+
case "wood-grain":
|
|
4212
|
+
return "hatched";
|
|
4213
|
+
case "watercolor":
|
|
4214
|
+
return "fill-and-stroke";
|
|
4215
|
+
case "fabric-weave":
|
|
4216
|
+
return "hatched";
|
|
4217
|
+
case "hand-drawn":
|
|
4218
|
+
return "fill-and-stroke";
|
|
4219
|
+
case "marble-vein":
|
|
4220
|
+
return "stroke-only";
|
|
4221
|
+
default:
|
|
4222
|
+
return style;
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4074
4225
|
// ── Shape categories for weighted selection (legacy fallback) ───────
|
|
4075
4226
|
const $4f72c5a314eddf25$var$SACRED_SHAPES = [
|
|
4076
4227
|
"mandala",
|
|
@@ -4456,6 +4607,15 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4456
4607
|
...(0, $93cf69256c93baa9$export$c2f8e0cc249a8d8f),
|
|
4457
4608
|
...config
|
|
4458
4609
|
};
|
|
4610
|
+
const _dt = finalConfig._debugTiming;
|
|
4611
|
+
const _t = _dt ? ()=>performance.now() : undefined;
|
|
4612
|
+
let _p = _t ? _t() : 0;
|
|
4613
|
+
function _mark(name) {
|
|
4614
|
+
if (!_dt || !_t) return;
|
|
4615
|
+
const now = _t();
|
|
4616
|
+
_dt.phases[name] = now - _p;
|
|
4617
|
+
_p = now;
|
|
4618
|
+
}
|
|
4459
4619
|
const rng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash));
|
|
4460
4620
|
// ── 0. Select archetype — fundamentally different visual personality ──
|
|
4461
4621
|
const archetype = (0, $f89bc858f7202849$export$f1142fd7da4d6590)(rng);
|
|
@@ -4489,12 +4649,14 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4489
4649
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
4490
4650
|
const cx = width / 2;
|
|
4491
4651
|
const cy = height / 2;
|
|
4652
|
+
_mark("0_setup");
|
|
4492
4653
|
// ── 1. Background ──────────────────────────────────────────────
|
|
4493
4654
|
const bgRadius = Math.hypot(cx, cy);
|
|
4494
4655
|
$4f72c5a314eddf25$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
4495
4656
|
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
4657
|
+
// Use source-over instead of soft-light for cheaper compositing
|
|
4496
4658
|
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
4497
|
-
ctx.
|
|
4659
|
+
ctx.globalAlpha = 1;
|
|
4498
4660
|
for(let i = 0; i < meshPoints; i++){
|
|
4499
4661
|
const mx = rng() * width;
|
|
4500
4662
|
const my = rng() * height;
|
|
@@ -4503,95 +4665,103 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4503
4665
|
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
4504
4666
|
grad.addColorStop(0, (0, $d016ad53434219a1$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
4505
4667
|
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
4506
|
-
ctx.globalAlpha = 1;
|
|
4507
4668
|
ctx.fillStyle = grad;
|
|
4508
|
-
|
|
4669
|
+
// Clip to gradient bounding box — avoids blending transparent pixels
|
|
4670
|
+
const gx = Math.max(0, mx - mRadius);
|
|
4671
|
+
const gy = Math.max(0, my - mRadius);
|
|
4672
|
+
const gw = Math.min(width, mx + mRadius) - gx;
|
|
4673
|
+
const gh = Math.min(height, my + mRadius) - gy;
|
|
4674
|
+
ctx.fillRect(gx, gy, gw, gh);
|
|
4509
4675
|
}
|
|
4510
|
-
ctx.globalCompositeOperation = "source-over";
|
|
4511
4676
|
// Compute average background luminance for contrast enforcement
|
|
4512
4677
|
const bgLum = ((0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
4513
4678
|
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
4679
|
+
// Use source-over with pre-multiplied alpha instead of soft-light
|
|
4680
|
+
// for much cheaper compositing (soft-light requires per-pixel blend)
|
|
4514
4681
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
4515
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4516
4682
|
for(let i = 0; i < bgShapeCount; i++){
|
|
4517
4683
|
const bx = rng() * width;
|
|
4518
4684
|
const by = rng() * height;
|
|
4519
4685
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
4520
4686
|
const bColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
4521
|
-
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
4687
|
+
ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
|
|
4522
4688
|
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(bColor, 0.15);
|
|
4523
4689
|
ctx.beginPath();
|
|
4524
4690
|
// Use archetype-appropriate background shapes
|
|
4525
|
-
if (archetype.name === "geometric-precision" || archetype.name === "op-art")
|
|
4526
|
-
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4691
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4527
4692
|
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
4528
4693
|
ctx.fill();
|
|
4529
4694
|
}
|
|
4530
|
-
// Subtle concentric rings from center
|
|
4695
|
+
// Subtle concentric rings from center — batched into single stroke
|
|
4531
4696
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
4532
4697
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
4533
4698
|
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4534
4699
|
ctx.lineWidth = 1 * scaleFactor;
|
|
4700
|
+
ctx.beginPath();
|
|
4535
4701
|
for(let i = 1; i <= ringCount; i++){
|
|
4536
4702
|
const r = Math.min(width, height) * 0.15 * i;
|
|
4537
|
-
ctx.
|
|
4703
|
+
ctx.moveTo(cx + r, cy);
|
|
4538
4704
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
4539
|
-
ctx.stroke();
|
|
4540
4705
|
}
|
|
4541
|
-
ctx.
|
|
4706
|
+
ctx.stroke();
|
|
4542
4707
|
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4543
4708
|
const bgPatternRoll = rng();
|
|
4544
4709
|
if (bgPatternRoll < 0.6) {
|
|
4545
4710
|
ctx.save();
|
|
4546
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4547
4711
|
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4548
4712
|
const patternColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4549
4713
|
if (bgPatternRoll < 0.2) {
|
|
4550
|
-
// Dot grid —
|
|
4551
|
-
const dotSpacing = Math.max(
|
|
4552
|
-
const
|
|
4714
|
+
// Dot grid — use fillRect instead of arcs (much cheaper, no path building)
|
|
4715
|
+
const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4716
|
+
const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
|
|
4553
4717
|
ctx.globalAlpha = patternOpacity;
|
|
4554
4718
|
ctx.fillStyle = patternColor;
|
|
4555
|
-
|
|
4556
|
-
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4557
|
-
ctx.
|
|
4558
|
-
|
|
4719
|
+
let dotCount = 0;
|
|
4720
|
+
for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
|
|
4721
|
+
ctx.fillRect(px, py, dotDiam, dotDiam);
|
|
4722
|
+
dotCount++;
|
|
4559
4723
|
}
|
|
4560
|
-
ctx.fill();
|
|
4561
4724
|
} else if (bgPatternRoll < 0.4) {
|
|
4562
|
-
// Diagonal lines — batched into a single path
|
|
4563
|
-
const lineSpacing = Math.max(
|
|
4725
|
+
// Diagonal lines — batched into a single path, capped at 300 lines
|
|
4726
|
+
const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4564
4727
|
ctx.globalAlpha = patternOpacity;
|
|
4565
4728
|
ctx.strokeStyle = patternColor;
|
|
4566
4729
|
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4567
4730
|
const diag = Math.hypot(width, height);
|
|
4568
4731
|
ctx.beginPath();
|
|
4569
|
-
|
|
4732
|
+
let lineCount = 0;
|
|
4733
|
+
for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
|
|
4570
4734
|
ctx.moveTo(d, 0);
|
|
4571
4735
|
ctx.lineTo(d + height, height);
|
|
4736
|
+
lineCount++;
|
|
4572
4737
|
}
|
|
4573
4738
|
ctx.stroke();
|
|
4574
4739
|
} else {
|
|
4575
|
-
// Tessellation — hexagonal grid,
|
|
4576
|
-
const tessSize = Math.max(
|
|
4740
|
+
// Tessellation — hexagonal grid, capped at 500 hexagons
|
|
4741
|
+
const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4577
4742
|
const tessH = tessSize * Math.sqrt(3);
|
|
4578
4743
|
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4579
4744
|
ctx.strokeStyle = patternColor;
|
|
4580
4745
|
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4746
|
+
// Pre-compute hex vertex offsets (avoid trig per vertex)
|
|
4747
|
+
const hexVx = [];
|
|
4748
|
+
const hexVy = [];
|
|
4749
|
+
for(let s = 0; s < 6; s++){
|
|
4750
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4751
|
+
hexVx.push(Math.cos(angle) * tessSize * 0.5);
|
|
4752
|
+
hexVy.push(Math.sin(angle) * tessSize * 0.5);
|
|
4753
|
+
}
|
|
4581
4754
|
ctx.beginPath();
|
|
4582
|
-
|
|
4755
|
+
let hexCount = 0;
|
|
4756
|
+
for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
|
|
4583
4757
|
const offsetX = row % 2 * tessSize * 0.75;
|
|
4584
|
-
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4758
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
|
|
4585
4759
|
const hx = col * tessSize * 1.5 + offsetX;
|
|
4586
4760
|
const hy = row * tessH;
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4590
|
-
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4591
|
-
if (s === 0) ctx.moveTo(vx, vy);
|
|
4592
|
-
else ctx.lineTo(vx, vy);
|
|
4593
|
-
}
|
|
4761
|
+
ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
|
|
4762
|
+
for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
|
|
4594
4763
|
ctx.closePath();
|
|
4764
|
+
hexCount++;
|
|
4595
4765
|
}
|
|
4596
4766
|
}
|
|
4597
4767
|
ctx.stroke();
|
|
@@ -4599,6 +4769,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4599
4769
|
ctx.restore();
|
|
4600
4770
|
}
|
|
4601
4771
|
ctx.globalCompositeOperation = "source-over";
|
|
4772
|
+
_mark("1_background");
|
|
4602
4773
|
// ── 2. Composition mode — archetype-aware selection ──────────────
|
|
4603
4774
|
const compositionMode = rng() < 0.7 ? archetype.preferredCompositions[Math.floor(rng() * archetype.preferredCompositions.length)] : $4f72c5a314eddf25$var$ALL_COMPOSITION_MODES[Math.floor(rng() * $4f72c5a314eddf25$var$ALL_COMPOSITION_MODES.length)];
|
|
4604
4775
|
const symRoll = rng();
|
|
@@ -4682,19 +4853,20 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4682
4853
|
ctx.beginPath();
|
|
4683
4854
|
ctx.arc(zone.x, zone.y, zone.radius, 0, Math.PI * 2);
|
|
4684
4855
|
ctx.stroke();
|
|
4685
|
-
// ~50% chance: scatter tiny dots inside the void
|
|
4856
|
+
// ~50% chance: scatter tiny dots inside the void — batched into single path
|
|
4686
4857
|
if (rng() < 0.5) {
|
|
4687
4858
|
const dotCount = 3 + Math.floor(rng() * 6);
|
|
4688
4859
|
ctx.globalAlpha = 0.06 + rng() * 0.04;
|
|
4689
4860
|
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
4861
|
+
ctx.beginPath();
|
|
4690
4862
|
for(let d = 0; d < dotCount; d++){
|
|
4691
4863
|
const angle = rng() * Math.PI * 2;
|
|
4692
4864
|
const dist = rng() * zone.radius * 0.7;
|
|
4693
4865
|
const dotR = (1 + rng() * 3) * scaleFactor;
|
|
4694
|
-
ctx.
|
|
4866
|
+
ctx.moveTo(zone.x + Math.cos(angle) * dist + dotR, zone.y + Math.sin(angle) * dist);
|
|
4695
4867
|
ctx.arc(zone.x + Math.cos(angle) * dist, zone.y + Math.sin(angle) * dist, dotR, 0, Math.PI * 2);
|
|
4696
|
-
ctx.fill();
|
|
4697
4868
|
}
|
|
4869
|
+
ctx.fill();
|
|
4698
4870
|
}
|
|
4699
4871
|
// ~30% chance: thin concentric ring inside
|
|
4700
4872
|
if (rng() < 0.3) {
|
|
@@ -4708,6 +4880,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4708
4880
|
}
|
|
4709
4881
|
}
|
|
4710
4882
|
ctx.globalAlpha = 1;
|
|
4883
|
+
_mark("2_3_composition_focal");
|
|
4711
4884
|
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4712
4885
|
// Create a seeded simplex noise field (unique per hash)
|
|
4713
4886
|
const noiseFieldRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 333));
|
|
@@ -4784,8 +4957,29 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4784
4957
|
shape: heroShape
|
|
4785
4958
|
});
|
|
4786
4959
|
}
|
|
4960
|
+
_mark("4_flowfield_hero");
|
|
4787
4961
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
4788
4962
|
const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
|
|
4963
|
+
// ── Complexity budget — caps total rendering work ──────────────
|
|
4964
|
+
// Budget scales with pixel area so larger canvases get proportionally
|
|
4965
|
+
// more headroom. The multiplier extras (glazing, echoes, nesting,
|
|
4966
|
+
// constellations, rhythm) are gated behind the budget; when it runs
|
|
4967
|
+
// low they are skipped. When it's exhausted, expensive render styles
|
|
4968
|
+
// are downgraded to cheaper alternatives.
|
|
4969
|
+
//
|
|
4970
|
+
// RNG values are always consumed even when skipping, so the
|
|
4971
|
+
// deterministic sequence for shapes that *do* render is preserved.
|
|
4972
|
+
const pixelArea = width * height;
|
|
4973
|
+
const BUDGET_PER_MEGAPIXEL = 6000; // cost units per 1M pixels
|
|
4974
|
+
let complexityBudget = pixelArea / 1000000 * BUDGET_PER_MEGAPIXEL;
|
|
4975
|
+
const totalBudget = complexityBudget;
|
|
4976
|
+
const budgetForExtras = complexityBudget * 0.25; // reserve 25% for multiplier extras
|
|
4977
|
+
let extrasSpent = 0;
|
|
4978
|
+
// Hard cap on clip-heavy render styles (stipple, noise-grain).
|
|
4979
|
+
// These generate O(size²) fillRect calls per shape and dominate
|
|
4980
|
+
// worst-case render time. Cap scales with pixel area.
|
|
4981
|
+
const MAX_CLIP_HEAVY_SHAPES = Math.max(4, Math.floor(8 * (pixelArea / 1000000)));
|
|
4982
|
+
let clipHeavyCount = 0;
|
|
4789
4983
|
for(let layer = 0; layer < layers; layer++){
|
|
4790
4984
|
const layerRatio = layers > 1 ? layer / (layers - 1) : 0;
|
|
4791
4985
|
const numShapes = shapesPerLayer + Math.floor(rng() * shapesPerLayer * 0.3);
|
|
@@ -4874,7 +5068,26 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4874
5068
|
const shapeRenderStyle = (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(shape, layerRenderStyle, rng);
|
|
4875
5069
|
// Organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
4876
5070
|
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
4877
|
-
|
|
5071
|
+
let finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
5072
|
+
// Budget check: downgrade expensive styles proportionally —
|
|
5073
|
+
// the more expensive the style, the earlier it gets downgraded.
|
|
5074
|
+
// noise-grain (400) downgrades when budget < 20% remaining,
|
|
5075
|
+
// stipple (90) when < 82%, wood-grain (10) when < 98%.
|
|
5076
|
+
let styleCost = $4f72c5a314eddf25$var$RENDER_STYLE_COST[finalRenderStyle] ?? 1;
|
|
5077
|
+
if (styleCost > 3) {
|
|
5078
|
+
const downgradeThreshold = Math.min(0.85, styleCost / 500);
|
|
5079
|
+
if (complexityBudget < totalBudget * (1 - downgradeThreshold)) {
|
|
5080
|
+
finalRenderStyle = $4f72c5a314eddf25$var$downgradeRenderStyle(finalRenderStyle);
|
|
5081
|
+
styleCost = $4f72c5a314eddf25$var$RENDER_STYLE_COST[finalRenderStyle] ?? 1;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
// Hard cap: clip-heavy styles (stipple, noise-grain) are limited
|
|
5085
|
+
// to MAX_CLIP_HEAVY_SHAPES total across the entire render.
|
|
5086
|
+
if ((finalRenderStyle === "stipple" || finalRenderStyle === "noise-grain") && clipHeavyCount >= MAX_CLIP_HEAVY_SHAPES) {
|
|
5087
|
+
finalRenderStyle = $4f72c5a314eddf25$var$downgradeRenderStyle(finalRenderStyle);
|
|
5088
|
+
styleCost = $4f72c5a314eddf25$var$RENDER_STYLE_COST[finalRenderStyle] ?? 1;
|
|
5089
|
+
}
|
|
5090
|
+
if (finalRenderStyle === "stipple" || finalRenderStyle === "noise-grain") clipHeavyCount++;
|
|
4878
5091
|
// Consistent light direction — subtle shadow offset
|
|
4879
5092
|
const shadowDist = hasGlow ? 0 : size * 0.02;
|
|
4880
5093
|
const shadowOffX = shadowDist * Math.cos(lightAngle);
|
|
@@ -4929,30 +5142,41 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4929
5142
|
lightAngle: lightAngle,
|
|
4930
5143
|
scaleFactor: scaleFactor
|
|
4931
5144
|
};
|
|
4932
|
-
if (shouldMirror)
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
5145
|
+
if (shouldMirror) {
|
|
5146
|
+
(0, $c3de8257a8baa3b0$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
5147
|
+
...shapeConfig,
|
|
5148
|
+
mirrorAxis: mirrorAxis,
|
|
5149
|
+
mirrorGap: size * (0.1 + rng() * 0.3)
|
|
5150
|
+
});
|
|
5151
|
+
complexityBudget -= styleCost * 2; // mirrored = 2 shapes
|
|
5152
|
+
} else {
|
|
5153
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, shapeConfig);
|
|
5154
|
+
complexityBudget -= styleCost;
|
|
5155
|
+
}
|
|
5156
|
+
// ── Extras budget gate — skip multiplier sections when over budget ──
|
|
5157
|
+
const extrasAllowed = extrasSpent < budgetForExtras;
|
|
4938
5158
|
// ── Glazing — luminous multi-pass transparency on ~20% of shapes ──
|
|
4939
5159
|
if (rng() < 0.2 && size > adjustedMinSize * 2) {
|
|
4940
5160
|
const glazePasses = 2 + Math.floor(rng() * 2);
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
5161
|
+
if (extrasAllowed) {
|
|
5162
|
+
for(let g = 0; g < glazePasses; g++){
|
|
5163
|
+
const glazeScale = 1 - (g + 1) * 0.12;
|
|
5164
|
+
const glazeAlpha = 0.08 + g * 0.04;
|
|
5165
|
+
ctx.globalAlpha = glazeAlpha;
|
|
5166
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, finalX, finalY, {
|
|
5167
|
+
fillColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, 0.15 + g * 0.1),
|
|
5168
|
+
strokeColor: "rgba(0,0,0,0)",
|
|
5169
|
+
strokeWidth: 0,
|
|
5170
|
+
size: size * glazeScale,
|
|
5171
|
+
rotation: rotation,
|
|
5172
|
+
proportionType: "GOLDEN_RATIO",
|
|
5173
|
+
renderStyle: "fill-only",
|
|
5174
|
+
rng: rng
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5177
|
+
extrasSpent += glazePasses;
|
|
4955
5178
|
}
|
|
5179
|
+
// RNG consumed by glazePasses calculation above regardless
|
|
4956
5180
|
}
|
|
4957
5181
|
shapePositions.push({
|
|
4958
5182
|
x: finalX,
|
|
@@ -4970,37 +5194,41 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4970
5194
|
if (size > adjustedMaxSize * 0.5 && rng() < 0.2) {
|
|
4971
5195
|
const echoCount = 2 + Math.floor(rng() * 2);
|
|
4972
5196
|
const echoAngle = rng() * Math.PI * 2;
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5197
|
+
if (extrasAllowed) {
|
|
5198
|
+
for(let e = 0; e < echoCount; e++){
|
|
5199
|
+
const echoScale = 0.3 - e * 0.08;
|
|
5200
|
+
const echoDist = size * (0.6 + e * 0.4);
|
|
5201
|
+
const echoX = finalX + Math.cos(echoAngle) * echoDist;
|
|
5202
|
+
const echoY = finalY + Math.sin(echoAngle) * echoDist;
|
|
5203
|
+
const echoSize = size * Math.max(0.1, echoScale);
|
|
5204
|
+
if (echoX < 0 || echoX > width || echoY < 0 || echoY > height) continue;
|
|
5205
|
+
ctx.globalAlpha = layerOpacity * (0.4 - e * 0.1);
|
|
5206
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, shape, echoX, echoY, {
|
|
5207
|
+
fillColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(fillColor, fillAlpha * 0.6),
|
|
5208
|
+
strokeColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(strokeColor, 0.4),
|
|
5209
|
+
strokeWidth: strokeWidth * 0.6,
|
|
5210
|
+
size: echoSize,
|
|
5211
|
+
rotation: rotation + (e + 1) * 15,
|
|
5212
|
+
proportionType: "GOLDEN_RATIO",
|
|
5213
|
+
renderStyle: finalRenderStyle,
|
|
5214
|
+
rng: rng
|
|
5215
|
+
});
|
|
5216
|
+
shapePositions.push({
|
|
5217
|
+
x: echoX,
|
|
5218
|
+
y: echoY,
|
|
5219
|
+
size: echoSize,
|
|
5220
|
+
shape: shape
|
|
5221
|
+
});
|
|
5222
|
+
spatialGrid.insert({
|
|
5223
|
+
x: echoX,
|
|
5224
|
+
y: echoY,
|
|
5225
|
+
size: echoSize,
|
|
5226
|
+
shape: shape
|
|
5227
|
+
});
|
|
5228
|
+
}
|
|
5229
|
+
extrasSpent += echoCount * styleCost;
|
|
5003
5230
|
}
|
|
5231
|
+
// RNG for echoCount + echoAngle consumed above regardless
|
|
5004
5232
|
}
|
|
5005
5233
|
// ── 5d. Recursive nesting ──────────────────────────────────
|
|
5006
5234
|
// Focal depth: shapes near focal points get more detail
|
|
@@ -5008,7 +5236,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5008
5236
|
const nestingChance = 0.15 + focalProximity * 0.15; // 15-30% near focal
|
|
5009
5237
|
if (size > adjustedMaxSize * 0.4 && rng() < nestingChance) {
|
|
5010
5238
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
5011
|
-
for(let n = 0; n < innerCount; n++){
|
|
5239
|
+
if (extrasAllowed) for(let n = 0; n < innerCount; n++){
|
|
5012
5240
|
// Pick inner shape from palette affinities
|
|
5013
5241
|
const innerSizeFraction = size * 0.25 / adjustedMaxSize;
|
|
5014
5242
|
const innerShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, innerSizeFraction);
|
|
@@ -5017,6 +5245,10 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5017
5245
|
const innerOffY = (rng() - 0.5) * size * 0.4;
|
|
5018
5246
|
const innerRot = rng() * 360;
|
|
5019
5247
|
const innerFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 10, 0.1), 0.3 + rng() * 0.4);
|
|
5248
|
+
let innerStyle = (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(innerShape, layerRenderStyle, rng);
|
|
5249
|
+
// Apply clip-heavy cap to nested shapes too
|
|
5250
|
+
if ((innerStyle === "stipple" || innerStyle === "noise-grain") && clipHeavyCount >= MAX_CLIP_HEAVY_SHAPES) innerStyle = $4f72c5a314eddf25$var$downgradeRenderStyle(innerStyle);
|
|
5251
|
+
if (innerStyle === "stipple" || innerStyle === "noise-grain") clipHeavyCount++;
|
|
5020
5252
|
ctx.globalAlpha = layerOpacity * 0.7;
|
|
5021
5253
|
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, innerShape, finalX + innerOffX, finalY + innerOffY, {
|
|
5022
5254
|
fillColor: innerFill,
|
|
@@ -5025,9 +5257,21 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5025
5257
|
size: innerSize,
|
|
5026
5258
|
rotation: innerRot,
|
|
5027
5259
|
proportionType: "GOLDEN_RATIO",
|
|
5028
|
-
renderStyle:
|
|
5260
|
+
renderStyle: innerStyle,
|
|
5029
5261
|
rng: rng
|
|
5030
5262
|
});
|
|
5263
|
+
extrasSpent += $4f72c5a314eddf25$var$RENDER_STYLE_COST[innerStyle] ?? 1;
|
|
5264
|
+
}
|
|
5265
|
+
else // Drain RNG to keep determinism — each nested shape consumes ~8 rng calls
|
|
5266
|
+
for(let n = 0; n < innerCount; n++){
|
|
5267
|
+
rng();
|
|
5268
|
+
rng();
|
|
5269
|
+
rng();
|
|
5270
|
+
rng();
|
|
5271
|
+
rng();
|
|
5272
|
+
rng();
|
|
5273
|
+
rng();
|
|
5274
|
+
rng();
|
|
5031
5275
|
}
|
|
5032
5276
|
}
|
|
5033
5277
|
// ── 5e. Shape constellations — pre-composed groups ─────────
|
|
@@ -5036,40 +5280,55 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5036
5280
|
const constellation = $4f72c5a314eddf25$var$CONSTELLATIONS[Math.floor(rng() * $4f72c5a314eddf25$var$CONSTELLATIONS.length)];
|
|
5037
5281
|
const members = constellation.build(rng, size);
|
|
5038
5282
|
const groupRotation = rng() * Math.PI * 2;
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5283
|
+
if (extrasAllowed) {
|
|
5284
|
+
const cosR = Math.cos(groupRotation);
|
|
5285
|
+
const sinR = Math.sin(groupRotation);
|
|
5286
|
+
for (const member of members){
|
|
5287
|
+
// Rotate the group offset by the group rotation
|
|
5288
|
+
const mx = finalX + member.dx * cosR - member.dy * sinR;
|
|
5289
|
+
const my = finalY + member.dx * sinR + member.dy * cosR;
|
|
5290
|
+
if (mx < 0 || mx > width || my < 0 || my > height) continue;
|
|
5291
|
+
const memberFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), rng, 8, 0.06), fillAlpha * 0.8);
|
|
5292
|
+
const memberStroke = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$18a34c25ea7e724b)(strokeBase, rng, 5, 0.04), bgLum);
|
|
5293
|
+
ctx.globalAlpha = layerOpacity * 0.6;
|
|
5294
|
+
// Use the member's shape if available, otherwise fall back to palette
|
|
5295
|
+
const memberShape = shapeNames.includes(member.shape) ? member.shape : (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, member.size / adjustedMaxSize);
|
|
5296
|
+
let memberStyle = (0, $e73976f898150d4d$export$ab873bb6fb56c1a8)(memberShape, layerRenderStyle, rng);
|
|
5297
|
+
// Apply clip-heavy cap to constellation members too
|
|
5298
|
+
if ((memberStyle === "stipple" || memberStyle === "noise-grain") && clipHeavyCount >= MAX_CLIP_HEAVY_SHAPES) memberStyle = $4f72c5a314eddf25$var$downgradeRenderStyle(memberStyle);
|
|
5299
|
+
if (memberStyle === "stipple" || memberStyle === "noise-grain") clipHeavyCount++;
|
|
5300
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, memberShape, mx, my, {
|
|
5301
|
+
fillColor: memberFill,
|
|
5302
|
+
strokeColor: memberStroke,
|
|
5303
|
+
strokeWidth: strokeWidth * 0.7,
|
|
5304
|
+
size: member.size,
|
|
5305
|
+
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
5306
|
+
proportionType: "GOLDEN_RATIO",
|
|
5307
|
+
renderStyle: memberStyle,
|
|
5308
|
+
rng: rng
|
|
5309
|
+
});
|
|
5310
|
+
shapePositions.push({
|
|
5311
|
+
x: mx,
|
|
5312
|
+
y: my,
|
|
5313
|
+
size: member.size,
|
|
5314
|
+
shape: memberShape
|
|
5315
|
+
});
|
|
5316
|
+
spatialGrid.insert({
|
|
5317
|
+
x: mx,
|
|
5318
|
+
y: my,
|
|
5319
|
+
size: member.size,
|
|
5320
|
+
shape: memberShape
|
|
5321
|
+
});
|
|
5322
|
+
extrasSpent += $4f72c5a314eddf25$var$RENDER_STYLE_COST[memberStyle] ?? 1;
|
|
5323
|
+
}
|
|
5324
|
+
} else // Drain RNG — each member consumes ~6 rng calls for colors/style
|
|
5325
|
+
for(let m = 0; m < members.length; m++){
|
|
5326
|
+
rng();
|
|
5327
|
+
rng();
|
|
5328
|
+
rng();
|
|
5329
|
+
rng();
|
|
5330
|
+
rng();
|
|
5331
|
+
rng();
|
|
5073
5332
|
}
|
|
5074
5333
|
}
|
|
5075
5334
|
// ── 5f. Rhythm placement — deliberate geometric progressions ──
|
|
@@ -5080,45 +5339,58 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5080
5339
|
const rhythmSpacing = size * (0.8 + rng() * 0.6);
|
|
5081
5340
|
const rhythmDecay = 0.7 + rng() * 0.15; // size multiplier per step
|
|
5082
5341
|
const rhythmShape = shape; // same shape for visual rhythm
|
|
5083
|
-
|
|
5342
|
+
if (extrasAllowed) {
|
|
5343
|
+
let rhythmSize = size * 0.6;
|
|
5344
|
+
for(let r = 0; r < rhythmCount; r++){
|
|
5345
|
+
const rx = finalX + Math.cos(rhythmAngle) * rhythmSpacing * (r + 1);
|
|
5346
|
+
const ry = finalY + Math.sin(rhythmAngle) * rhythmSpacing * (r + 1);
|
|
5347
|
+
if (rx < 0 || rx > width || ry < 0 || ry > height) break;
|
|
5348
|
+
if ($4f72c5a314eddf25$var$isInVoidZone(rx, ry, voidZones)) break;
|
|
5349
|
+
rhythmSize *= rhythmDecay;
|
|
5350
|
+
if (rhythmSize < adjustedMinSize) break;
|
|
5351
|
+
const rhythmAlpha = layerOpacity * (0.6 - r * 0.08);
|
|
5352
|
+
ctx.globalAlpha = Math.max(0.1, rhythmAlpha);
|
|
5353
|
+
const rhythmFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(layerHierarchy, rng), rng, 5, 0.04), fillAlpha * 0.7);
|
|
5354
|
+
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, rhythmShape, rx, ry, {
|
|
5355
|
+
fillColor: rhythmFill,
|
|
5356
|
+
strokeColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(strokeColor, 0.5),
|
|
5357
|
+
strokeWidth: strokeWidth * 0.7,
|
|
5358
|
+
size: rhythmSize,
|
|
5359
|
+
rotation: rotation + (r + 1) * 12,
|
|
5360
|
+
proportionType: "GOLDEN_RATIO",
|
|
5361
|
+
renderStyle: finalRenderStyle,
|
|
5362
|
+
rng: rng
|
|
5363
|
+
});
|
|
5364
|
+
shapePositions.push({
|
|
5365
|
+
x: rx,
|
|
5366
|
+
y: ry,
|
|
5367
|
+
size: rhythmSize,
|
|
5368
|
+
shape: rhythmShape
|
|
5369
|
+
});
|
|
5370
|
+
spatialGrid.insert({
|
|
5371
|
+
x: rx,
|
|
5372
|
+
y: ry,
|
|
5373
|
+
size: rhythmSize,
|
|
5374
|
+
shape: rhythmShape
|
|
5375
|
+
});
|
|
5376
|
+
}
|
|
5377
|
+
extrasSpent += rhythmCount * styleCost;
|
|
5378
|
+
} else // Drain RNG — each rhythm step consumes ~3 rng calls for colors
|
|
5084
5379
|
for(let r = 0; r < rhythmCount; r++){
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
if ($4f72c5a314eddf25$var$isInVoidZone(rx, ry, voidZones)) break;
|
|
5089
|
-
rhythmSize *= rhythmDecay;
|
|
5090
|
-
if (rhythmSize < adjustedMinSize) break;
|
|
5091
|
-
const rhythmAlpha = layerOpacity * (0.6 - r * 0.08);
|
|
5092
|
-
ctx.globalAlpha = Math.max(0.1, rhythmAlpha);
|
|
5093
|
-
const rhythmFill = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$18a34c25ea7e724b)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(layerHierarchy, rng), rng, 5, 0.04), fillAlpha * 0.7);
|
|
5094
|
-
(0, $c3de8257a8baa3b0$export$bb35a6995ddbf32d)(ctx, rhythmShape, rx, ry, {
|
|
5095
|
-
fillColor: rhythmFill,
|
|
5096
|
-
strokeColor: (0, $d016ad53434219a1$export$f2121afcad3d553f)(strokeColor, 0.5),
|
|
5097
|
-
strokeWidth: strokeWidth * 0.7,
|
|
5098
|
-
size: rhythmSize,
|
|
5099
|
-
rotation: rotation + (r + 1) * 12,
|
|
5100
|
-
proportionType: "GOLDEN_RATIO",
|
|
5101
|
-
renderStyle: finalRenderStyle,
|
|
5102
|
-
rng: rng
|
|
5103
|
-
});
|
|
5104
|
-
shapePositions.push({
|
|
5105
|
-
x: rx,
|
|
5106
|
-
y: ry,
|
|
5107
|
-
size: rhythmSize,
|
|
5108
|
-
shape: rhythmShape
|
|
5109
|
-
});
|
|
5110
|
-
spatialGrid.insert({
|
|
5111
|
-
x: rx,
|
|
5112
|
-
y: ry,
|
|
5113
|
-
size: rhythmSize,
|
|
5114
|
-
shape: rhythmShape
|
|
5115
|
-
});
|
|
5380
|
+
rng();
|
|
5381
|
+
rng();
|
|
5382
|
+
rng();
|
|
5116
5383
|
}
|
|
5117
5384
|
}
|
|
5118
5385
|
}
|
|
5119
5386
|
}
|
|
5120
5387
|
// Reset blend mode for post-processing passes
|
|
5121
5388
|
ctx.globalCompositeOperation = "source-over";
|
|
5389
|
+
if (_dt) {
|
|
5390
|
+
_dt.shapeCount = shapePositions.length;
|
|
5391
|
+
_dt.extraCount = extrasSpent;
|
|
5392
|
+
}
|
|
5393
|
+
_mark("5_shape_layers");
|
|
5122
5394
|
// ── 5g. Layered masking / cutout portals ───────────────────────
|
|
5123
5395
|
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
5124
5396
|
// with a tinted background wash, creating a "peek through" effect.
|
|
@@ -5177,15 +5449,28 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5177
5449
|
ctx.restore();
|
|
5178
5450
|
}
|
|
5179
5451
|
}
|
|
5452
|
+
_mark("5g_portals");
|
|
5180
5453
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5454
|
+
// Optimized: collect all segments into width-quantized buckets, then
|
|
5455
|
+
// render each bucket as a single batched path. This reduces
|
|
5456
|
+
// beginPath/stroke calls from O(segments) to O(buckets).
|
|
5181
5457
|
const baseFlowLines = 6 + Math.floor(rng() * 10);
|
|
5182
5458
|
const numFlowLines = Math.round(baseFlowLines * archetype.flowLineMultiplier);
|
|
5459
|
+
// Width buckets — 6 buckets cover the taper×pressure range
|
|
5460
|
+
const FLOW_WIDTH_BUCKETS = 6;
|
|
5461
|
+
const flowBuckets = [];
|
|
5462
|
+
for(let b = 0; b < FLOW_WIDTH_BUCKETS; b++)flowBuckets.push([]);
|
|
5463
|
+
// Track the representative width for each bucket
|
|
5464
|
+
const flowBucketWidths = new Array(FLOW_WIDTH_BUCKETS);
|
|
5465
|
+
// Pre-compute max possible width for bucket assignment
|
|
5466
|
+
let globalMaxFlowWidth = 0;
|
|
5183
5467
|
for(let i = 0; i < numFlowLines; i++){
|
|
5184
5468
|
let fx = rng() * width;
|
|
5185
5469
|
let fy = rng() * height;
|
|
5186
5470
|
const steps = 30 + Math.floor(rng() * 40);
|
|
5187
5471
|
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
5188
5472
|
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
5473
|
+
if (startWidth > globalMaxFlowWidth) globalMaxFlowWidth = startWidth;
|
|
5189
5474
|
// Variable color: interpolate between two hierarchy colors along the stroke
|
|
5190
5475
|
const lineColorStart = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
5191
5476
|
const lineColorEnd = (0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum);
|
|
@@ -5207,19 +5492,22 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5207
5492
|
continue;
|
|
5208
5493
|
}
|
|
5209
5494
|
const t = s / steps;
|
|
5210
|
-
// Taper + pressure
|
|
5211
5495
|
const taper = 1 - t * 0.8;
|
|
5212
5496
|
const pressure = 0.6 + 0.4 * Math.sin(t * pressureFreq * Math.PI + pressurePhase);
|
|
5213
|
-
|
|
5214
|
-
|
|
5497
|
+
const segWidth = startWidth * taper * pressure;
|
|
5498
|
+
const segAlpha = lineAlpha * taper;
|
|
5215
5499
|
const lineColor = t < 0.5 ? (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorStart, 0.4 + t * 0.2) : (0, $d016ad53434219a1$export$f2121afcad3d553f)(lineColorEnd, 0.4 + (1 - t) * 0.2);
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5500
|
+
// Quantize width into bucket
|
|
5501
|
+
const bucketIdx = Math.min(FLOW_WIDTH_BUCKETS - 1, Math.floor(segWidth / (globalMaxFlowWidth || 1) * FLOW_WIDTH_BUCKETS));
|
|
5502
|
+
flowBuckets[bucketIdx].push({
|
|
5503
|
+
x1: prevX,
|
|
5504
|
+
y1: prevY,
|
|
5505
|
+
x2: fx,
|
|
5506
|
+
y2: fy,
|
|
5507
|
+
color: lineColor,
|
|
5508
|
+
alpha: segAlpha
|
|
5509
|
+
});
|
|
5510
|
+
flowBucketWidths[bucketIdx] = segWidth;
|
|
5223
5511
|
// Branching: ~12% chance per step to spawn a thinner child stroke
|
|
5224
5512
|
if (rng() < 0.12 && s > 5 && s < steps - 10) {
|
|
5225
5513
|
const branchAngle = angle + (rng() < 0.5 ? 1 : -1) * (0.3 + rng() * 0.5);
|
|
@@ -5235,12 +5523,18 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5235
5523
|
by += Math.sin(bAngle) * stepLen * 0.8;
|
|
5236
5524
|
if (bx < 0 || bx > width || by < 0 || by > height) break;
|
|
5237
5525
|
const bTaper = 1 - bs / branchSteps * 0.9;
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5526
|
+
const bSegWidth = branchWidth * bTaper;
|
|
5527
|
+
const bAlpha = lineAlpha * taper * bTaper * 0.6;
|
|
5528
|
+
const bBucket = Math.min(FLOW_WIDTH_BUCKETS - 1, Math.floor(bSegWidth / (globalMaxFlowWidth || 1) * FLOW_WIDTH_BUCKETS));
|
|
5529
|
+
flowBuckets[bBucket].push({
|
|
5530
|
+
x1: bPrevX,
|
|
5531
|
+
y1: bPrevY,
|
|
5532
|
+
x2: bx,
|
|
5533
|
+
y2: by,
|
|
5534
|
+
color: lineColor,
|
|
5535
|
+
alpha: bAlpha
|
|
5536
|
+
});
|
|
5537
|
+
flowBucketWidths[bBucket] = bSegWidth;
|
|
5244
5538
|
bPrevX = bx;
|
|
5245
5539
|
bPrevY = by;
|
|
5246
5540
|
}
|
|
@@ -5249,7 +5543,41 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5249
5543
|
prevY = fy;
|
|
5250
5544
|
}
|
|
5251
5545
|
}
|
|
5546
|
+
// Render flow line buckets — one batched path per width bucket
|
|
5547
|
+
// Within each bucket, further sub-batch by quantized alpha (4 levels)
|
|
5548
|
+
ctx.lineCap = "round";
|
|
5549
|
+
const FLOW_ALPHA_BUCKETS = 4;
|
|
5550
|
+
for(let wb = 0; wb < FLOW_WIDTH_BUCKETS; wb++){
|
|
5551
|
+
const segs = flowBuckets[wb];
|
|
5552
|
+
if (segs.length === 0) continue;
|
|
5553
|
+
ctx.lineWidth = flowBucketWidths[wb];
|
|
5554
|
+
// Sub-bucket by alpha
|
|
5555
|
+
const alphaSubs = [];
|
|
5556
|
+
for(let a = 0; a < FLOW_ALPHA_BUCKETS; a++)alphaSubs.push([]);
|
|
5557
|
+
let maxAlpha = 0;
|
|
5558
|
+
for(let j = 0; j < segs.length; j++)if (segs[j].alpha > maxAlpha) maxAlpha = segs[j].alpha;
|
|
5559
|
+
for(let j = 0; j < segs.length; j++){
|
|
5560
|
+
const ai = Math.min(FLOW_ALPHA_BUCKETS - 1, Math.floor(segs[j].alpha / (maxAlpha || 1) * FLOW_ALPHA_BUCKETS));
|
|
5561
|
+
alphaSubs[ai].push(segs[j]);
|
|
5562
|
+
}
|
|
5563
|
+
for(let ai = 0; ai < FLOW_ALPHA_BUCKETS; ai++){
|
|
5564
|
+
const sub = alphaSubs[ai];
|
|
5565
|
+
if (sub.length === 0) continue;
|
|
5566
|
+
// Use the median segment's alpha and color as representative
|
|
5567
|
+
const rep = sub[Math.floor(sub.length / 2)];
|
|
5568
|
+
ctx.globalAlpha = rep.alpha;
|
|
5569
|
+
ctx.strokeStyle = rep.color;
|
|
5570
|
+
ctx.beginPath();
|
|
5571
|
+
for(let j = 0; j < sub.length; j++){
|
|
5572
|
+
ctx.moveTo(sub[j].x1, sub[j].y1);
|
|
5573
|
+
ctx.lineTo(sub[j].x2, sub[j].y2);
|
|
5574
|
+
}
|
|
5575
|
+
ctx.stroke();
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
_mark("6_flow_lines");
|
|
5252
5579
|
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
5580
|
+
// Optimized: collect all burst segments, then batch by quantized alpha
|
|
5253
5581
|
const energyArchetypes = [
|
|
5254
5582
|
"dense-chaotic",
|
|
5255
5583
|
"cosmic",
|
|
@@ -5260,8 +5588,12 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5260
5588
|
if (hasEnergyLines && shapePositions.length > 0) {
|
|
5261
5589
|
const energyCount = 5 + Math.floor(rng() * 10);
|
|
5262
5590
|
ctx.lineCap = "round";
|
|
5591
|
+
// Collect all energy segments with their computed state
|
|
5592
|
+
const ENERGY_ALPHA_BUCKETS = 3;
|
|
5593
|
+
const energyBuckets = [];
|
|
5594
|
+
for(let b = 0; b < ENERGY_ALPHA_BUCKETS; b++)energyBuckets.push([]);
|
|
5595
|
+
const energyAlphas = new Array(ENERGY_ALPHA_BUCKETS).fill(0);
|
|
5263
5596
|
for(let e = 0; e < energyCount; e++){
|
|
5264
|
-
// Pick a random shape to radiate from
|
|
5265
5597
|
const source = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5266
5598
|
const burstCount = 2 + Math.floor(rng() * 4);
|
|
5267
5599
|
const baseAngle = flowAngle(source.x, source.y);
|
|
@@ -5273,16 +5605,40 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5273
5605
|
const sy = source.y + Math.sin(angle) * startDist;
|
|
5274
5606
|
const ex = sx + Math.cos(angle) * lineLen;
|
|
5275
5607
|
const ey = sy + Math.sin(angle) * lineLen;
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5608
|
+
const eAlpha = 0.04 + rng() * 0.06;
|
|
5609
|
+
const eColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
5610
|
+
const eLw = (0.5 + rng() * 1.5) * scaleFactor;
|
|
5611
|
+
// Quantize alpha into bucket
|
|
5612
|
+
const bi = Math.min(ENERGY_ALPHA_BUCKETS - 1, Math.floor((eAlpha - 0.04) / 0.06 * ENERGY_ALPHA_BUCKETS));
|
|
5613
|
+
energyBuckets[bi].push({
|
|
5614
|
+
x1: sx,
|
|
5615
|
+
y1: sy,
|
|
5616
|
+
x2: ex,
|
|
5617
|
+
y2: ey,
|
|
5618
|
+
color: eColor,
|
|
5619
|
+
lw: eLw
|
|
5620
|
+
});
|
|
5621
|
+
energyAlphas[bi] = eAlpha;
|
|
5283
5622
|
}
|
|
5284
5623
|
}
|
|
5624
|
+
// Render batched energy lines
|
|
5625
|
+
for(let bi = 0; bi < ENERGY_ALPHA_BUCKETS; bi++){
|
|
5626
|
+
const segs = energyBuckets[bi];
|
|
5627
|
+
if (segs.length === 0) continue;
|
|
5628
|
+
ctx.globalAlpha = energyAlphas[bi];
|
|
5629
|
+
// Use median segment's color and width as representative
|
|
5630
|
+
const rep = segs[Math.floor(segs.length / 2)];
|
|
5631
|
+
ctx.strokeStyle = rep.color;
|
|
5632
|
+
ctx.lineWidth = rep.lw;
|
|
5633
|
+
ctx.beginPath();
|
|
5634
|
+
for(let j = 0; j < segs.length; j++){
|
|
5635
|
+
ctx.moveTo(segs[j].x1, segs[j].y1);
|
|
5636
|
+
ctx.lineTo(segs[j].x2, segs[j].y2);
|
|
5637
|
+
}
|
|
5638
|
+
ctx.stroke();
|
|
5639
|
+
}
|
|
5285
5640
|
}
|
|
5641
|
+
_mark("6b_energy_lines");
|
|
5286
5642
|
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
5287
5643
|
if (symmetryMode !== "none") {
|
|
5288
5644
|
const canvas = ctx.canvas;
|
|
@@ -5303,43 +5659,25 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5303
5659
|
}
|
|
5304
5660
|
ctx.restore();
|
|
5305
5661
|
}
|
|
5306
|
-
|
|
5662
|
+
_mark("6c_symmetry");
|
|
5663
|
+
// ── 7. Noise texture overlay ─────────────────────────────────────
|
|
5664
|
+
// With density capped at 2500 dots, direct fillRect calls are far cheaper
|
|
5665
|
+
// than the getImageData/putImageData round-trip which copies the entire
|
|
5666
|
+
// pixel buffer (4 × width × height bytes) twice.
|
|
5307
5667
|
const noiseRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 777));
|
|
5308
|
-
const
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
const
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
for(let dy = 0; dy < pixelScale && ny + dy < height; dy++)for(let dx = 0; dx < pixelScale && nx + dx < width; dx++){
|
|
5320
|
-
const idx = ((ny + dy) * width + (nx + dx)) * 4;
|
|
5321
|
-
// Alpha-blend the noise dot onto existing pixel data
|
|
5322
|
-
const srcA = alpha / 255;
|
|
5323
|
-
const invA = 1 - srcA;
|
|
5324
|
-
data[idx] = Math.round(data[idx] * invA + brightness * srcA);
|
|
5325
|
-
data[idx + 1] = Math.round(data[idx + 1] * invA + brightness * srcA);
|
|
5326
|
-
data[idx + 2] = Math.round(data[idx + 2] * invA + brightness * srcA);
|
|
5327
|
-
// Keep existing alpha
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
ctx.putImageData(imageData, 0, 0);
|
|
5331
|
-
} catch {
|
|
5332
|
-
// Fallback for environments where getImageData isn't available (e.g. some OffscreenCanvas)
|
|
5333
|
-
for(let i = 0; i < noiseDensity; i++){
|
|
5334
|
-
const nx = noiseRng() * width;
|
|
5335
|
-
const ny = noiseRng() * height;
|
|
5336
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5337
|
-
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5338
|
-
ctx.globalAlpha = alpha;
|
|
5339
|
-
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
5340
|
-
ctx.fillRect(nx, ny, 1 * scaleFactor, 1 * scaleFactor);
|
|
5341
|
-
}
|
|
5668
|
+
const rawNoiseDensity = Math.floor(width * height / 800);
|
|
5669
|
+
const noiseDensity = Math.min(rawNoiseDensity, 2500);
|
|
5670
|
+
const pixelScale = Math.max(1, Math.round(scaleFactor));
|
|
5671
|
+
for(let i = 0; i < noiseDensity; i++){
|
|
5672
|
+
const nx = noiseRng() * width;
|
|
5673
|
+
const ny = noiseRng() * height;
|
|
5674
|
+
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5675
|
+
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5676
|
+
ctx.globalAlpha = alpha;
|
|
5677
|
+
ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
|
|
5678
|
+
ctx.fillRect(nx, ny, pixelScale, pixelScale);
|
|
5342
5679
|
}
|
|
5680
|
+
_mark("7_noise_texture");
|
|
5343
5681
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
5344
5682
|
ctx.globalAlpha = 1;
|
|
5345
5683
|
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
@@ -5353,11 +5691,20 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5353
5691
|
vigGrad.addColorStop(1, vignetteColor);
|
|
5354
5692
|
ctx.fillStyle = vigGrad;
|
|
5355
5693
|
ctx.fillRect(0, 0, width, height);
|
|
5694
|
+
_mark("8_vignette");
|
|
5356
5695
|
// ── 9. Organic connecting curves — proximity-aware ───────────────
|
|
5696
|
+
// Optimized: batch all curves into alpha-quantized groups to reduce
|
|
5697
|
+
// beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
|
|
5357
5698
|
if (shapePositions.length > 1) {
|
|
5358
5699
|
const numCurves = Math.floor(8 * (width * height) / 1048576);
|
|
5359
5700
|
const maxCurveDist = Math.hypot(width, height) * 0.2; // only connect nearby shapes
|
|
5360
5701
|
ctx.lineWidth = 0.8 * scaleFactor;
|
|
5702
|
+
// Collect curves into 3 alpha buckets
|
|
5703
|
+
const CURVE_ALPHA_BUCKETS = 3;
|
|
5704
|
+
const curveBuckets = [];
|
|
5705
|
+
const curveColors = [];
|
|
5706
|
+
const curveAlphas = new Array(CURVE_ALPHA_BUCKETS).fill(0);
|
|
5707
|
+
for(let b = 0; b < CURVE_ALPHA_BUCKETS; b++)curveBuckets.push([]);
|
|
5361
5708
|
for(let i = 0; i < numCurves; i++){
|
|
5362
5709
|
const idxA = Math.floor(rng() * shapePositions.length);
|
|
5363
5710
|
const offset = 1 + Math.floor(rng() * Math.min(5, shapePositions.length - 1));
|
|
@@ -5374,14 +5721,36 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5374
5721
|
const bulge = (rng() - 0.5) * dist * 0.4;
|
|
5375
5722
|
const cpx = mx + -dy / (dist || 1) * bulge;
|
|
5376
5723
|
const cpy = my + dx / (dist || 1) * bulge;
|
|
5377
|
-
|
|
5378
|
-
|
|
5724
|
+
const curveAlpha = 0.06 + rng() * 0.1;
|
|
5725
|
+
const curveColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$90ad0e6170cf6af5)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), bgLum), 0.3);
|
|
5726
|
+
const bi = Math.min(CURVE_ALPHA_BUCKETS - 1, Math.floor((curveAlpha - 0.06) / 0.1 * CURVE_ALPHA_BUCKETS));
|
|
5727
|
+
curveBuckets[bi].push({
|
|
5728
|
+
ax: a.x,
|
|
5729
|
+
ay: a.y,
|
|
5730
|
+
cpx: cpx,
|
|
5731
|
+
cpy: cpy,
|
|
5732
|
+
bx: b.x,
|
|
5733
|
+
by: b.y
|
|
5734
|
+
});
|
|
5735
|
+
curveAlphas[bi] = curveAlpha;
|
|
5736
|
+
if (!curveColors[bi]) curveColors[bi] = curveColor;
|
|
5737
|
+
}
|
|
5738
|
+
// Render batched curves
|
|
5739
|
+
for(let bi = 0; bi < CURVE_ALPHA_BUCKETS; bi++){
|
|
5740
|
+
const curves = curveBuckets[bi];
|
|
5741
|
+
if (curves.length === 0) continue;
|
|
5742
|
+
ctx.globalAlpha = curveAlphas[bi];
|
|
5743
|
+
ctx.strokeStyle = curveColors[bi];
|
|
5379
5744
|
ctx.beginPath();
|
|
5380
|
-
|
|
5381
|
-
|
|
5745
|
+
for(let j = 0; j < curves.length; j++){
|
|
5746
|
+
const c = curves[j];
|
|
5747
|
+
ctx.moveTo(c.ax, c.ay);
|
|
5748
|
+
ctx.quadraticCurveTo(c.cpx, c.cpy, c.bx, c.by);
|
|
5749
|
+
}
|
|
5382
5750
|
ctx.stroke();
|
|
5383
5751
|
}
|
|
5384
5752
|
}
|
|
5753
|
+
_mark("9_connecting_curves");
|
|
5385
5754
|
// ── 10. Post-processing ────────────────────────────────────────
|
|
5386
5755
|
// 10a. Color grading — unified tone across the whole image
|
|
5387
5756
|
// Apply as a semi-transparent overlay in the grade hue
|
|
@@ -5441,6 +5810,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5441
5810
|
ctx.fillRect(0, 0, width, height);
|
|
5442
5811
|
ctx.globalCompositeOperation = "source-over";
|
|
5443
5812
|
}
|
|
5813
|
+
_mark("10_post_processing");
|
|
5444
5814
|
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5445
5815
|
{
|
|
5446
5816
|
ctx.save();
|
|
@@ -5496,11 +5866,14 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5496
5866
|
}
|
|
5497
5867
|
} else if (archName.includes("botanical") || archName.includes("organic") || archName.includes("watercolor")) {
|
|
5498
5868
|
// Vine tendrils — organic curving lines along edges
|
|
5869
|
+
// Optimized: batch all tendrils into a single path
|
|
5499
5870
|
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.15);
|
|
5500
5871
|
ctx.lineWidth = Math.max(0.8, 1.2 * scaleFactor);
|
|
5501
5872
|
ctx.globalAlpha = 0.12 + borderRng() * 0.08;
|
|
5502
5873
|
ctx.lineCap = "round";
|
|
5503
5874
|
const tendrilCount = 8 + Math.floor(borderRng() * 8);
|
|
5875
|
+
ctx.beginPath();
|
|
5876
|
+
const leafPositions = [];
|
|
5504
5877
|
for(let t = 0; t < tendrilCount; t++){
|
|
5505
5878
|
// Start from a random edge point
|
|
5506
5879
|
const edge = Math.floor(borderRng() * 4);
|
|
@@ -5518,7 +5891,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5518
5891
|
tx = width - borderPad;
|
|
5519
5892
|
ty = borderRng() * height;
|
|
5520
5893
|
}
|
|
5521
|
-
ctx.beginPath();
|
|
5522
5894
|
ctx.moveTo(tx, ty);
|
|
5523
5895
|
const segs = 3 + Math.floor(borderRng() * 4);
|
|
5524
5896
|
for(let s = 0; s < segs; s++){
|
|
@@ -5532,14 +5904,23 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5532
5904
|
ty = cpy3;
|
|
5533
5905
|
ctx.quadraticCurveTo(cpx2, cpy2, tx, ty);
|
|
5534
5906
|
}
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5907
|
+
// Collect leaf positions for batch fill
|
|
5908
|
+
if (borderRng() < 0.6) leafPositions.push({
|
|
5909
|
+
x: tx,
|
|
5910
|
+
y: ty,
|
|
5911
|
+
r: borderPad * (0.15 + borderRng() * 0.2)
|
|
5912
|
+
});
|
|
5913
|
+
}
|
|
5914
|
+
ctx.stroke();
|
|
5915
|
+
// Batch all leaf dots into a single fill
|
|
5916
|
+
if (leafPositions.length > 0) {
|
|
5917
|
+
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.secondary, 0.08);
|
|
5918
|
+
ctx.beginPath();
|
|
5919
|
+
for (const leaf of leafPositions){
|
|
5920
|
+
ctx.moveTo(leaf.x + leaf.r, leaf.y);
|
|
5921
|
+
ctx.arc(leaf.x, leaf.y, leaf.r, 0, Math.PI * 2);
|
|
5542
5922
|
}
|
|
5923
|
+
ctx.fill();
|
|
5543
5924
|
}
|
|
5544
5925
|
} else if (archName.includes("celestial") || archName.includes("cosmic") || archName.includes("neon")) {
|
|
5545
5926
|
// Star-studded arcs along edges
|
|
@@ -5554,8 +5935,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5554
5935
|
ctx.beginPath();
|
|
5555
5936
|
ctx.arc(cx, height * 1.3, height * 0.6, Math.PI + 0.3, -0.3);
|
|
5556
5937
|
ctx.stroke();
|
|
5557
|
-
// Scatter small stars along the border region
|
|
5938
|
+
// Scatter small stars along the border region — batched into single path
|
|
5558
5939
|
const starCount = 15 + Math.floor(borderRng() * 15);
|
|
5940
|
+
ctx.beginPath();
|
|
5559
5941
|
for(let s = 0; s < starCount; s++){
|
|
5560
5942
|
const edge = Math.floor(borderRng() * 4);
|
|
5561
5943
|
let sx, sy;
|
|
@@ -5574,7 +5956,6 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5574
5956
|
}
|
|
5575
5957
|
const starR = (1 + borderRng() * 2.5) * scaleFactor;
|
|
5576
5958
|
// 4-point star
|
|
5577
|
-
ctx.beginPath();
|
|
5578
5959
|
for(let p = 0; p < 8; p++){
|
|
5579
5960
|
const a = p / 8 * Math.PI * 2;
|
|
5580
5961
|
const r = p % 2 === 0 ? starR : starR * 0.4;
|
|
@@ -5584,8 +5965,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5584
5965
|
else ctx.lineTo(px2, py2);
|
|
5585
5966
|
}
|
|
5586
5967
|
ctx.closePath();
|
|
5587
|
-
ctx.fill();
|
|
5588
5968
|
}
|
|
5969
|
+
ctx.fill();
|
|
5589
5970
|
} else if (archName.includes("minimal") || archName.includes("monochrome") || archName.includes("stipple")) {
|
|
5590
5971
|
// Thin single rule — understated elegance
|
|
5591
5972
|
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
@@ -5596,6 +5977,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5596
5977
|
// Other archetypes: no border (intentional — not every image needs one)
|
|
5597
5978
|
ctx.restore();
|
|
5598
5979
|
}
|
|
5980
|
+
_mark("10e_borders");
|
|
5599
5981
|
// ── 11. Signature mark — placed in the least-dense corner ──────
|
|
5600
5982
|
{
|
|
5601
5983
|
const sigRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 42));
|
|
@@ -5663,6 +6045,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5663
6045
|
ctx.restore();
|
|
5664
6046
|
}
|
|
5665
6047
|
ctx.globalAlpha = 1;
|
|
6048
|
+
_mark("11_signature");
|
|
5666
6049
|
}
|
|
5667
6050
|
|
|
5668
6051
|
|