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/dist/module.js CHANGED
@@ -4593,6 +4593,15 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4593
4593
  ...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
4594
4594
  ...config
4595
4595
  };
4596
+ const _dt = finalConfig._debugTiming;
4597
+ const _t = _dt ? ()=>performance.now() : undefined;
4598
+ let _p = _t ? _t() : 0;
4599
+ function _mark(name) {
4600
+ if (!_dt || !_t) return;
4601
+ const now = _t();
4602
+ _dt.phases[name] = now - _p;
4603
+ _p = now;
4604
+ }
4596
4605
  const rng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash));
4597
4606
  // ── 0. Select archetype — fundamentally different visual personality ──
4598
4607
  const archetype = (0, $3faa2521b78398cf$export$f1142fd7da4d6590)(rng);
@@ -4626,12 +4635,14 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4626
4635
  const adjustedMaxSize = maxShapeSize * scaleFactor;
4627
4636
  const cx = width / 2;
4628
4637
  const cy = height / 2;
4638
+ _mark("0_setup");
4629
4639
  // ── 1. Background ──────────────────────────────────────────────
4630
4640
  const bgRadius = Math.hypot(cx, cy);
4631
4641
  $b623126c6e9cbb71$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
4632
4642
  // Gradient mesh overlay — 3-4 color control points for richer backgrounds
4643
+ // Use source-over instead of soft-light for cheaper compositing
4633
4644
  const meshPoints = 3 + Math.floor(rng() * 2);
