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/module.js
CHANGED
|
@@ -2081,14 +2081,15 @@ function $9beb8f41637c29fd$export$909ab0580e273f19(style) {
|
|
|
2081
2081
|
}
|
|
2082
2082
|
}
|
|
2083
2083
|
function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
2084
|
-
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
2084
|
+
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, activeShapes: activeShapes } = config;
|
|
2085
2085
|
ctx.save();
|
|
2086
2086
|
ctx.translate(x, y);
|
|
2087
2087
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2088
2088
|
ctx.fillStyle = fillColor;
|
|
2089
2089
|
ctx.strokeStyle = strokeColor;
|
|
2090
2090
|
ctx.lineWidth = strokeWidth;
|
|
2091
|
-
const
|
|
2091
|
+
const registry = activeShapes ?? (0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5);
|
|
2092
|
+
const drawFunction = registry[shape];
|
|
2092
2093
|
if (drawFunction) {
|
|
2093
2094
|
drawFunction(ctx, size);
|
|
2094
2095
|
ctx.fill();
|
|
@@ -2598,7 +2599,8 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2598
2599
|
} else ctx.fillStyle = fillColor;
|
|
2599
2600
|
ctx.strokeStyle = strokeColor;
|
|
2600
2601
|
ctx.lineWidth = strokeWidth;
|
|
2601
|
-
const
|
|
2602
|
+
const registry = config.activeShapes ?? (0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5);
|
|
2603
|
+
const drawFunction = registry[shape];
|
|
2602
2604
|
if (drawFunction) {
|
|
2603
2605
|
drawFunction(ctx, size, {
|
|
2604
2606
|
rng: rng
|
|
@@ -3462,6 +3464,26 @@ const $24064302523652b1$export$4343b39fe47bd82c = {
|
|
|
3462
3464
|
]
|
|
3463
3465
|
}
|
|
3464
3466
|
};
|
|
3467
|
+
function $24064302523652b1$export$90912290d628650f(name, partial) {
|
|
3468
|
+
$24064302523652b1$export$4343b39fe47bd82c[name] = {
|
|
3469
|
+
tier: partial?.tier ?? 2,
|
|
3470
|
+
minSizeFraction: partial?.minSizeFraction ?? 0.05,
|
|
3471
|
+
maxSizeFraction: partial?.maxSizeFraction ?? 1.0,
|
|
3472
|
+
affinities: partial?.affinities ?? [
|
|
3473
|
+
"circle",
|
|
3474
|
+
"square"
|
|
3475
|
+
],
|
|
3476
|
+
category: "procedural",
|
|
3477
|
+
heroCandidate: partial?.heroCandidate ?? false,
|
|
3478
|
+
bestStyles: partial?.bestStyles ?? [
|
|
3479
|
+
"fill-and-stroke",
|
|
3480
|
+
"watercolor"
|
|
3481
|
+
]
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
function $24064302523652b1$export$f4ca68bd046f15ae(name) {
|
|
3485
|
+
delete $24064302523652b1$export$4343b39fe47bd82c[name];
|
|
3486
|
+
}
|
|
3465
3487
|
function $24064302523652b1$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
3466
3488
|
const available = shapeNames.filter((s)=>$24064302523652b1$export$4343b39fe47bd82c[s]);
|
|
3467
3489
|
// Pick a seed shape — tier 1 shapes that are hero candidates
|
|
@@ -3651,7 +3673,14 @@ function $24064302523652b1$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
|
|
|
3651
3673
|
|
|
3652
3674
|
|
|
3653
3675
|
/**
|
|
3654
|
-
*
|
|
3676
|
+
* Draw function signature for custom shapes.
|
|
3677
|
+
* The function should build a canvas path (moveTo/lineTo/arc/etc.)
|
|
3678
|
+
* centered at the origin. The pipeline handles translate, rotate,
|
|
3679
|
+
* fill, and stroke — your function just defines the geometry.
|
|
3680
|
+
*
|
|
3681
|
+
* @param ctx - Canvas 2D rendering context (already translated to shape center)
|
|
3682
|
+
* @param size - Bounding size in pixels
|
|
3683
|
+
* @param rng - Deterministic RNG seeded from the git hash — use this instead of Math.random()
|
|
3655
3684
|
*/ const $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f = {
|
|
3656
3685
|
width: 2048,
|
|
3657
3686
|
height: 2048,
|
|
@@ -4593,6 +4622,15 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4593
4622
|
...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
|
|
4594
4623
|
...config
|
|
4595
4624
|
};
|
|
4625
|
+
const _dt = finalConfig._debugTiming;
|
|
4626
|
+
const _t = _dt ? ()=>performance.now() : undefined;
|
|
4627
|
+
let _p = _t ? _t() : 0;
|
|
4628
|
+
function _mark(name) {
|
|
4629
|
+
if (!_dt || !_t) return;
|
|
4630
|
+
const now = _t();
|
|
4631
|
+
_dt.phases[name] = now - _p;
|
|
4632
|
+
_p = now;
|
|
4633
|
+
}
|
|
4596
4634
|
const rng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash));
|
|
4597
4635
|
// ── 0. Select archetype — fundamentally different visual personality ──
|
|
4598
4636
|
const archetype = (0, $3faa2521b78398cf$export$f1142fd7da4d6590)(rng);
|
|
@@ -4613,7 +4651,39 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4613
4651
|
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
4614
4652
|
const colorHierarchy = (0, $9d614e7d77fc2947$export$fabac4600b87056)(colors, rng);
|
|
4615
4653
|
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
4616
|
-
|
|
4654
|
+
// Merge custom shapes into a combined registry
|
|
4655
|
+
const customShapeNames = [];
|
|
4656
|
+
let activeShapes;
|
|
4657
|
+
if (finalConfig.customShapes && Object.keys(finalConfig.customShapes).length > 0) {
|
|
4658
|
+
activeShapes = {
|
|
4659
|
+
...(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)
|
|
4660
|
+
};
|
|
4661
|
+
for (const [name, def] of Object.entries(finalConfig.customShapes)){
|
|
4662
|
+
// Wrap CustomDrawFunction (ctx, size, rng) into DrawFunction (ctx, size, config?)
|
|
4663
|
+
const customDraw = def.draw;
|
|
4664
|
+
activeShapes[name] = (ctx, size, config)=>{
|
|
4665
|
+
customDraw(ctx, size, config?.rng ?? Math.random);
|
|
4666
|
+
};
|
|
4667
|
+
// Register profile for affinity system (inlined to avoid ESM interop issues)
|
|
4668
|
+
(0, $24064302523652b1$export$4343b39fe47bd82c)[name] = {
|
|
4669
|
+
tier: def.profile?.tier ?? 2,
|
|
4670
|
+
minSizeFraction: def.profile?.minSizeFraction ?? 0.05,
|
|
4671
|
+
maxSizeFraction: def.profile?.maxSizeFraction ?? 1.0,
|
|
4672
|
+
affinities: def.profile?.affinities ?? [
|
|
4673
|
+
"circle",
|
|
4674
|
+
"square"
|
|
4675
|
+
],
|
|
4676
|
+
category: "procedural",
|
|
4677
|
+
heroCandidate: def.profile?.heroCandidate ?? false,
|
|
4678
|
+
bestStyles: def.profile?.bestStyles ?? [
|
|
4679
|
+
"fill-and-stroke",
|
|
4680
|
+
"watercolor"
|
|
4681
|
+
]
|
|
4682
|
+
};
|
|
4683
|
+
customShapeNames.push(name);
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
const shapeNames = Object.keys(activeShapes ?? (0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5));
|
|
4617
4687
|
const shapePalette = (0, $24064302523652b1$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
4618
4688
|
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
4619
4689
|
const colorGrade = (0, $9d614e7d77fc2947$export$6d1620b367f86f7a)(rng);
|
|
@@ -4626,12 +4696,14 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4626
4696
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
4627
4697
|
const cx = width / 2;
|
|
4628
4698
|
const cy = height / 2;
|
|
4699
|
+
_mark("0_setup");
|
|
4629
4700
|
// ── 1. Background ──────────────────────────────────────────────
|
|
4630
4701
|
const bgRadius = Math.hypot(cx, cy);
|
|
4631
4702
|
$b623126c6e9cbb71$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
4632
4703
|
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
4704
|
+
// Use source-over instead of soft-light for cheaper compositing
|
|
4633
4705
|
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
4634
|
-
ctx.
|
|
4706
|
+
ctx.globalAlpha = 1;
|
|
4635
4707
|
for(let i = 0; i < meshPoints; i++){
|
|
4636
4708
|
const mx = rng() * width;
|
|
4637
4709
|
const my = rng() * height;
|
|
@@ -4640,95 +4712,103 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4640
4712
|
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
4641
4713
|
grad.addColorStop(0, (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
4642
4714
|
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
4643
|
-
ctx.globalAlpha = 1;
|
|
4644
4715
|
ctx.fillStyle = grad;
|
|
4645
|
-
|
|
4716
|
+
// Clip to gradient bounding box — avoids blending transparent pixels
|
|
4717
|
+
const gx = Math.max(0, mx - mRadius);
|
|
4718
|
+
const gy = Math.max(0, my - mRadius);
|
|
4719
|
+
const gw = Math.min(width, mx + mRadius) - gx;
|
|
4720
|
+
const gh = Math.min(height, my + mRadius) - gy;
|
|
4721
|
+
ctx.fillRect(gx, gy, gw, gh);
|
|
4646
4722
|
}
|
|
4647
|
-
ctx.globalCompositeOperation = "source-over";
|
|
4648
4723
|
// Compute average background luminance for contrast enforcement
|
|
4649
4724
|
const bgLum = ((0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
4650
4725
|
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
4726
|
+
// Use source-over with pre-multiplied alpha instead of soft-light
|
|
4727
|
+
// for much cheaper compositing (soft-light requires per-pixel blend)
|
|
4651
4728
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
4652
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4653
4729
|
for(let i = 0; i < bgShapeCount; i++){
|
|
4654
4730
|
const bx = rng() * width;
|
|
4655
4731
|
const by = rng() * height;
|
|
4656
4732
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
4657
4733
|
const bColor = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
4658
|
-
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
4734
|
+
ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
|
|
4659
4735
|
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(bColor, 0.15);
|
|
4660
4736
|
ctx.beginPath();
|
|
4661
4737
|
// Use archetype-appropriate background shapes
|
|
4662
|
-
if (archetype.name === "geometric-precision" || archetype.name === "op-art")
|
|
4663
|
-
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4738
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4664
4739
|
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
4665
4740
|
ctx.fill();
|
|
4666
4741
|
}
|
|
4667
|
-
// Subtle concentric rings from center
|
|
4742
|
+
// Subtle concentric rings from center — batched into single stroke
|
|
4668
4743
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
4669
4744
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
4670
4745
|
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4671
4746
|
ctx.lineWidth = 1 * scaleFactor;
|
|
4747
|
+
ctx.beginPath();
|
|
4672
4748
|
for(let i = 1; i <= ringCount; i++){
|
|
4673
4749
|
const r = Math.min(width, height) * 0.15 * i;
|
|
4674
|
-
ctx.
|
|
4750
|
+
ctx.moveTo(cx + r, cy);
|
|
4675
4751
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
4676
|
-
ctx.stroke();
|
|
4677
4752
|
}
|
|
4678
|
-
ctx.
|
|
4753
|
+
ctx.stroke();
|
|
4679
4754
|
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4680
4755
|
const bgPatternRoll = rng();
|
|
4681
4756
|
if (bgPatternRoll < 0.6) {
|
|
4682
4757
|
ctx.save();
|
|
4683
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4684
4758
|
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4685
4759
|
const patternColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4686
4760
|
if (bgPatternRoll < 0.2) {
|
|
4687
|
-
// Dot grid —
|
|
4688
|
-
const dotSpacing = Math.max(
|
|
4689
|
-
const
|
|
4761
|
+
// Dot grid — use fillRect instead of arcs (much cheaper, no path building)
|
|
4762
|
+
const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4763
|
+
const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
|
|
4690
4764
|
ctx.globalAlpha = patternOpacity;
|
|
4691
4765
|
ctx.fillStyle = patternColor;
|
|
4692
|
-
|
|
4693
|
-
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4694
|
-
ctx.
|
|
4695
|
-
|
|
4766
|
+
let dotCount = 0;
|
|
4767
|
+
for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
|
|
4768
|
+
ctx.fillRect(px, py, dotDiam, dotDiam);
|
|
4769
|
+
dotCount++;
|
|
4696
4770
|
}
|
|
4697
|
-
ctx.fill();
|
|
4698
4771
|
} else if (bgPatternRoll < 0.4) {
|
|
4699
|
-
// Diagonal lines — batched into a single path
|
|
4700
|
-
const lineSpacing = Math.max(
|
|
4772
|
+
// Diagonal lines — batched into a single path, capped at 300 lines
|
|
4773
|
+
const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4701
4774
|
ctx.globalAlpha = patternOpacity;
|
|
4702
4775
|
ctx.strokeStyle = patternColor;
|
|
4703
4776
|
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4704
4777
|
const diag = Math.hypot(width, height);
|
|
4705
4778
|
ctx.beginPath();
|
|
4706
|
-
|
|
4779
|
+
let lineCount = 0;
|
|
4780
|
+
for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
|
|
4707
4781
|
ctx.moveTo(d, 0);
|
|
4708
4782
|
ctx.lineTo(d + height, height);
|
|
4783
|
+
lineCount++;
|
|
4709
4784
|
}
|
|
4710
4785
|
ctx.stroke();
|
|
4711
4786
|
} else {
|
|
4712
|
-
// Tessellation — hexagonal grid,
|
|
4713
|
-
const tessSize = Math.max(
|
|
4787
|
+
// Tessellation — hexagonal grid, capped at 500 hexagons
|
|
4788
|
+
const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4714
4789
|
const tessH = tessSize * Math.sqrt(3);
|
|
4715
4790
|
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4716
4791
|
ctx.strokeStyle = patternColor;
|
|
4717
4792
|
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4793
|
+
// Pre-compute hex vertex offsets (avoid trig per vertex)
|
|
4794
|
+
const hexVx = [];
|
|
4795
|
+
const hexVy = [];
|
|
4796
|
+
for(let s = 0; s < 6; s++){
|
|
4797
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4798
|
+
hexVx.push(Math.cos(angle) * tessSize * 0.5);
|
|
4799
|
+
hexVy.push(Math.sin(angle) * tessSize * 0.5);
|
|
4800
|
+
}
|
|
4718
4801
|
ctx.beginPath();
|
|
4719
|
-
|
|
4802
|
+
let hexCount = 0;
|
|
4803
|
+
for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
|
|
4720
4804
|
const offsetX = row % 2 * tessSize * 0.75;
|
|
4721
|
-
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4805
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
|
|
4722
4806
|
const hx = col * tessSize * 1.5 + offsetX;
|
|
4723
4807
|
const hy = row * tessH;
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4727
|
-
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4728
|
-
if (s === 0) ctx.moveTo(vx, vy);
|
|
4729
|
-
else ctx.lineTo(vx, vy);
|
|
4730
|
-
}
|
|
4808
|
+
ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
|
|
4809
|
+
for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
|
|
4731
4810
|
ctx.closePath();
|
|
4811
|
+
hexCount++;
|
|
4732
4812
|
}
|
|
4733
4813
|
}
|
|
4734
4814
|
ctx.stroke();
|
|
@@ -4736,6 +4816,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4736
4816
|
ctx.restore();
|
|
4737
4817
|
}
|
|
4738
4818
|
ctx.globalCompositeOperation = "source-over";
|
|
4819
|
+
_mark("1_background");
|
|
4739
4820
|
// ── 2. Composition mode — archetype-aware selection ──────────────
|
|
4740
4821
|
const compositionMode = rng() < 0.7 ? archetype.preferredCompositions[Math.floor(rng() * archetype.preferredCompositions.length)] : $b623126c6e9cbb71$var$ALL_COMPOSITION_MODES[Math.floor(rng() * $b623126c6e9cbb71$var$ALL_COMPOSITION_MODES.length)];
|
|
4741
4822
|
const symRoll = rng();
|
|
@@ -4846,6 +4927,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4846
4927
|
}
|
|
4847
4928
|
}
|
|
4848
4929
|
ctx.globalAlpha = 1;
|
|
4930
|
+
_mark("2_3_composition_focal");
|
|
4849
4931
|
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4850
4932
|
// Create a seeded simplex noise field (unique per hash)
|
|
4851
4933
|
const noiseFieldRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 333));
|
|
@@ -4902,7 +4984,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4902
4984
|
renderStyle: heroStyle,
|
|
4903
4985
|
rng: rng,
|
|
4904
4986
|
lightAngle: lightAngle,
|
|
4905
|
-
scaleFactor: scaleFactor
|
|
4987
|
+
scaleFactor: scaleFactor,
|
|
4988
|
+
activeShapes: activeShapes
|
|
4906
4989
|
});
|
|
4907
4990
|
heroCenter = {
|
|
4908
4991
|
x: heroFocal.x,
|
|
@@ -4922,6 +5005,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4922
5005
|
shape: heroShape
|
|
4923
5006
|
});
|
|
4924
5007
|
}
|
|
5008
|
+
_mark("4_flowfield_hero");
|
|
4925
5009
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
4926
5010
|
const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
|
|
4927
5011
|
// ── Complexity budget — caps total rendering work ──────────────
|
|
@@ -5104,7 +5188,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5104
5188
|
renderStyle: finalRenderStyle,
|
|
5105
5189
|
rng: rng,
|
|
5106
5190
|
lightAngle: lightAngle,
|
|
5107
|
-
scaleFactor: scaleFactor
|
|
5191
|
+
scaleFactor: scaleFactor,
|
|
5192
|
+
activeShapes: activeShapes
|
|
5108
5193
|
};
|
|
5109
5194
|
if (shouldMirror) {
|
|
5110
5195
|
(0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
@@ -5135,7 +5220,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5135
5220
|
rotation: rotation,
|
|
5136
5221
|
proportionType: "GOLDEN_RATIO",
|
|
5137
5222
|
renderStyle: "fill-only",
|
|
5138
|
-
rng: rng
|
|
5223
|
+
rng: rng,
|
|
5224
|
+
activeShapes: activeShapes
|
|
5139
5225
|
});
|
|
5140
5226
|
}
|
|
5141
5227
|
extrasSpent += glazePasses;
|
|
@@ -5175,7 +5261,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5175
5261
|
rotation: rotation + (e + 1) * 15,
|
|
5176
5262
|
proportionType: "GOLDEN_RATIO",
|
|
5177
5263
|
renderStyle: finalRenderStyle,
|
|
5178
|
-
rng: rng
|
|
5264
|
+
rng: rng,
|
|
5265
|
+
activeShapes: activeShapes
|
|
5179
5266
|
});
|
|
5180
5267
|
shapePositions.push({
|
|
5181
5268
|
x: echoX,
|
|
@@ -5222,7 +5309,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5222
5309
|
rotation: innerRot,
|
|
5223
5310
|
proportionType: "GOLDEN_RATIO",
|
|
5224
5311
|
renderStyle: innerStyle,
|
|
5225
|
-
rng: rng
|
|
5312
|
+
rng: rng,
|
|
5313
|
+
activeShapes: activeShapes
|
|
5226
5314
|
});
|
|
5227
5315
|
extrasSpent += $b623126c6e9cbb71$var$RENDER_STYLE_COST[innerStyle] ?? 1;
|
|
5228
5316
|
}
|
|
@@ -5269,7 +5357,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5269
5357
|
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
5270
5358
|
proportionType: "GOLDEN_RATIO",
|
|
5271
5359
|
renderStyle: memberStyle,
|
|
5272
|
-
rng: rng
|
|
5360
|
+
rng: rng,
|
|
5361
|
+
activeShapes: activeShapes
|
|
5273
5362
|
});
|
|
5274
5363
|
shapePositions.push({
|
|
5275
5364
|
x: mx,
|
|
@@ -5323,7 +5412,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5323
5412
|
rotation: rotation + (r + 1) * 12,
|
|
5324
5413
|
proportionType: "GOLDEN_RATIO",
|
|
5325
5414
|
renderStyle: finalRenderStyle,
|
|
5326
|
-
rng: rng
|
|
5415
|
+
rng: rng,
|
|
5416
|
+
activeShapes: activeShapes
|
|
5327
5417
|
});
|
|
5328
5418
|
shapePositions.push({
|
|
5329
5419
|
x: rx,
|
|
@@ -5350,64 +5440,13 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5350
5440
|
}
|
|
5351
5441
|
// Reset blend mode for post-processing passes
|
|
5352
5442
|
ctx.globalCompositeOperation = "source-over";
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
5357
|
-
const portalCount = 1 + Math.floor(rng() * 2);
|
|
5358
|
-
for(let p = 0; p < portalCount; p++){
|
|
5359
|
-
// Pick a position biased toward placed shapes
|
|
5360
|
-
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5361
|
-
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5362
|
-
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5363
|
-
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
5364
|
-
// Pick a portal shape from the palette
|
|
5365
|
-
const portalShape = (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
5366
|
-
const portalRotation = rng() * 360;
|
|
5367
|
-
const portalAlpha = 0.6 + rng() * 0.35;
|
|
5368
|
-
ctx.save();
|
|
5369
|
-
ctx.translate(portalX, portalY);
|
|
5370
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5371
|
-
// Step 1: Clip to the portal shape and fill with background wash
|
|
5372
|
-
ctx.beginPath();
|
|
5373
|
-
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
5374
|
-
ctx.clip();
|
|
5375
|
-
// Fill the clipped region with a radial gradient from background colors
|
|
5376
|
-
const portalColor = (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
5377
|
-
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
5378
|
-
portalGrad.addColorStop(0, portalColor);
|
|
5379
|
-
portalGrad.addColorStop(1, bgEnd);
|
|
5380
|
-
ctx.globalAlpha = portalAlpha;
|
|
5381
|
-
ctx.fillStyle = portalGrad;
|
|
5382
|
-
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
5383
|
-
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
5384
|
-
if (rng() < 0.5) {
|
|
5385
|
-
const dotCount = 3 + Math.floor(rng() * 5);
|
|
5386
|
-
ctx.globalAlpha = portalAlpha * 0.3;
|
|
5387
|
-
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
5388
|
-
for(let d = 0; d < dotCount; d++){
|
|
5389
|
-
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
5390
|
-
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
5391
|
-
const dr = (1 + rng() * 3) * scaleFactor;
|
|
5392
|
-
ctx.beginPath();
|
|
5393
|
-
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
5394
|
-
ctx.fill();
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
ctx.restore();
|
|
5398
|
-
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
5399
|
-
ctx.save();
|
|
5400
|
-
ctx.translate(portalX, portalY);
|
|
5401
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5402
|
-
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
5403
|
-
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
5404
|
-
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
5405
|
-
ctx.beginPath();
|
|
5406
|
-
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
5407
|
-
ctx.stroke();
|
|
5408
|
-
ctx.restore();
|
|
5409
|
-
}
|
|
5443
|
+
if (_dt) {
|
|
5444
|
+
_dt.shapeCount = shapePositions.length;
|
|
5445
|
+
_dt.extraCount = extrasSpent;
|
|
5410
5446
|
}
|
|
5447
|
+
_mark("5_shape_layers");
|
|
5448
|
+
// ── 5g. (Portal/cutout feature removed — replaced by custom shapes API) ──
|
|
5449
|
+
_mark("5g_portals");
|
|
5411
5450
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5412
5451
|
// Optimized: collect all segments into width-quantized buckets, then
|
|
5413
5452
|
// render each bucket as a single batched path. This reduces
|
|
@@ -5533,6 +5572,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5533
5572
|
ctx.stroke();
|
|
5534
5573
|
}
|
|
5535
5574
|
}
|
|
5575
|
+
_mark("6_flow_lines");
|
|
5536
5576
|
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
5537
5577
|
// Optimized: collect all burst segments, then batch by quantized alpha
|
|
5538
5578
|
const energyArchetypes = [
|
|
@@ -5595,6 +5635,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5595
5635
|
ctx.stroke();
|
|
5596
5636
|
}
|
|
5597
5637
|
}
|
|
5638
|
+
_mark("6b_energy_lines");
|
|
5598
5639
|
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
5599
5640
|
if (symmetryMode !== "none") {
|
|
5600
5641
|
const canvas = ctx.canvas;
|
|
@@ -5615,60 +5656,25 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5615
5656
|
}
|
|
5616
5657
|
ctx.restore();
|
|
5617
5658
|
}
|
|
5618
|
-
|
|
5619
|
-
//
|
|
5620
|
-
//
|
|
5659
|
+
_mark("6c_symmetry");
|
|
5660
|
+
// ── 7. Noise texture overlay ─────────────────────────────────────
|
|
5661
|
+
// With density capped at 2500 dots, direct fillRect calls are far cheaper
|
|
5662
|
+
// than the getImageData/putImageData round-trip which copies the entire
|
|
5663
|
+
// pixel buffer (4 × width × height bytes) twice.
|
|
5621
5664
|
const noiseRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 777));
|
|
5622
5665
|
const rawNoiseDensity = Math.floor(width * height / 800);
|
|
5623
|
-
// Cap at 2500 dots — beyond this the visual effect is indistinguishable
|
|
5624
|
-
// but getImageData/putImageData cost scales with canvas size
|
|
5625
5666
|
const noiseDensity = Math.min(rawNoiseDensity, 2500);
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
const
|
|
5629
|
-
const
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5636
|
-
// srcA in range [0.01, 0.04] — multiply by 256 for fixed-point
|
|
5637
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5638
|
-
const invA256 = 256 - srcA256;
|
|
5639
|
-
const bSrc = brightness * srcA256; // pre-multiply brightness × alpha
|
|
5640
|
-
const idx = ny * width + nx << 2;
|
|
5641
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5642
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5643
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5644
|
-
}
|
|
5645
|
-
else for(let i = 0; i < noiseDensity; i++){
|
|
5646
|
-
const nx = Math.floor(noiseRng() * width);
|
|
5647
|
-
const ny = Math.floor(noiseRng() * height);
|
|
5648
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5649
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5650
|
-
const invA256 = 256 - srcA256;
|
|
5651
|
-
const bSrc = brightness * srcA256;
|
|
5652
|
-
for(let dy = 0; dy < pixelScale && ny + dy < height; dy++)for(let dx = 0; dx < pixelScale && nx + dx < width; dx++){
|
|
5653
|
-
const idx = (ny + dy) * width + (nx + dx) << 2;
|
|
5654
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5655
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5656
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5657
|
-
}
|
|
5658
|
-
}
|
|
5659
|
-
ctx.putImageData(imageData, 0, 0);
|
|
5660
|
-
} catch {
|
|
5661
|
-
// Fallback for environments where getImageData isn't available (e.g. some OffscreenCanvas)
|
|
5662
|
-
for(let i = 0; i < noiseDensity; i++){
|
|
5663
|
-
const nx = noiseRng() * width;
|
|
5664
|
-
const ny = noiseRng() * height;
|
|
5665
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5666
|
-
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5667
|
-
ctx.globalAlpha = alpha;
|
|
5668
|
-
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
5669
|
-
ctx.fillRect(nx, ny, 1 * scaleFactor, 1 * scaleFactor);
|
|
5670
|
-
}
|
|
5667
|
+
const pixelScale = Math.max(1, Math.round(scaleFactor));
|
|
5668
|
+
for(let i = 0; i < noiseDensity; i++){
|
|
5669
|
+
const nx = noiseRng() * width;
|
|
5670
|
+
const ny = noiseRng() * height;
|
|
5671
|
+
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5672
|
+
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5673
|
+
ctx.globalAlpha = alpha;
|
|
5674
|
+
ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
|
|
5675
|
+
ctx.fillRect(nx, ny, pixelScale, pixelScale);
|
|
5671
5676
|
}
|
|
5677
|
+
_mark("7_noise_texture");
|
|
5672
5678
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
5673
5679
|
ctx.globalAlpha = 1;
|
|
5674
5680
|
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
@@ -5682,6 +5688,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5682
5688
|
vigGrad.addColorStop(1, vignetteColor);
|
|
5683
5689
|
ctx.fillStyle = vigGrad;
|
|
5684
5690
|
ctx.fillRect(0, 0, width, height);
|
|
5691
|
+
_mark("8_vignette");
|
|
5685
5692
|
// ── 9. Organic connecting curves — proximity-aware ───────────────
|
|
5686
5693
|
// Optimized: batch all curves into alpha-quantized groups to reduce
|
|
5687
5694
|
// beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
|
|
@@ -5740,6 +5747,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5740
5747
|
ctx.stroke();
|
|
5741
5748
|
}
|
|
5742
5749
|
}
|
|
5750
|
+
_mark("9_connecting_curves");
|
|
5743
5751
|
// ── 10. Post-processing ────────────────────────────────────────
|
|
5744
5752
|
// 10a. Color grading — unified tone across the whole image
|
|
5745
5753
|
// Apply as a semi-transparent overlay in the grade hue
|
|
@@ -5799,6 +5807,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5799
5807
|
ctx.fillRect(0, 0, width, height);
|
|
5800
5808
|
ctx.globalCompositeOperation = "source-over";
|
|
5801
5809
|
}
|
|
5810
|
+
_mark("10_post_processing");
|
|
5802
5811
|
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5803
5812
|
{
|
|
5804
5813
|
ctx.save();
|
|
@@ -5965,6 +5974,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5965
5974
|
// Other archetypes: no border (intentional — not every image needs one)
|
|
5966
5975
|
ctx.restore();
|
|
5967
5976
|
}
|
|
5977
|
+
_mark("10e_borders");
|
|
5968
5978
|
// ── 11. Signature mark — placed in the least-dense corner ──────
|
|
5969
5979
|
{
|
|
5970
5980
|
const sigRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
|
|
@@ -6032,6 +6042,9 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
6032
6042
|
ctx.restore();
|
|
6033
6043
|
}
|
|
6034
6044
|
ctx.globalAlpha = 1;
|
|
6045
|
+
_mark("11_signature");
|
|
6046
|
+
// Clean up custom shape profiles to avoid leaking into subsequent renders
|
|
6047
|
+
for (const name of customShapeNames)delete (0, $24064302523652b1$export$4343b39fe47bd82c)[name];
|
|
6035
6048
|
}
|
|
6036
6049
|
|
|
6037
6050
|
|