git-hash-art 0.12.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/CHANGELOG.md +8 -0
- package/dist/browser.js +85 -85
- package/dist/browser.js.map +1 -1
- package/dist/main.js +85 -85
- package/dist/main.js.map +1 -1
- package/dist/module.js +85 -85
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/phase-breakdown.test.ts +44 -0
- package/src/lib/render.ts +96 -89
- package/src/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [0.13.0](https://github.com/gfargo/git-hash-art/compare/0.12.0...0.13.0)
|
|
8
|
+
|
|
9
|
+
- perf: 33-111× pipeline speedup via phase-level profiling [`#23`](https://github.com/gfargo/git-hash-art/pull/23)
|
|
10
|
+
- perf: 33-111× speedup via phase-level profiling and targeted optimizations [`90ed5f5`](https://github.com/gfargo/git-hash-art/commit/90ed5f567bb57a507f11b836156bf8828a946013)
|
|
11
|
+
|
|
7
12
|
#### [0.12.0](https://github.com/gfargo/git-hash-art/compare/0.11.0...0.12.0)
|
|
8
13
|
|
|
14
|
+
> 19 March 2026
|
|
15
|
+
|
|
9
16
|
- perf: cross-env rendering optimizations round 2 [`#22`](https://github.com/gfargo/git-hash-art/pull/22)
|
|
10
17
|
- perf: optimize rendering pipeline — batch flow lines, cap clip-heavy styles, cache color parsing [`#21`](https://github.com/gfargo/git-hash-art/pull/21)
|
|
11
18
|
- docs: update ALGORITHM.md to reflect rendering pipeline changes [`2631e0c`](https://github.com/gfargo/git-hash-art/commit/2631e0c10b2b6bfb0a98c6a31d71d7ddaa8e7511)
|
|
19
|
+
- chore: release v0.12.0 [`501a71c`](https://github.com/gfargo/git-hash-art/commit/501a71c9ca8251d67141fa69b9ecaa62ae5f96c1)
|
|
12
20
|
|
|
13
21
|
#### [0.11.0](https://github.com/gfargo/git-hash-art/compare/0.10.1...0.11.0)
|
|
14
22
|
|
package/dist/browser.js
CHANGED
|
@@ -4582,6 +4582,15 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4582
4582
|
...(0, $81c1b644006d48ec$export$c2f8e0cc249a8d8f),
|
|
4583
4583
|
...config
|
|
4584
4584
|
};
|
|
4585
|
+
const _dt = finalConfig._debugTiming;
|
|
4586
|
+
const _t = _dt ? ()=>performance.now() : undefined;
|
|
4587
|
+
let _p = _t ? _t() : 0;
|
|
4588
|
+
function _mark(name) {
|
|
4589
|
+
if (!_dt || !_t) return;
|
|
4590
|
+
const now = _t();
|
|
4591
|
+
_dt.phases[name] = now - _p;
|
|
4592
|
+
_p = now;
|
|
4593
|
+
}
|
|
4585
4594
|
const rng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash));
|
|
4586
4595
|
// ── 0. Select archetype — fundamentally different visual personality ──
|
|
4587
4596
|
const archetype = (0, $68a238ccd77f2bcd$export$f1142fd7da4d6590)(rng);
|
|
@@ -4615,12 +4624,14 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4615
4624
|
const adjustedMaxSize = maxShapeSize * scaleFactor;
|
|
4616
4625
|
const cx = width / 2;
|
|
4617
4626
|
const cy = height / 2;
|
|
4627
|
+
_mark("0_setup");
|
|
4618
4628
|
// ── 1. Background ──────────────────────────────────────────────
|
|
4619
4629
|
const bgRadius = Math.hypot(cx, cy);
|
|
4620
4630
|
$1f63dc64b5593c73$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
|
|
4621
4631
|
// Gradient mesh overlay — 3-4 color control points for richer backgrounds
|
|
4632
|
+
// Use source-over instead of soft-light for cheaper compositing
|
|
4622
4633
|
const meshPoints = 3 + Math.floor(rng() * 2);
|
|
4623
|
-
ctx.
|
|
4634
|
+
ctx.globalAlpha = 1;
|
|
4624
4635
|
for(let i = 0; i < meshPoints; i++){
|
|
4625
4636
|
const mx = rng() * width;
|
|
4626
4637
|
const my = rng() * height;
|
|
@@ -4629,95 +4640,103 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4629
4640
|
const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
|
|
4630
4641
|
grad.addColorStop(0, (0, $b5a262d09b87e373$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
|
|
4631
4642
|
grad.addColorStop(1, "rgba(0,0,0,0)");
|
|
4632
|
-
ctx.globalAlpha = 1;
|
|
4633
4643
|
ctx.fillStyle = grad;
|
|
4634
|
-
|
|
4644
|
+
// Clip to gradient bounding box — avoids blending transparent pixels
|
|
4645
|
+
const gx = Math.max(0, mx - mRadius);
|
|
4646
|
+
const gy = Math.max(0, my - mRadius);
|
|
4647
|
+
const gw = Math.min(width, mx + mRadius) - gx;
|
|
4648
|
+
const gh = Math.min(height, my + mRadius) - gy;
|
|
4649
|
+
ctx.fillRect(gx, gy, gw, gh);
|
|
4635
4650
|
}
|
|
4636
|
-
ctx.globalCompositeOperation = "source-over";
|
|
4637
4651
|
// Compute average background luminance for contrast enforcement
|
|
4638
4652
|
const bgLum = ((0, $b5a262d09b87e373$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $b5a262d09b87e373$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
|
|
4639
4653
|
// ── 1b. Layered background — archetype-coherent shapes ─────────
|
|
4654
|
+
// Use source-over with pre-multiplied alpha instead of soft-light
|
|
4655
|
+
// for much cheaper compositing (soft-light requires per-pixel blend)
|
|
4640
4656
|
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
4641
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4642
4657
|
for(let i = 0; i < bgShapeCount; i++){
|
|
4643
4658
|
const bx = rng() * width;
|
|
4644
4659
|
const by = rng() * height;
|
|
4645
4660
|
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
4646
4661
|
const bColor = (0, $b5a262d09b87e373$export$b49f62f0a99da0e8)(colorHierarchy, rng);
|
|
4647
|
-
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
4662
|
+
ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
|
|
4648
4663
|
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(bColor, 0.15);
|
|
4649
4664
|
ctx.beginPath();
|
|
4650
4665
|
// Use archetype-appropriate background shapes
|
|
4651
|
-
if (archetype.name === "geometric-precision" || archetype.name === "op-art")
|
|
4652
|
-
ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4666
|
+
if (archetype.name === "geometric-precision" || archetype.name === "op-art") ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
|
|
4653
4667
|
else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
4654
4668
|
ctx.fill();
|
|
4655
4669
|
}
|
|
4656
|
-
// Subtle concentric rings from center
|
|
4670
|
+
// Subtle concentric rings from center — batched into single stroke
|
|
4657
4671
|
const ringCount = 2 + Math.floor(rng() * 3);
|
|
4658
4672
|
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
4659
4673
|
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
|
|
4660
4674
|
ctx.lineWidth = 1 * scaleFactor;
|
|
4675
|
+
ctx.beginPath();
|
|
4661
4676
|
for(let i = 1; i <= ringCount; i++){
|
|
4662
4677
|
const r = Math.min(width, height) * 0.15 * i;
|
|
4663
|
-
ctx.
|
|
4678
|
+
ctx.moveTo(cx + r, cy);
|
|
4664
4679
|
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
4665
|
-
ctx.stroke();
|
|
4666
4680
|
}
|
|
4667
|
-
ctx.
|
|
4681
|
+
ctx.stroke();
|
|
4668
4682
|
// ── 1c. Background pattern layer — subtle textured paper ───────
|
|
4669
4683
|
const bgPatternRoll = rng();
|
|
4670
4684
|
if (bgPatternRoll < 0.6) {
|
|
4671
4685
|
ctx.save();
|
|
4672
|
-
ctx.globalCompositeOperation = "soft-light";
|
|
4673
4686
|
const patternOpacity = 0.02 + rng() * 0.04;
|
|
4674
4687
|
const patternColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
|
|
4675
4688
|
if (bgPatternRoll < 0.2) {
|
|
4676
|
-
// Dot grid —
|
|
4677
|
-
const dotSpacing = Math.max(
|
|
4678
|
-
const
|
|
4689
|
+
// Dot grid — use fillRect instead of arcs (much cheaper, no path building)
|
|
4690
|
+
const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
|
|
4691
|
+
const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
|
|
4679
4692
|
ctx.globalAlpha = patternOpacity;
|
|
4680
4693
|
ctx.fillStyle = patternColor;
|
|
4681
|
-
|
|
4682
|
-
for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
|
|
4683
|
-
ctx.
|
|
4684
|
-
|
|
4694
|
+
let dotCount = 0;
|
|
4695
|
+
for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
|
|
4696
|
+
ctx.fillRect(px, py, dotDiam, dotDiam);
|
|
4697
|
+
dotCount++;
|
|
4685
4698
|
}
|
|
4686
|
-
ctx.fill();
|
|
4687
4699
|
} else if (bgPatternRoll < 0.4) {
|
|
4688
|
-
// Diagonal lines — batched into a single path
|
|
4689
|
-
const lineSpacing = Math.max(
|
|
4700
|
+
// Diagonal lines — batched into a single path, capped at 300 lines
|
|
4701
|
+
const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
|
|
4690
4702
|
ctx.globalAlpha = patternOpacity;
|
|
4691
4703
|
ctx.strokeStyle = patternColor;
|
|
4692
4704
|
ctx.lineWidth = 0.5 * scaleFactor;
|
|
4693
4705
|
const diag = Math.hypot(width, height);
|
|
4694
4706
|
ctx.beginPath();
|
|
4695
|
-
|
|
4707
|
+
let lineCount = 0;
|
|
4708
|
+
for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
|
|
4696
4709
|
ctx.moveTo(d, 0);
|
|
4697
4710
|
ctx.lineTo(d + height, height);
|
|
4711
|
+
lineCount++;
|
|
4698
4712
|
}
|
|
4699
4713
|
ctx.stroke();
|
|
4700
4714
|
} else {
|
|
4701
|
-
// Tessellation — hexagonal grid,
|
|
4702
|
-
const tessSize = Math.max(
|
|
4715
|
+
// Tessellation — hexagonal grid, capped at 500 hexagons
|
|
4716
|
+
const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
|
|
4703
4717
|
const tessH = tessSize * Math.sqrt(3);
|
|
4704
4718
|
ctx.globalAlpha = patternOpacity * 0.7;
|
|
4705
4719
|
ctx.strokeStyle = patternColor;
|
|
4706
4720
|
ctx.lineWidth = 0.4 * scaleFactor;
|
|
4721
|
+
// Pre-compute hex vertex offsets (avoid trig per vertex)
|
|
4722
|
+
const hexVx = [];
|
|
4723
|
+
const hexVy = [];
|
|
4724
|
+
for(let s = 0; s < 6; s++){
|
|
4725
|
+
const angle = Math.PI / 3 * s - Math.PI / 6;
|
|
4726
|
+
hexVx.push(Math.cos(angle) * tessSize * 0.5);
|
|
4727
|
+
hexVy.push(Math.sin(angle) * tessSize * 0.5);
|
|
4728
|
+
}
|
|
4707
4729
|
ctx.beginPath();
|
|
4708
|
-
|
|
4730
|
+
let hexCount = 0;
|
|
4731
|
+
for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
|
|
4709
4732
|
const offsetX = row % 2 * tessSize * 0.75;
|
|
4710
|
-
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
|
|
4733
|
+
for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
|
|
4711
4734
|
const hx = col * tessSize * 1.5 + offsetX;
|
|
4712
4735
|
const hy = row * tessH;
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
const vx = hx + Math.cos(angle) * tessSize * 0.5;
|
|
4716
|
-
const vy = hy + Math.sin(angle) * tessSize * 0.5;
|
|
4717
|
-
if (s === 0) ctx.moveTo(vx, vy);
|
|
4718
|
-
else ctx.lineTo(vx, vy);
|
|
4719
|
-
}
|
|
4736
|
+
ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
|
|
4737
|
+
for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
|
|
4720
4738
|
ctx.closePath();
|
|
4739
|
+
hexCount++;
|
|
4721
4740
|
}
|
|
4722
4741
|
}
|
|
4723
4742
|
ctx.stroke();
|
|
@@ -4725,6 +4744,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4725
4744
|
ctx.restore();
|
|
4726
4745
|
}
|
|
4727
4746
|
ctx.globalCompositeOperation = "source-over";
|
|
4747
|
+
_mark("1_background");
|
|
4728
4748
|
// ── 2. Composition mode — archetype-aware selection ──────────────
|
|
4729
4749
|
const compositionMode = rng() < 0.7 ? archetype.preferredCompositions[Math.floor(rng() * archetype.preferredCompositions.length)] : $1f63dc64b5593c73$var$ALL_COMPOSITION_MODES[Math.floor(rng() * $1f63dc64b5593c73$var$ALL_COMPOSITION_MODES.length)];
|
|
4730
4750
|
const symRoll = rng();
|
|
@@ -4835,6 +4855,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4835
4855
|
}
|
|
4836
4856
|
}
|
|
4837
4857
|
ctx.globalAlpha = 1;
|
|
4858
|
+
_mark("2_3_composition_focal");
|
|
4838
4859
|
// ── 4. Flow field — simplex noise for organic variation ─────────
|
|
4839
4860
|
// Create a seeded simplex noise field (unique per hash)
|
|
4840
4861
|
const noiseFieldRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 333));
|
|
@@ -4911,6 +4932,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4911
4932
|
shape: heroShape
|
|
4912
4933
|
});
|
|
4913
4934
|
}
|
|
4935
|
+
_mark("4_flowfield_hero");
|
|
4914
4936
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
4915
4937
|
const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
|
|
4916
4938
|
// ── Complexity budget — caps total rendering work ──────────────
|
|
@@ -5339,6 +5361,11 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5339
5361
|
}
|
|
5340
5362
|
// Reset blend mode for post-processing passes
|
|
5341
5363
|
ctx.globalCompositeOperation = "source-over";
|
|
5364
|
+
if (_dt) {
|
|
5365
|
+
_dt.shapeCount = shapePositions.length;
|
|
5366
|
+
_dt.extraCount = extrasSpent;
|
|
5367
|
+
}
|
|
5368
|
+
_mark("5_shape_layers");
|
|
5342
5369
|
// ── 5g. Layered masking / cutout portals ───────────────────────
|
|
5343
5370
|
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
5344
5371
|
// with a tinted background wash, creating a "peek through" effect.
|
|
@@ -5397,6 +5424,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5397
5424
|
ctx.restore();
|
|
5398
5425
|
}
|
|
5399
5426
|
}
|
|
5427
|
+
_mark("5g_portals");
|
|
5400
5428
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5401
5429
|
// Optimized: collect all segments into width-quantized buckets, then
|
|
5402
5430
|
// render each bucket as a single batched path. This reduces
|
|
@@ -5522,6 +5550,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5522
5550
|
ctx.stroke();
|
|
5523
5551
|
}
|
|
5524
5552
|
}
|
|
5553
|
+
_mark("6_flow_lines");
|
|
5525
5554
|
// ── 6b. Motion/energy lines — short directional bursts ─────────
|
|
5526
5555
|
// Optimized: collect all burst segments, then batch by quantized alpha
|
|
5527
5556
|
const energyArchetypes = [
|
|
@@ -5584,6 +5613,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5584
5613
|
ctx.stroke();
|
|
5585
5614
|
}
|
|
5586
5615
|
}
|
|
5616
|
+
_mark("6b_energy_lines");
|
|
5587
5617
|
// ── 6c. Apply symmetry mirroring ─────────────────────────────────
|
|
5588
5618
|
if (symmetryMode !== "none") {
|
|
5589
5619
|
const canvas = ctx.canvas;
|
|
@@ -5604,60 +5634,25 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5604
5634
|
}
|
|
5605
5635
|
ctx.restore();
|
|
5606
5636
|
}
|
|
5607
|
-
|
|
5608
|
-
//
|
|
5609
|
-
//
|
|
5637
|
+
_mark("6c_symmetry");
|
|
5638
|
+
// ── 7. Noise texture overlay ─────────────────────────────────────
|
|
5639
|
+
// With density capped at 2500 dots, direct fillRect calls are far cheaper
|
|
5640
|
+
// than the getImageData/putImageData round-trip which copies the entire
|
|
5641
|
+
// pixel buffer (4 × width × height bytes) twice.
|
|
5610
5642
|
const noiseRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 777));
|
|
5611
5643
|
const rawNoiseDensity = Math.floor(width * height / 800);
|
|
5612
|
-
// Cap at 2500 dots — beyond this the visual effect is indistinguishable
|
|
5613
|
-
// but getImageData/putImageData cost scales with canvas size
|
|
5614
5644
|
const noiseDensity = Math.min(rawNoiseDensity, 2500);
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
const
|
|
5618
|
-
const
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5625
|
-
// srcA in range [0.01, 0.04] — multiply by 256 for fixed-point
|
|
5626
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5627
|
-
const invA256 = 256 - srcA256;
|
|
5628
|
-
const bSrc = brightness * srcA256; // pre-multiply brightness × alpha
|
|
5629
|
-
const idx = ny * width + nx << 2;
|
|
5630
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5631
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5632
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5633
|
-
}
|
|
5634
|
-
else for(let i = 0; i < noiseDensity; i++){
|
|
5635
|
-
const nx = Math.floor(noiseRng() * width);
|
|
5636
|
-
const ny = Math.floor(noiseRng() * height);
|
|
5637
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5638
|
-
const srcA256 = Math.round((0.01 + noiseRng() * 0.03) * 256);
|
|
5639
|
-
const invA256 = 256 - srcA256;
|
|
5640
|
-
const bSrc = brightness * srcA256;
|
|
5641
|
-
for(let dy = 0; dy < pixelScale && ny + dy < height; dy++)for(let dx = 0; dx < pixelScale && nx + dx < width; dx++){
|
|
5642
|
-
const idx = (ny + dy) * width + (nx + dx) << 2;
|
|
5643
|
-
data[idx] = data[idx] * invA256 + bSrc >> 8;
|
|
5644
|
-
data[idx + 1] = data[idx + 1] * invA256 + bSrc >> 8;
|
|
5645
|
-
data[idx + 2] = data[idx + 2] * invA256 + bSrc >> 8;
|
|
5646
|
-
}
|
|
5647
|
-
}
|
|
5648
|
-
ctx.putImageData(imageData, 0, 0);
|
|
5649
|
-
} catch {
|
|
5650
|
-
// Fallback for environments where getImageData isn't available (e.g. some OffscreenCanvas)
|
|
5651
|
-
for(let i = 0; i < noiseDensity; i++){
|
|
5652
|
-
const nx = noiseRng() * width;
|
|
5653
|
-
const ny = noiseRng() * height;
|
|
5654
|
-
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5655
|
-
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5656
|
-
ctx.globalAlpha = alpha;
|
|
5657
|
-
ctx.fillStyle = `rgba(${brightness},${brightness},${brightness},1)`;
|
|
5658
|
-
ctx.fillRect(nx, ny, 1 * scaleFactor, 1 * scaleFactor);
|
|
5659
|
-
}
|
|
5645
|
+
const pixelScale = Math.max(1, Math.round(scaleFactor));
|
|
5646
|
+
for(let i = 0; i < noiseDensity; i++){
|
|
5647
|
+
const nx = noiseRng() * width;
|
|
5648
|
+
const ny = noiseRng() * height;
|
|
5649
|
+
const brightness = noiseRng() > 0.5 ? 255 : 0;
|
|
5650
|
+
const alpha = 0.01 + noiseRng() * 0.03;
|
|
5651
|
+
ctx.globalAlpha = alpha;
|
|
5652
|
+
ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
|
|
5653
|
+
ctx.fillRect(nx, ny, pixelScale, pixelScale);
|
|
5660
5654
|
}
|
|
5655
|
+
_mark("7_noise_texture");
|
|
5661
5656
|
// ── 8. Vignette — darken edges to draw the eye inward ───────────
|
|
5662
5657
|
ctx.globalAlpha = 1;
|
|
5663
5658
|
const vignetteStrength = 0.25 + rng() * 0.2;
|
|
@@ -5671,6 +5666,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5671
5666
|
vigGrad.addColorStop(1, vignetteColor);
|
|
5672
5667
|
ctx.fillStyle = vigGrad;
|
|
5673
5668
|
ctx.fillRect(0, 0, width, height);
|
|
5669
|
+
_mark("8_vignette");
|
|
5674
5670
|
// ── 9. Organic connecting curves — proximity-aware ───────────────
|
|
5675
5671
|
// Optimized: batch all curves into alpha-quantized groups to reduce
|
|
5676
5672
|
// beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
|
|
@@ -5729,6 +5725,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5729
5725
|
ctx.stroke();
|
|
5730
5726
|
}
|
|
5731
5727
|
}
|
|
5728
|
+
_mark("9_connecting_curves");
|
|
5732
5729
|
// ── 10. Post-processing ────────────────────────────────────────
|
|
5733
5730
|
// 10a. Color grading — unified tone across the whole image
|
|
5734
5731
|
// Apply as a semi-transparent overlay in the grade hue
|
|
@@ -5788,6 +5785,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5788
5785
|
ctx.fillRect(0, 0, width, height);
|
|
5789
5786
|
ctx.globalCompositeOperation = "source-over";
|
|
5790
5787
|
}
|
|
5788
|
+
_mark("10_post_processing");
|
|
5791
5789
|
// ── 10e. Generative borders — archetype-driven decorative frames ──
|
|
5792
5790
|
{
|
|
5793
5791
|
ctx.save();
|
|
@@ -5954,6 +5952,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5954
5952
|
// Other archetypes: no border (intentional — not every image needs one)
|
|
5955
5953
|
ctx.restore();
|
|
5956
5954
|
}
|
|
5955
|
+
_mark("10e_borders");
|
|
5957
5956
|
// ── 11. Signature mark — placed in the least-dense corner ──────
|
|
5958
5957
|
{
|
|
5959
5958
|
const sigRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 42));
|
|
@@ -6021,6 +6020,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
6021
6020
|
ctx.restore();
|
|
6022
6021
|
}
|
|
6023
6022
|
ctx.globalAlpha = 1;
|
|
6023
|
+
_mark("11_signature");
|
|
6024
6024
|
}
|
|
6025
6025
|
|
|
6026
6026
|
|