4634
- ctx.globalCompositeOperation = "soft-light";
4645
+ ctx.globalAlpha = 1;
4635
4646
  for(let i = 0; i < meshPoints; i++){
4636
4647
  const mx = rng() * width;
4637
4648
  const my = rng() * height;
@@ -4640,95 +4651,103 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4640
4651
  const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
4641
4652
  grad.addColorStop(0, (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
4642
4653
  grad.addColorStop(1, "rgba(0,0,0,0)");
4643
- ctx.globalAlpha = 1;
4644
4654
  ctx.fillStyle = grad;
4645
- ctx.fillRect(0, 0, width, height);
4655
+ // Clip to gradient bounding box — avoids blending transparent pixels
4656
+ const gx = Math.max(0, mx - mRadius);
4657
+ const gy = Math.max(0, my - mRadius);
4658
+ const gw = Math.min(width, mx + mRadius) - gx;
4659
+ const gh = Math.min(height, my + mRadius) - gy;
4660
+ ctx.fillRect(gx, gy, gw, gh);
4646
4661
  }
4647
- ctx.globalCompositeOperation = "source-over";
4648
4662
  // Compute average background luminance for contrast enforcement
4649
4663
  const bgLum = ((0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $9d614e7d77fc2947$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
4650
4664
  // ── 1b. Layered background — archetype-coherent shapes ─────────
4665
+ // Use source-over with pre-multiplied alpha instead of soft-light
4666
+ // for much cheaper compositing (soft-light requires per-pixel blend)
4651
4667
  const bgShapeCount = 3 + Math.floor(rng() * 4);
4652
- ctx.globalCompositeOperation = "soft-light";
4653
4668
  for(let i = 0; i < bgShapeCount; i++){
4654
4669
  const bx = rng() * width;
4655
4670
  const by = rng() * height;
4656
4671
  const bSize = width * 0.3 + rng() * width * 0.5;
4657
4672
  const bColor = (0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng);
4658
- ctx.globalAlpha = 0.03 + rng() * 0.05;
4673
+ ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
4659
4674
  ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(bColor, 0.15);
4660
4675
  ctx.beginPath();
4661
4676
  // Use archetype-appropriate background shapes
4662
- if (archetype.name === "geometric-precision" || archetype.name === "op-art") // Rectangular shapes for geometric archetypes
4663
- ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
4677
+ 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
4678
  else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
4665
4679
  ctx.fill();
4666
4680
  }
4667
- // Subtle concentric rings from center
4681
+ // Subtle concentric rings from center — batched into single stroke
4668
4682
  const ringCount = 2 + Math.floor(rng() * 3);
4669
4683
  ctx.globalAlpha = 0.02 + rng() * 0.03;
4670
4684
  ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
4671
4685
  ctx.lineWidth = 1 * scaleFactor;
4686
+ ctx.beginPath();
4672
4687
  for(let i = 1; i <= ringCount; i++){
4673
4688
  const r = Math.min(width, height) * 0.15 * i;
4674
- ctx.beginPath();
4689
+ ctx.moveTo(cx + r, cy);
4675
4690
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
4676
- ctx.stroke();
4677
4691
  }
4678
- ctx.globalCompositeOperation = "source-over";
4692
+ ctx.stroke();
4679
4693
  // ── 1c. Background pattern layer — subtle textured paper ───────
4680
4694
  const bgPatternRoll = rng();
4681
4695
  if (bgPatternRoll < 0.6) {
4682
4696
  ctx.save();
4683
- ctx.globalCompositeOperation = "soft-light";
4684
4697
  const patternOpacity = 0.02 + rng() * 0.04;
4685
4698
  const patternColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
4686
4699
  if (bgPatternRoll < 0.2) {
4687
- // Dot grid — batched into a single path
4688
- const dotSpacing = Math.max(8, Math.min(width, height) * (0.015 + rng() * 0.015));
4689
- const dotR = dotSpacing * 0.08;
4700
+ // Dot grid — use fillRect instead of arcs (much cheaper, no path building)
4701
+ const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
4702
+ const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
4690
4703
  ctx.globalAlpha = patternOpacity;
4691
4704
  ctx.fillStyle = patternColor;
4692
- ctx.beginPath();
4693
- for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
4694
- ctx.moveTo(px + dotR, py);
4695
- ctx.arc(px, py, dotR, 0, Math.PI * 2);
4705
+ let dotCount = 0;
4706
+ for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
4707
+ ctx.fillRect(px, py, dotDiam, dotDiam);
4708
+ dotCount++;
4696
4709
  }
4697
- ctx.fill();
4698
4710
  } else if (bgPatternRoll < 0.4) {
4699
- // Diagonal lines — batched into a single path
4700
- const lineSpacing = Math.max(6, Math.min(width, height) * (0.02 + rng() * 0.02));
4711
+ // Diagonal lines — batched into a single path, capped at 300 lines
4712
+ const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
4701
4713
  ctx.globalAlpha = patternOpacity;
4702
4714
  ctx.strokeStyle = patternColor;
4703
4715
  ctx.lineWidth = 0.5 * scaleFactor;
4704
4716
  const diag = Math.hypot(width, height);
4705
4717
  ctx.beginPath();
4706
- for(let d = -diag; d < diag; d += lineSpacing){
4718
+ let lineCount = 0;
4719
+ for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
4707
4720
  ctx.moveTo(d, 0);
4708
4721
  ctx.lineTo(d + height, height);
4722
+ lineCount++;
4709
4723
  }
4710
4724
  ctx.stroke();
4711
4725
  } else {
4712
- // Tessellation — hexagonal grid, batched into a single path
4713
- const tessSize = Math.max(10, Math.min(width, height) * (0.025 + rng() * 0.02));
4726
+ // Tessellation — hexagonal grid, capped at 500 hexagons
4727
+ const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
4714
4728
  const tessH = tessSize * Math.sqrt(3);
4715
4729
  ctx.globalAlpha = patternOpacity * 0.7;
4716
4730
  ctx.strokeStyle = patternColor;
4717
4731
  ctx.lineWidth = 0.4 * scaleFactor;
4732
+ // Pre-compute hex vertex offsets (avoid trig per vertex)
4733
+ const hexVx = [];
4734
+ const hexVy = [];
4735
+ for(let s = 0; s < 6; s++){
4736
+ const angle = Math.PI / 3 * s - Math.PI / 6;
4737
+ hexVx.push(Math.cos(angle) * tessSize * 0.5);
4738
+ hexVy.push(Math.sin(angle) * tessSize * 0.5);
4739
+ }
4718
4740
  ctx.beginPath();
4719
- for(let row = 0; row * tessH < height + tessH; row++){
4741
+ let hexCount = 0;
4742
+ for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
4720
4743
  const offsetX = row % 2 * tessSize * 0.75;
4721
- for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
4744
+ for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
4722
4745
  const hx = col * tessSize * 1.5 + offsetX;
4723
4746
  const hy = row * tessH;
4724
- for(let s = 0; s < 6; s++){
4725
- const angle = Math.PI / 3 * s - Math.PI / 6;
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
- }
4747
+ ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
4748
+ for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
4731
4749
  ctx.closePath();
4750
+ hexCount++;
4732
4751
  }
4733
4752
  }
4734
4753
  ctx.stroke();
@@ -4736,6 +4755,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4736
4755
  ctx.restore();
4737
4756
  }
4738
4757
  ctx.globalCompositeOperation = "source-over";
4758
+ _mark("1_background");
4739
4759
  // ── 2. Composition mode — archetype-aware selection ──────────────
4740
4760
  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
4761
  const symRoll = rng();
@@ -4846,6 +4866,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4846
4866
  }
4847
4867
  }
4848
4868
  ctx.globalAlpha = 1;
4869
+ _mark("2_3_composition_focal");
4849
4870
  // ── 4. Flow field — simplex noise for organic variation ─────────
4850
4871
  // Create a seeded simplex noise field (unique per hash)
4851
4872
  const noiseFieldRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 333));
