git-hash-art 0.12.0 → 0.14.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/CHANGELOG.md +16 -0
- package/dist/browser.js +167 -154
- package/dist/browser.js.map +1 -1
- package/dist/main.js +167 -154
- package/dist/main.js.map +1 -1
- package/dist/module.js +167 -154
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/custom-shapes.test.ts +182 -0
- package/src/__tests__/phase-breakdown.test.ts +44 -0
- package/src/browser.ts +1 -1
- package/src/index.ts +1 -1
- package/src/lib/canvas/draw.ts +8 -4
- package/src/lib/canvas/shapes/affinity.ts +33 -0
- package/src/lib/render.ts +133 -155
- package/src/types.ts +52 -0
package/dist/main.js
CHANGED
|
@@ -2095,14 +2095,15 @@ function $c3de8257a8baa3b0$export$909ab0580e273f19(style) {
|
|
|
2095
2095
|
}
|
|
2096
2096
|
}
|
|
2097
2097
|
function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
2098
|
-
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
2098
|
+
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, activeShapes: activeShapes } = config;
|
|
2099
2099
|
ctx.save();
|
|
2100
2100
|
ctx.translate(x, y);
|
|
2101
2101
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2102
2102
|
ctx.fillStyle = fillColor;
|
|
2103
2103
|
ctx.strokeStyle = strokeColor;
|
|
2104
2104
|
ctx.lineWidth = strokeWidth;
|
|
2105
|
-
const
|
|
2105
|
+
const registry = activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5);
|
|
2106
|
+
const drawFunction = registry[shape];
|
|
2106
2107
|
if (drawFunction) {
|
|
2107
2108
|
drawFunction(ctx, size);
|
|
2108
2109
|
ctx.fill();
|
|
@@ -2612,7 +2613,8 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2612
2613
|
} else ctx.fillStyle = fillColor;
|
|
2613
2614
|
ctx.strokeStyle = strokeColor;
|
|
2614
2615
|
ctx.lineWidth = strokeWidth;
|
|
2615
|
-
const
|
|
2616
|
+
const registry = config.activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5);
|
|
2617
|
+
const drawFunction = registry[shape];
|
|
2616
2618
|
if (drawFunction) {
|
|
2617
2619
|
drawFunction(ctx, size, {
|
|
2618
2620
|
rng: rng
|
|
@@ -3476,6 +3478,26 @@ const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
|
3476
3478
|
]
|
|
3477
3479
|
}
|
|
3478
3480
|
};
|
|
3481
|
+
function $e73976f898150d4d$export$90912290d628650f(name, partial) {
|
|
3482
|
+
$e73976f898150d4d$export$4343b39fe47bd82c[name] = {
|
|
3483
|
+
tier: partial?.tier ?? 2,
|
|
3484
|
+
minSizeFraction: partial?.minSizeFraction ?? 0.05,
|
|
3485
|
+
maxSizeFraction: partial?.maxSizeFraction ?? 1.0,
|
|
3486
|
+
affinities: partial?.affinities ?? [
|
|
3487
|
+
"circle",
|
|
3488
|
+
"square"
|
|
3489
|
+
],
|
|
3490
|
+
category: "procedural",
|
|
3491
|
+
heroCandidate: partial?.heroCandidate ?? false,
|
|
3492
|
+
bestStyles: partial?.bestStyles ?? [
|
|
3493
|
+
"fill-and-stroke",
|
|
3494
|
+
"watercolor"
|
|
3495
|
+
]
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
function $e73976f898150d4d$export$f4ca68bd046f15ae(name) {
|
|
3499
|
+
delete $e73976f898150d4d$export$4343b39fe47bd82c[name];
|
|
3500
|
+
}
|
|
3479
3501
|
function $e73976f898150d4d$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
3480
3502
|
const available = shapeNames.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]);
|
|
3481
3503
|
// Pick a seed shape — tier 1 shapes that are hero candidates
|
|
@@ -3665,7 +3687,14 @@ function $e73976f898150d4d$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
|
|
|
3665
3687
|
|
|
3666
3688
|
|
|
3667
3689
|
/**
|
|
3668
|
-
*
|
|
3690
|
+
* Draw function signature for custom shapes.
|
|
3691
|
+
* The function should build a canvas path (moveTo/lineTo/arc/etc.)
|
|
3692
|
+
* centered at the origin. The pipeline handles translate, rotate,
|
|
3693
|
+
* fill, and stroke — your function just defines the geometry.
|
|
3694
|
+
*
|
|
3695
|
+
* @param ctx - Canvas 2D rendering context (already translated to shape center)
|
|
3696
|
+
* @param size - Bounding size in pixels
|
|
3697
|
+
* @param rng - Deterministic RNG seeded from the git hash — use this instead of Math.random()
|
|
3669
3698
|
*/ const $93cf69256c93baa9$export$c2f8e0cc249a8d8f = {
|
|
3670
3699
|
width: 2048,
|
|
3671
3700
|
height: 2048,
|
|
@@ -4607,6 +4636,15 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4607
4636
|
...(0, $93cf69256c93baa9$export$c2f8e0cc249a8d8f),
|
|
4608
4637
|
...config
|
|
4609
4638
|
};
|
|
4639
|
+
const _dt = finalConfig._debugTiming;
|
|
4640
|
+
const _t = _dt ? ()=>performance.now() : undefined;
|
|
4641
|
+
let _p = _t ? _t() : 0;
|
|
4642
|
+
function _mark(name) {
|
|
4643
|
+
if (!_dt || !_t) return;
|
|
4644
|
+
const now = _t();
|
|
4645
|
+
_dt.phases[name] = now - _p;
|
|
4646
|
+
_p = now;
|
|
4647
|
+
}
|
|
4610
4648
|
const rng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash));
|
|
4611
4649
|
// ── 0. Select archetype — fundamentally different visual personality ──
|
|
4612
4650
|
const archetype = (0, $f89bc858f7202849$export$f1142fd7da4d6590)(rng);
|
|
@@ -4627,7 +4665,39 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4627
4665
|
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
4628
4666
|
const colorHierarchy = (0, $d016ad53434219a1$export$fabac4600b87056)(colors, rng);
|
|
4629
4667
|
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
4630
|
-
|
|
4668
|
+
// Merge custom shapes into a combined registry
|
|
4669
|
+
const customShapeNames = [];
|
|
4670
|
+
let activeShapes;
|
|
4671
|
+
if (finalConfig.customShapes && Object.keys(finalConfig.customShapes).length > 0) {
|
|
4672
|
+
activeShapes = {
|
|
4673
|
+
...(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)
|
|
4674
|
+
};
|
|
4675
|
+
for (const [name, def] of Object.entries(finalConfig.customShapes)){
|
|
4676
|
+
// Wrap CustomDrawFunction (ctx, size, rng) into DrawFunction (ctx, size, config?)
|
|
4677
|
+
const customDraw = def.draw;
|
|
4678
|
+
activeShapes[name] = (ctx, size, config)=>{
|
|
4679
|
+
customDraw(ctx, size, config?.rng ?? Math.random);
|
|
4680
|
+
};
|
|
4681
|
+
// Register profile for affinity system (inlined to avoid ESM interop issues)
|
|
4682
|
+
(0, $e73976f898150d4d$export$4343b39fe47bd82c)[name] = {
|
|
4683
|
+
tier: def.profile?.tier ?? 2,
|
|
4684
|
+
minSizeFraction: def.profile?.minSizeFraction ?? 0.05,
|
|
4685
|
+
maxSizeFraction: def.profile?.maxSizeFraction ?? 1.0,
|
|
4686
|
+
affinities: def.profile?.affinities ?? [
|
|
4687
|
+
"circle",
|
|
4688
|
+
"square"
|
|
4689
|
+
],
|
|
4690
|
+
category: "procedural",
|
|
4691
|
+
heroCandidate: def.profile?.heroCandidate ?? false,
|
|
4692
|
+
bestStyles: def.profile?.bestStyles ?? [
|
|
4693
|
+
"fill-and-stroke",
|
|
4694
|
+
"watercolor"
|
|
4695
|
+
]
|
|
4696
|
+
};
|
|
4697
|
+
customShapeNames.push(name);
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
const shapeNames = Object.keys(activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5));
|
|
4631
4701
|
const shapePalette = (0, $e73976f898150d4d$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
4632
4702
|
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
4633
4703
|
const colorGrade = (0, $d016ad53434219a1$export$6d1620b367f86f7a)(rng);
|
|
@@ -4640,12 +4710,14 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4640
4710
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
4641
4711
|
const cx = width / 2;
|
|
4642
4712
|
const cy = height / 2;
|
|
4713
|
+
_mark("0_setup");
|
|
4643
4714
|
// ── 1. Background ──────────────────────────────────────────────
|
|
4644
4715
|
const bgRadius = Math.hypot(cx, cy);
|
|
4645
4716
|
$4f72c5a314eddf25$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
4646
4717
|
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
4718
|
+
// Use source-over instead of soft-light for cheaper compositing
|
|
4647
4719
|
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
4648
|
-
ctx.
|
|
4720
|
+
ctx.globalAlpha = 1;
|
|
4649
4721
|
for(let i = 0; i < meshPoints; i++){
|
|
4650
4722
|
const mx = rng() * width;
|
|
4651
4723
|
const my = rng() * height;
|
|
@@ -4654,95 +4726,103 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4654
4726
|
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
4655
4727
|
grad.addColorStop(0, (0, $d016ad53434219a1$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
4656
4728
|
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
4657
|
-
ctx.globalAlpha = 1;
|
|
4658
4729
|
ctx.fillStyle = grad;
|
|
4659
|
-
|
|
4730
|
+
// Clip to gradient bounding box — avoids blending transparent pixels
|
|
4731
|
+
const gx = Math.max(0, mx - mRadius);
|
|
4732
|
+
const gy = Math.max(0, my - mRadius);
|
|
4733
|
+
const gw = Math.min(width, mx + mRadius) - gx;
|
|
4734
|
+
const gh = Math.min(height, my + mRadius) - gy;
|
|
4735
|
+
ctx.fillRect(gx, gy, gw, gh);
|
|
4660
4736
|
}
|
|
4661
|
-
ctx.globalCompositeOperation = "source-over";
|
|
4662
4737
|
// Compute average background luminance for contrast enforcement
|
|
4663
4738
|
const bgLum = ((0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
4664
4739
|
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
4740
|
+
// Use source-over with pre-multiplied alpha instead of soft-light
|
|
4741
|
+
// for much cheaper compositing (soft-light requires per-pixel blend)
|
|
4665
4742
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
4666
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4667
4743
|
for(let i = 0; i < bgShapeCount; i++){
|
|
4668
4744
|
const bx = rng() * width;
|
|
4669
4745
|
const by = rng() * height;
|
|
4670
4746
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
4671
4747
|
const bColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
4672
|
-
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
4748
|
+
ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
|
|
4673
4749
|
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(bColor, 0.15);
|
|
4674
4750
|
ctx.beginPath();
|
|
4675
4751
|
// Use archetype-appropriate background shapes
|
|
4676
|
-
if (archetype.name === "geometric-precision" || archetype.name === "op-art")
|
|
4677
|
-
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4752
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4678
4753
|
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
4679
4754
|
ctx.fill();
|
|
4680
4755
|
}
|
|
4681
|
-
// Subtle concentric rings from center
|
|
4756
|
+
// Subtle concentric rings from center — batched into single stroke
|
|
4682
4757
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
4683
4758
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
4684
4759
|
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4685
4760
|
ctx.lineWidth = 1 * scaleFactor;
|
|
4761
|
+
ctx.beginPath();
|
|
4686
4762
|
for(let i = 1; i <= ringCount; i++){
|
|
4687
4763
|
const r = Math.min(width, height) * 0.15 * i;
|
|
4688
|
-
ctx.
|
|
4764
|
+
ctx.moveTo(cx + r, cy);
|
|
4689
4765
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
4690
|
-
ctx.stroke();
|
|
4691
4766
|
}
|
|
4692
|
-
ctx.
|
|
4767
|
+
ctx.stroke();
|
|
4693
4768
|
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4694
4769
|
const bgPatternRoll = rng();
|
|
4695
4770
|
if (bgPatternRoll < 0.6) {
|
|
4696
4771
|
ctx.save();
|
|
4697
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4698
4772
|
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4699
4773
|
const patternColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4700
4774
|
if (bgPatternRoll < 0.2) {
|
|
4701
|
-
// Dot grid —
|
|
4702
|
-
const dotSpacing = Math.max(
|
|
4703
|
-
const
|
|
4775
|
+
// Dot grid — use fillRect instead of arcs (much cheaper, no path building)
|
|
4776
|
+
const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4777
|
+
const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
|
|
4704
4778
|
ctx.globalAlpha = patternOpacity;
|
|
4705
4779
|
ctx.fillStyle = patternColor;
|
|
4706
|
-
|
|
4707
|
-
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4708
|
-
ctx.
|
|
4709
|
-
|
|
4780
|
+
let dotCount = 0;
|
|
4781
|
+
for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
|
|
4782
|
+
ctx.fillRect(px, py, dotDiam, dotDiam);
|
|
4783
|
+
dotCount++;
|
|
4710
4784
|
}
|
|
4711
|
-
ctx.fill();
|
|
4712
4785
|
} else if (bgPatternRoll < 0.4) {
|
|
4713
|
-
// Diagonal lines — batched into a single path
|
|
4714
|
-
const lineSpacing = Math.max(
|
|
4786
|
+
// Diagonal lines — batched into a single path, capped at 300 lines
|
|
4787
|
+
const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4715
4788
|
ctx.globalAlpha = patternOpacity;
|
|
4716
4789
|
ctx.strokeStyle = patternColor;
|
|
4717
4790
|
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4718
4791
|
const diag = Math.hypot(width, height);
|
|
4719
4792
|
ctx.beginPath();
|
|
4720
|
-
|
|
4793
|
+
let lineCount = 0;
|
|
4794
|
+
for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
|
|
4721
4795
|
ctx.moveTo(d, 0);
|
|
4722
4796
|
ctx.lineTo(d + height, height);
|
|
4797
|
+
lineCount++;
|
|
4723
4798
|
}
|
|
4724
4799
|
ctx.stroke();
|
|
4725
4800
|
} else {
|
|
4726
|
-
// Tessellation — hexagonal grid,
|
|
4727
|
-
const tessSize = Math.max(
|
|
4801
|
+
// Tessellation — hexagonal grid, capped at 500 hexagons
|
|
4802
|
+
const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4728
4803
|
const tessH = tessSize * Math.sqrt(3);
|
|
4729
4804
|
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4730
4805
|
ctx.strokeStyle = patternColor;
|
|
4731
4806
|
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4807
|
+
// Pre-compute hex vertex offsets (avoid trig per vertex)
|
|
4808
|
+
const hexVx = [];
|
|
4809
|
+
const hexVy = [];
|
|
4810
|
+
for(let s = 0; s < 6; s++){
|
|
4811
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4812
|
+
hexVx.push(Math.cos(angle) * tessSize * 0.5);
|
|
4813
|
+
hexVy.push(Math.sin(angle) * tessSize * 0.5);
|
|
4814
|
+
}
|
|
4732
4815
|
ctx.beginPath();
|
|
4733
|
-
|
|
4816
|
+
let hexCount = 0;
|
|
4817
|
+
for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
|
|
4734
4818
|
const offsetX = row % 2 * tessSize * 0.75;
|
|
4735
|
-
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4819
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
|
|
4736
4820
|
const hx = col * tessSize * 1.5 + offsetX;
|
|
4737
4821
|
const hy = row * tessH;
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4741
|
-
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4742
|
-
if (s === 0) ctx.moveTo(vx, vy);
|
|
4743
|
-
else ctx.lineTo(vx, vy);
|
|
4744
|
-
}
|
|
4822
|
+
ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
|
|
4823
|
+
for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
|
|
4745
4824
|
ctx.closePath();
|
|
4825
|
+
hexCount++;
|
|
4746
4826
|
}
|
|
4747
4827
|
}
|
|
4748
4828
|
ctx.stroke();
|
|
@@ -4750,6 +4830,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4750
4830
|
ctx.restore();
|
|
4751
4831
|
}
|
|
4752
4832
|
ctx.globalCompositeOperation = "source-over";
|
|
4833
|
+
_mark("1_background");
|
|
4753
4834
|
// ── 2. Composition mode — archetype-aware selection ──────────────
|
|
4754
4835
|
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)];
|
|
4755
4836
|
const symRoll = rng();
|
|
@@ -4860,6 +4941,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4860
4941
|
}
|
|
4861
4942
|
}
|
|
4862
4943
|
ctx.globalAlpha = 1;
|
|
4944
|
+
_mark("2_3_composition_focal");
|
|
4863
4945
|
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4864
4946
|
// Create a seeded simplex noise field (unique per hash)
|
|
4865
4947
|
const noiseFieldRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 333));
|
|
@@ -4916,7 +4998,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4916
4998
|
renderStyle: heroStyle,
|
|
4917
4999
|
rng: rng,
|
|
4918
5000
|
lightAngle: lightAngle,
|
|
4919
|
-
scaleFactor: scaleFactor
|
|
5001
|
+
scaleFactor: scaleFactor,
|
|
5002
|
+
activeShapes: activeShapes
|
|
4920
5003
|
});
|
|
4921
5004
|
heroCenter = {
|
|
4922
5005
|
x: heroFocal.x,
|
|
@@ -4936,6 +5019,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4936
5019
|
shape: heroShape
|
|
4937
5020
|
});
|
|
4938
5021
|
}
|
|
5022
|
+
_mark("4_flowfield_hero");
|
|
4939
5023
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
4940
5024
|
const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
|
|
4941
5025
|
// ── Complexity budget — caps total rendering work ──────────────
|
|
@@ -5118,7 +5202,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5118
5202
|
renderStyle: finalRenderStyle,
|
|
5119
5203
|
rng: rng,
|
|
5120
5204
|
lightAngle: lightAngle,
|
|
5121
|
-
scaleFactor: scaleFactor
|
|
5205
|
+
scaleFactor: scaleFactor,
|
|
5206
|
+
activeShapes: activeShapes
|
|
5122
5207
|
};
|
|
5123
5208
|
if (shouldMirror) {
|
|
5124
5209
|
(0, $c3de8257a8baa3b0$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
@@ -5149,7 +5234,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5149
5234
|
rotation: rotation,
|
|
5150
5235
|
proportionType: "GOLDEN_RATIO",
|
|
5151
5236
|
renderStyle: "fill-only",
|
|
5152
|
-
rng: rng
|
|
5237
|
+
rng: rng,
|
|
5238
|
+
activeShapes: activeShapes
|
|
5153
5239
|
});
|
|
5154
5240
|
}
|
|
5155
5241
|
extrasSpent += glazePasses;
|
|
@@ -5189,7 +5275,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5189
5275
|
rotation: rotation + (e + 1) * 15,
|
|
5190
5276
|
proportionType: "GOLDEN_RATIO",
|
|
5191
5277
|
renderStyle: finalRenderStyle,
|
|
5192
|
-
rng: rng
|
|
5278
|
+
rng: rng,
|
|
5279
|
+
activeShapes: activeShapes
|
|
5193
5280
|
});
|
|
5194
5281
|
shapePositions.push({
|
|
5195
5282
|
x: echoX,
|
|
@@ -5236,7 +5323,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5236
5323
|
rotation: innerRot,
|
|
5237
5324
|
proportionType: "GOLDEN_RATIO",
|
|
5238
5325
|
renderStyle: innerStyle,
|
|
5239
|
-
rng: rng
|
|
5326
|
+
rng: rng,
|
|
5327
|
+
activeShapes: activeShapes
|
|
5240
5328
|
});
|
|
5241
5329
|
extrasSpent += $4f72c5a314eddf25$var$RENDER_STYLE_COST[innerStyle] ?? 1;
|
|
5242
5330
|
}
|
|
@@ -5283,7 +5371,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5283
5371
|
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
5284
5372
|
proportionType: "GOLDEN_RATIO",
|
|
5285
5373
|
renderStyle: memberStyle,
|
|
5286
|
-
rng: rng
|
|
5374
|
+
rng: rng,
|
|
5375
|
+
activeShapes: activeShapes
|
|
5287
5376
|
});
|
|
5288
5377
|
shapePositions.push({
|
|
5289
5378
|
x: mx,
|
|
@@ -5337,7 +5426,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5337
5426
|
rotation: rotation + (r + 1) * 12,
|
|
5338
5427
|
proportionType: "GOLDEN_RATIO",
|
|
5339
5428
|
renderStyle: finalRenderStyle,
|
|
5340
|
-
rng: rng
|
|
5429
|
+
rng: rng,
|
|
5430
|
+
activeShapes: activeShapes
|
|
5341
5431
|
});
|
|
5342
5432
|
shapePositions.push({
|
|
5343
5433
|
x: rx,
|
|
@@ -5364,64 +5454,13 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5364
5454
|
}
|
|
5365
5455
|
// Reset blend mode for post-processing passes
|
|
5366
5456
|
ctx.globalCompositeOperation = "source-over";
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
5371
|
-
const portalCount = 1 + Math.floor(rng() * 2);
|
|
5372
|
-
for(let p = 0; p < portalCount; p++){
|
|
5373
|
-
// Pick a position biased toward placed shapes
|
|
5374
|
-
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5375
|
-
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5376
|
-
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5377
|
-
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
5378
|
-
// Pick a portal shape from the palette
|
|
5379
|
-
const portalShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
5380
|
-
const portalRotation = rng() * 360;
|
|
5381
|
-
const portalAlpha = 0.6 + rng() * 0.35;
|
|
5382
|
-
ctx.save();
|
|
5383
|
-
ctx.translate(portalX, portalY);
|
|
5384
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5385
|
-
// Step 1: Clip to the portal shape and fill with background wash
|
|
5386
|
-
ctx.beginPath();
|
|
5387
|
-
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
5388
|
-
ctx.clip();
|
|
5389
|
-
// Fill the clipped region with a radial gradient from background colors
|
|
5390
|
-
const portalColor = (0, $d016ad53434219a1$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
5391
|
-
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
5392
|
-
portalGrad.addColorStop(0, portalColor);
|
|
5393
|
-
portalGrad.addColorStop(1, bgEnd);
|
|
5394
|
-
ctx.globalAlpha = portalAlpha;
|
|
5395
|
-
ctx.fillStyle = portalGrad;
|
|
5396
|
-
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
5397
|
-
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
5398
|
-
if (rng() < 0.5) {
|
|
5399
|
-
const dotCount = 3 + Math.floor(rng() * 5);
|
|
5400
|
-
ctx.globalAlpha = portalAlpha * 0.3;
|
|
5401
|
-
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
5402
|
-
for(let d = 0; d < dotCount; d++){
|
|
5403
|
-
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
5404
|
-
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
5405
|
-
const dr = (1 + rng() * 3) * scaleFactor;
|
|
5406
|
-
ctx.beginPath();
|
|
5407
|
-
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
5408
|
-
ctx.fill();
|
|
5409
|
-
}
|
|
5410
|
-
}
|
|
5411
|
-
ctx.restore();
|
|
5412
|
-
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
5413
|
-
ctx.save();
|
|
5414
|
-
ctx.translate(portalX, portalY);
|
|
5415
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5416
|
-
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
5417
|
-
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
5418
|
-
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
5419
|
-
ctx.beginPath();
|
|
5420
|
-
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
5421
|
-
ctx.stroke();
|
|
5422
|
-
ctx.restore();
|
|
5423
|
-
}
|
|
5457
|
+
if (_dt) {
|
|
5458
|
+
_dt.shapeCount = shapePositions.length;
|
|
5459
|
+
_dt.extraCount = extrasSpent;
|
|
5424
5460
|
}
|
|
5461
|
+
_mark("5_shape_layers");
|
|
5462
|
+
// ── 5g. (Portal/cutout feature removed — replaced by custom shapes API) ──
|
|
5463
|
+
_mark("5g_portals");
|
|
5425
5464
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5426
5465
|
// Optimized: collect all segments into width-quantized buckets, then
|
|
5427
5466
|
// render each bucket as a single batched path. This reduces
|
|
@@ -5547,6 +5586,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5547
5586
|
ctx.stroke();
|
|
5548
5587
|
}
|
|
5549
5588
|
}
|
|
5589
|
+
_mark("6_flow_lines");
|
|
5550
5590
|
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
5551
5591
|
// Optimized: collect all burst segments, then batch by quantized alpha
|
|
5552
5592
|
const energyArchetypes = [
|
|
@@ -5609,6 +5649,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5609
5649
|
ctx.stroke();
|
|
5610
5650
|
}
|
|
5611
5651
|
}
|
|
5652
|
+
_mark("6b_energy_lines");
|
|
5612
5653
|
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
5613
5654
|
if (symmetryMode !== "none") {
|
|
5614
5655
|
const canvas = ctx.canvas;
|
|
@@ -5629,60 +5670,25 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5629
5670
|
}
|
|
5630
5671
|
ctx.restore();
|
|
5631
5672
|
}
|
|
5632
|
-
|
|
5633
|
-
//
|
|
5634
|
-
//
|
|
5673
|
+
_mark("6c_symmetry");
|
|
5674
|
+
// ── 7. Noise texture overlay ─────────────────────────────────────
|
|
5675
|
+
// With density capped at 2500 dots, direct fillRect calls are far cheaper
|
|
5676
|
+
// than the getImageData/putImageData round-trip which copies the entire
|
|
5677
|
+
// pixel buffer (4 × width × height bytes) twice.
|
|
5635
5678
|
const noiseRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 777));
|
|
5636
5679
|
const rawNoiseDensity = Math.floor(width * height / 800);
|
|
5637
|
-
// Cap at 2500 dots — beyond this the visual effect is indistinguishable
|
|
5638
|
-
// but getImageData/putImageData cost scales with canvas size
|
|
5639
5680
|
const noiseDensity = Math.min(rawNoiseDensity, 2500);
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
const
|
|
5643
|
-
const
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5650
|
-
// srcA in range [0.01, 0.04] — multiply by 256 for fixed-point
|
|
5651
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5652
|
-
const invA256 = 256 - srcA256;
|
|
5653
|
-
const bSrc = brightness * srcA256; // pre-multiply brightness × alpha
|
|
5654
|
-
const idx = ny * width + nx << 2;
|
|
5655
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5656
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5657
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5658
|
-
}
|
|
5659
|
-
else for(let i = 0; i < noiseDensity; i++){
|
|
5660
|
-
const nx = Math.floor(noiseRng() * width);
|
|
5661
|
-
const ny = Math.floor(noiseRng() * height);
|
|
5662
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5663
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5664
|
-
const invA256 = 256 - srcA256;
|
|
5665
|
-
const bSrc = brightness * srcA256;
|
|
5666
|
-
for(let dy = 0; dy < pixelScale && ny + dy < height; dy++)for(let dx = 0; dx < pixelScale && nx + dx < width; dx++){
|
|
5667
|
-
const idx = (ny + dy) * width + (nx + dx) << 2;
|
|
5668
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5669
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5670
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5671
|
-
}
|
|
5672
|
-
}
|
|
5673
|
-
ctx.putImageData(imageData, 0, 0);
|
|
5674
|
-
} catch {
|
|
5675
|
-
// Fallback for environments where getImageData isn't available (e.g. some OffscreenCanvas)
|
|
5676
|
-
for(let i = 0; i < noiseDensity; i++){
|
|
5677
|
-
const nx = noiseRng() * width;
|
|
5678
|
-
const ny = noiseRng() * height;
|
|
5679
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5680
|
-
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5681
|
-
ctx.globalAlpha = alpha;
|
|
5682
|
-
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
5683
|
-
ctx.fillRect(nx, ny, 1 * scaleFactor, 1 * scaleFactor);
|
|
5684
|
-
}
|
|
5681
|
+
const pixelScale = Math.max(1, Math.round(scaleFactor));
|
|
5682
|
+
for(let i = 0; i < noiseDensity; i++){
|
|
5683
|
+
const nx = noiseRng() * width;
|
|
5684
|
+
const ny = noiseRng() * height;
|
|
5685
|
+
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5686
|
+
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5687
|
+
ctx.globalAlpha = alpha;
|
|
5688
|
+
ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
|
|
5689
|
+
ctx.fillRect(nx, ny, pixelScale, pixelScale);
|
|
5685
5690
|
}
|
|
5691
|
+
_mark("7_noise_texture");
|
|
5686
5692
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
5687
5693
|
ctx.globalAlpha = 1;
|
|
5688
5694
|
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
@@ -5696,6 +5702,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5696
5702
|
vigGrad.addColorStop(1, vignetteColor);
|
|
5697
5703
|
ctx.fillStyle = vigGrad;
|
|
5698
5704
|
ctx.fillRect(0, 0, width, height);
|
|
5705
|
+
_mark("8_vignette");
|
|
5699
5706
|
// ── 9. Organic connecting curves — proximity-aware ───────────────
|
|
5700
5707
|
// Optimized: batch all curves into alpha-quantized groups to reduce
|
|
5701
5708
|
// beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
|
|
@@ -5754,6 +5761,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5754
5761
|
ctx.stroke();
|
|
5755
5762
|
}
|
|
5756
5763
|
}
|
|
5764
|
+
_mark("9_connecting_curves");
|
|
5757
5765
|
// ── 10. Post-processing ────────────────────────────────────────
|
|
5758
5766
|
// 10a. Color grading — unified tone across the whole image
|
|
5759
5767
|
// Apply as a semi-transparent overlay in the grade hue
|
|
@@ -5813,6 +5821,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5813
5821
|
ctx.fillRect(0, 0, width, height);
|
|
5814
5822
|
ctx.globalCompositeOperation = "source-over";
|
|
5815
5823
|
}
|
|
5824
|
+
_mark("10_post_processing");
|
|
5816
5825
|
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5817
5826
|
{
|
|
5818
5827
|
ctx.save();
|
|
@@ -5979,6 +5988,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5979
5988
|
// Other archetypes: no border (intentional — not every image needs one)
|
|
5980
5989
|
ctx.restore();
|
|
5981
5990
|
}
|
|
5991
|
+
_mark("10e_borders");
|
|
5982
5992
|
// ── 11. Signature mark — placed in the least-dense corner ──────
|
|
5983
5993
|
{
|
|
5984
5994
|
const sigRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 42));
|
|
@@ -6046,6 +6056,9 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
6046
6056
|
ctx.restore();
|
|
6047
6057
|
}
|
|
6048
6058
|
ctx.globalAlpha = 1;
|
|
6059
|
+
_mark("11_signature");
|
|
6060
|
+
// Clean up custom shape profiles to avoid leaking into subsequent renders
|
|
6061
|
+
for (const name of customShapeNames)delete (0, $e73976f898150d4d$export$4343b39fe47bd82c)[name];
|
|
6049
6062
|
}
|
|
6050
6063
|
|
|
6051
6064
|
|