@@ -4922,6 +4943,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
4922
4943
  shape: heroShape
4923
4944
  });
4924
4945
  }
4946
+ _mark("4_flowfield_hero");
4925
4947
  // ── 5. Shape layers ────────────────────────────────────────────
4926
4948
  const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
4927
4949
  // ── Complexity budget — caps total rendering work ──────────────
@@ -5350,6 +5372,11 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5350
5372
  }
5351
5373
  // Reset blend mode for post-processing passes
5352
5374
  ctx.globalCompositeOperation = "source-over";
5375
+ if (_dt) {
5376
+ _dt.shapeCount = shapePositions.length;
5377
+ _dt.extraCount = extrasSpent;
5378
+ }
5379
+ _mark("5_shape_layers");
5353
5380
  // ── 5g. Layered masking / cutout portals ───────────────────────
5354
5381
  // ~18% of images get 1-3 portal windows that paint over foreground
5355
5382
  // with a tinted background wash, creating a "peek through" effect.
@@ -5408,6 +5435,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5408
5435
  ctx.restore();
5409
5436
  }
5410
5437
  }
5438
+ _mark("5g_portals");
5411
5439
  // ── 6. Flow-line pass — variable color, branching, pressure ────
5412
5440
  // Optimized: collect all segments into width-quantized buckets, then
5413
5441
  // render each bucket as a single batched path. This reduces
@@ -5533,6 +5561,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5533
5561
  ctx.stroke();
5534
5562
  }
5535
5563
  }
5564
+ _mark("6_flow_lines");
5536
5565
  // ── 6b. Motion/energy lines — short directional bursts ─────────
5537
5566
  // Optimized: collect all burst segments, then batch by quantized alpha
5538
5567
  const energyArchetypes = [
@@ -5595,6 +5624,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5595
5624
  ctx.stroke();
5596
5625
  }
5597
5626
  }
5627
+ _mark("6b_energy_lines");
5598
5628
  // ── 6c. Apply symmetry mirroring ─────────────────────────────────
5599
5629
  if (symmetryMode !== "none") {
5600
5630
  const canvas = ctx.canvas;
@@ -5615,60 +5645,25 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5615
5645
  }
5616
5646
  ctx.restore();
5617
5647
  }
5618
- // ── 7. Noise texture overlay — batched via ImageData ─────────────
5619
- // Optimized: cap density at large sizes (diminishing returns above ~2K dots),
5620
- // skip inner pixelScale loop when scale=1, use Uint32Array for faster writes.
5648
+ _mark("6c_symmetry");
5649
+ // ── 7. Noise texture overlay ─────────────────────────────────────
5650
+ // With density capped at 2500 dots, direct fillRect calls are far cheaper
5651
+ // than the getImageData/putImageData round-trip which copies the entire
5652
+ // pixel buffer (4 × width × height bytes) twice.
5621
5653
  const noiseRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 777));
5622
5654
  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
5655
  const noiseDensity = Math.min(rawNoiseDensity, 2500);
5626
- try {
5627
- const imageData = ctx.getImageData(0, 0, width, height);
5628
- const data = imageData.data;
5629
- const pixelScale = Math.max(1, Math.round(scaleFactor));
5630
- if (pixelScale === 1) // Fast path no inner loop, direct pixel write
5631
- // Pre-compute alpha blend as integer math (avoid float multiply per channel)
5632
- for(let i = 0; i < noiseDensity; i++){
5633
- const nx = Math.floor(noiseRng() * width);
5634
- const ny = Math.floor(noiseRng() * height);
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
- }
5656
+ const pixelScale = Math.max(1, Math.round(scaleFactor));
5657
+ for(let i = 0; i < noiseDensity; i++){
5658
+ const nx = noiseRng() * width;
5659
+ const ny = noiseRng() * height;
5660
+ const brightness = noiseRng() > 0.5 ? 255 : 0;
5661
+ const alpha = 0.01 + noiseRng() * 0.03;
5662
+ ctx.globalAlpha = alpha;
5663
+ ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
5664
+ ctx.fillRect(nx, ny, pixelScale, pixelScale);
5671
5665
  }
5666
+ _mark("7_noise_texture");
5672
5667
  // ── 8. Vignette — darken edges to draw the eye inward ───────────
5673
5668
  ctx.globalAlpha = 1;
5674
5669
  const vignetteStrength = 0.25 + rng() * 0.2;
@@ -5682,6 +5677,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5682
5677
  vigGrad.addColorStop(1, vignetteColor);
5683
5678
  ctx.fillStyle = vigGrad;
5684
5679
  ctx.fillRect(0, 0, width, height);
5680
+ _mark("8_vignette");
5685
5681
  // ── 9. Organic connecting curves — proximity-aware ───────────────
5686
5682
  // Optimized: batch all curves into alpha-quantized groups to reduce
5687
5683
  // beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
@@ -5740,6 +5736,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5740
5736
  ctx.stroke();
5741
5737
  }
5742
5738
  }
5739
+ _mark("9_connecting_curves");
5743
5740
  // ── 10. Post-processing ────────────────────────────────────────
5744
5741
  // 10a. Color grading — unified tone across the whole image
5745
5742
  // Apply as a semi-transparent overlay in the grade hue
@@ -5799,6 +5796,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5799
5796
  ctx.fillRect(0, 0, width, height);
5800
5797
  ctx.globalCompositeOperation = "source-over";
5801
5798
  }
5799
+ _mark("10_post_processing");
5802
5800
  // ── 10e. Generative borders — archetype-driven decorative frames ──
5803
5801
  {
5804
5802
  ctx.save();
@@ -5965,6 +5963,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
5965
5963
  // Other archetypes: no border (intentional — not every image needs one)
5966
5964
  ctx.restore();
5967
5965
  }
5966
+ _mark("10e_borders");
5968
5967
  // ── 11. Signature mark — placed in the least-dense corner ──────
5969
5968
  {
5970
5969
  const sigRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
@@ -6032,6 +6031,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
6032
6031
  ctx.restore();
6033
6032
  }
6034
6033
  ctx.globalAlpha = 1;
6034
+ _mark("11_signature");
6035
6035
  }
6036
6036
 
6037
6037