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/main.js CHANGED
@@ -4607,6 +4607,15 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4607
4607
  ...(0, $93cf69256c93baa9$export$c2f8e0cc249a8d8f),
4608
4608
  ...config
4609
4609
  };
4610
+ const _dt = finalConfig._debugTiming;
4611
+ const _t = _dt ? ()=>performance.now() : undefined;
4612
+ let _p = _t ? _t() : 0;
4613
+ function _mark(name) {
4614
+ if (!_dt || !_t) return;
4615
+ const now = _t();
4616
+ _dt.phases[name] = now - _p;
4617
+ _p = now;
4618
+ }
4610
4619
  const rng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash));
4611
4620
  // ── 0. Select archetype — fundamentally different visual personality ──
4612
4621
  const archetype = (0, $f89bc858f7202849$export$f1142fd7da4d6590)(rng);
@@ -4640,12 +4649,14 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4640
4649
  const adjustedMaxSize = maxShapeSize * scaleFactor;
4641
4650
  const cx = width / 2;
4642
4651
  const cy = height / 2;
4652
+ _mark("0_setup");
4643
4653
  // ── 1. Background ──────────────────────────────────────────────
4644
4654
  const bgRadius = Math.hypot(cx, cy);
4645
4655
  $4f72c5a314eddf25$var$drawBackground(ctx, archetype.backgroundStyle, bgStart, bgEnd, width, height, cx, cy, bgRadius, rng, colors);
4646
4656
  // Gradient mesh overlay — 3-4 color control points for richer backgrounds
4657
+ // Use source-over instead of soft-light for cheaper compositing
4647
4658
  const meshPoints = 3 + Math.floor(rng() * 2);
4648
- ctx.globalCompositeOperation = "soft-light";
4659
+ ctx.globalAlpha = 1;
4649
4660
  for(let i = 0; i < meshPoints; i++){
4650
4661
  const mx = rng() * width;
4651
4662
  const my = rng() * height;
@@ -4654,95 +4665,103 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4654
4665
  const grad = ctx.createRadialGradient(mx, my, 0, mx, my, mRadius);
4655
4666
  grad.addColorStop(0, (0, $d016ad53434219a1$export$f2121afcad3d553f)(mColor, 0.08 + rng() * 0.06));
4656
4667
  grad.addColorStop(1, "rgba(0,0,0,0)");
4657
- ctx.globalAlpha = 1;
4658
4668
  ctx.fillStyle = grad;
4659
- ctx.fillRect(0, 0, width, height);
4669
+ // Clip to gradient bounding box — avoids blending transparent pixels
4670
+ const gx = Math.max(0, mx - mRadius);
4671
+ const gy = Math.max(0, my - mRadius);
4672
+ const gw = Math.min(width, mx + mRadius) - gx;
4673
+ const gh = Math.min(height, my + mRadius) - gy;
4674
+ ctx.fillRect(gx, gy, gw, gh);
4660
4675
  }
4661
- ctx.globalCompositeOperation = "source-over";
4662
4676
  // Compute average background luminance for contrast enforcement
4663
4677
  const bgLum = ((0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgStart) + (0, $d016ad53434219a1$export$5c6e3c2b59b7fbbe)(bgEnd)) / 2;
4664
4678
  // ── 1b. Layered background — archetype-coherent shapes ─────────
4679
+ // Use source-over with pre-multiplied alpha instead of soft-light
4680
+ // for much cheaper compositing (soft-light requires per-pixel blend)
4665
4681
  const bgShapeCount = 3 + Math.floor(rng() * 4);
4666
- ctx.globalCompositeOperation = "soft-light";
4667
4682
  for(let i = 0; i < bgShapeCount; i++){
4668
4683
  const bx = rng() * width;
4669
4684
  const by = rng() * height;
4670
4685
  const bSize = width * 0.3 + rng() * width * 0.5;
4671
4686
  const bColor = (0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng);
4672
- ctx.globalAlpha = 0.03 + rng() * 0.05;
4687
+ ctx.globalAlpha = (0.03 + rng() * 0.05) * 0.5; // halved to compensate for source-over vs soft-light
4673
4688
  ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(bColor, 0.15);
4674
4689
  ctx.beginPath();
4675
4690
  // Use archetype-appropriate background shapes
4676
- if (archetype.name === "geometric-precision" || archetype.name === "op-art") // Rectangular shapes for geometric archetypes
4677
- ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
4691
+ if (archetype.name === "geometric-precision" || archetype.name === "op-art") ctx.rect(bx - bSize / 2, by - bSize / 2, bSize, bSize * (0.5 + rng() * 0.5));
4678
4692
  else ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
4679
4693
  ctx.fill();
4680
4694
  }
4681
- // Subtle concentric rings from center
4695
+ // Subtle concentric rings from center — batched into single stroke
4682
4696
  const ringCount = 2 + Math.floor(rng() * 3);
4683
4697
  ctx.globalAlpha = 0.02 + rng() * 0.03;
4684
4698
  ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.1);
4685
4699
  ctx.lineWidth = 1 * scaleFactor;
4700
+ ctx.beginPath();
4686
4701
  for(let i = 1; i <= ringCount; i++){
4687
4702
  const r = Math.min(width, height) * 0.15 * i;
4688
- ctx.beginPath();
4703
+ ctx.moveTo(cx + r, cy);
4689
4704
  ctx.arc(cx, cy, r, 0, Math.PI * 2);
4690
- ctx.stroke();
4691
4705
  }
4692
- ctx.globalCompositeOperation = "source-over";
4706
+ ctx.stroke();
4693
4707
  // ── 1c. Background pattern layer — subtle textured paper ───────
4694
4708
  const bgPatternRoll = rng();
4695
4709
  if (bgPatternRoll < 0.6) {
4696
4710
  ctx.save();
4697
- ctx.globalCompositeOperation = "soft-light";
4698
4711
  const patternOpacity = 0.02 + rng() * 0.04;
4699
4712
  const patternColor = (0, $d016ad53434219a1$export$f2121afcad3d553f)(colorHierarchy.dominant, 0.15);
4700
4713
  if (bgPatternRoll < 0.2) {
4701
- // Dot grid — batched into a single path
4702
- const dotSpacing = Math.max(8, Math.min(width, height) * (0.015 + rng() * 0.015));
4703
- const dotR = dotSpacing * 0.08;
4714
+ // Dot grid — use fillRect instead of arcs (much cheaper, no path building)
4715
+ const dotSpacing = Math.max(12, Math.min(width, height) * (0.015 + rng() * 0.015));
4716
+ const dotDiam = Math.max(1, Math.round(dotSpacing * 0.16));
4704
4717
  ctx.globalAlpha = patternOpacity;
4705
4718
  ctx.fillStyle = patternColor;
4706
- ctx.beginPath();
4707
- for(let px = 0; px < width; px += dotSpacing)for(let py = 0; py < height; py += dotSpacing){
4708
- ctx.moveTo(px + dotR, py);
4709
- ctx.arc(px, py, dotR, 0, Math.PI * 2);
4719
+ let dotCount = 0;
4720
+ for(let px = 0; px < width && dotCount < 2000; px += dotSpacing)for(let py = 0; py < height && dotCount < 2000; py += dotSpacing){
4721
+ ctx.fillRect(px, py, dotDiam, dotDiam);
4722
+ dotCount++;
4710
4723
  }
4711
- ctx.fill();
4712
4724
  } else if (bgPatternRoll < 0.4) {
4713
- // Diagonal lines — batched into a single path
4714
- const lineSpacing = Math.max(6, Math.min(width, height) * (0.02 + rng() * 0.02));
4725
+ // Diagonal lines — batched into a single path, capped at 300 lines
4726
+ const lineSpacing = Math.max(10, Math.min(width, height) * (0.02 + rng() * 0.02));
4715
4727
  ctx.globalAlpha = patternOpacity;
4716
4728
  ctx.strokeStyle = patternColor;
4717
4729
  ctx.lineWidth = 0.5 * scaleFactor;
4718
4730
  const diag = Math.hypot(width, height);
4719
4731
  ctx.beginPath();
4720
- for(let d = -diag; d < diag; d += lineSpacing){
4732
+ let lineCount = 0;
4733
+ for(let d = -diag; d < diag && lineCount < 300; d += lineSpacing){
4721
4734
  ctx.moveTo(d, 0);
4722
4735
  ctx.lineTo(d + height, height);
4736
+ lineCount++;
4723
4737
  }
4724
4738
  ctx.stroke();
4725
4739
  } else {
4726
- // Tessellation — hexagonal grid, batched into a single path
4727
- const tessSize = Math.max(10, Math.min(width, height) * (0.025 + rng() * 0.02));
4740
+ // Tessellation — hexagonal grid, capped at 500 hexagons
4741
+ const tessSize = Math.max(15, Math.min(width, height) * (0.025 + rng() * 0.02));
4728
4742
  const tessH = tessSize * Math.sqrt(3);
4729
4743
  ctx.globalAlpha = patternOpacity * 0.7;
4730
4744
  ctx.strokeStyle = patternColor;
4731
4745
  ctx.lineWidth = 0.4 * scaleFactor;
4746
+ // Pre-compute hex vertex offsets (avoid trig per vertex)
4747
+ const hexVx = [];
4748
+ const hexVy = [];
4749
+ for(let s = 0; s < 6; s++){
4750
+ const angle = Math.PI / 3 * s - Math.PI / 6;
4751
+ hexVx.push(Math.cos(angle) * tessSize * 0.5);
4752
+ hexVy.push(Math.sin(angle) * tessSize * 0.5);
4753
+ }
4732
4754
  ctx.beginPath();
4733
- for(let row = 0; row * tessH < height + tessH; row++){
4755
+ let hexCount = 0;
4756
+ for(let row = 0; row * tessH < height + tessH && hexCount < 500; row++){
4734
4757
  const offsetX = row % 2 * tessSize * 0.75;
4735
- for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5; col++){
4758
+ for(let col = 0; col * tessSize * 1.5 < width + tessSize * 1.5 && hexCount < 500; col++){
4736
4759
  const hx = col * tessSize * 1.5 + offsetX;
4737
4760
  const hy = row * tessH;
4738
- for(let s = 0; s < 6; s++){
4739
- const angle = Math.PI / 3 * s - Math.PI / 6;
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
- }
4761
+ ctx.moveTo(hx + hexVx[0], hy + hexVy[0]);
4762
+ for(let s = 1; s < 6; s++)ctx.lineTo(hx + hexVx[s], hy + hexVy[s]);
4745
4763
  ctx.closePath();
4764
+ hexCount++;
4746
4765
  }
4747
4766
  }
4748
4767
  ctx.stroke();
@@ -4750,6 +4769,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4750
4769
  ctx.restore();
4751
4770
  }
4752
4771
  ctx.globalCompositeOperation = "source-over";
4772
+ _mark("1_background");
4753
4773
  // ── 2. Composition mode — archetype-aware selection ──────────────
4754
4774
  const compositionMode = rng() < 0.7 ? archetype.preferredCompositions[Math.floor(rng() * archetype.preferredCompositions.length)] : $4f72c5a314eddf25$var$ALL_COMPOSITION_MODES[Math.floor(rng() * $4f72c5a314eddf25$var$ALL_COMPOSITION_MODES.length)];
4755
4775
  const symRoll = rng();
@@ -4860,6 +4880,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4860
4880
  }
4861
4881
  }
4862
4882
  ctx.globalAlpha = 1;
4883
+ _mark("2_3_composition_focal");
4863
4884
  // ── 4. Flow field — simplex noise for organic variation ─────────
4864
4885
  // Create a seeded simplex noise field (unique per hash)
4865
4886
  const noiseFieldRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 333));
@@ -4936,6 +4957,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
4936
4957
  shape: heroShape
4937
4958
  });
4938
4959
  }
4960
+ _mark("4_flowfield_hero");
4939
4961
  // ── 5. Shape layers ────────────────────────────────────────────
4940
4962
  const maxLocalDensity = Math.ceil(shapesPerLayer * 0.15);
4941
4963
  // ── Complexity budget — caps total rendering work ──────────────
@@ -5364,6 +5386,11 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5364
5386
  }
5365
5387
  // Reset blend mode for post-processing passes
5366
5388
  ctx.globalCompositeOperation = "source-over";
5389
+ if (_dt) {
5390
+ _dt.shapeCount = shapePositions.length;
5391
+ _dt.extraCount = extrasSpent;
5392
+ }
5393
+ _mark("5_shape_layers");
5367
5394
  // ── 5g. Layered masking / cutout portals ───────────────────────
5368
5395
  // ~18% of images get 1-3 portal windows that paint over foreground
5369
5396
  // with a tinted background wash, creating a "peek through" effect.
@@ -5422,6 +5449,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5422
5449
  ctx.restore();
5423
5450
  }
5424
5451
  }
5452
+ _mark("5g_portals");
5425
5453
  // ── 6. Flow-line pass — variable color, branching, pressure ────
5426
5454
  // Optimized: collect all segments into width-quantized buckets, then
5427
5455
  // render each bucket as a single batched path. This reduces
@@ -5547,6 +5575,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5547
5575
  ctx.stroke();
5548
5576
  }
5549
5577
  }
5578
+ _mark("6_flow_lines");
5550
5579
  // ── 6b. Motion/energy lines — short directional bursts ─────────
5551
5580
  // Optimized: collect all burst segments, then batch by quantized alpha
5552
5581
  const energyArchetypes = [
@@ -5609,6 +5638,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5609
5638
  ctx.stroke();
5610
5639
  }
5611
5640
  }
5641
+ _mark("6b_energy_lines");
5612
5642
  // ── 6c. Apply symmetry mirroring ─────────────────────────────────
5613
5643
  if (symmetryMode !== "none") {
5614
5644
  const canvas = ctx.canvas;
@@ -5629,60 +5659,25 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5629
5659
  }
5630
5660
  ctx.restore();
5631
5661
  }
5632
- // ── 7. Noise texture overlay — batched via ImageData ─────────────
5633
- // Optimized: cap density at large sizes (diminishing returns above ~2K dots),
5634
- // skip inner pixelScale loop when scale=1, use Uint32Array for faster writes.
5662
+ _mark("6c_symmetry");
5663
+ // ── 7. Noise texture overlay ─────────────────────────────────────
5664
+ // With density capped at 2500 dots, direct fillRect calls are far cheaper
5665
+ // than the getImageData/putImageData round-trip which copies the entire
5666
+ // pixel buffer (4 × width × height bytes) twice.
5635
5667
  const noiseRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 777));
5636
5668
  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
5669
  const noiseDensity = Math.min(rawNoiseDensity, 2500);
5640
- try {
5641
- const imageData = ctx.getImageData(0, 0, width, height);
5642
- const data = imageData.data;
5643
- const pixelScale = Math.max(1, Math.round(scaleFactor));
5644
- if (pixelScale === 1) // Fast path no inner loop, direct pixel write
5645
- // Pre-compute alpha blend as integer math (avoid float multiply per channel)
5646
- for(let i = 0; i < noiseDensity; i++){
5647
- const nx = Math.floor(noiseRng() * width);
5648
- const ny = Math.floor(noiseRng() * height);
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
- }
5670
+ const pixelScale = Math.max(1, Math.round(scaleFactor));
5671
+ for(let i = 0; i < noiseDensity; i++){
5672
+ const nx = noiseRng() * width;
5673
+ const ny = noiseRng() * height;
5674
+ const brightness = noiseRng() > 0.5 ? 255 : 0;
5675
+ const alpha = 0.01 + noiseRng() * 0.03;
5676
+ ctx.globalAlpha = alpha;
5677
+ ctx.fillStyle = `rgb(${brightness},${brightness},${brightness})`;
5678
+ ctx.fillRect(nx, ny, pixelScale, pixelScale);
5685
5679
  }
5680
+ _mark("7_noise_texture");
5686
5681
  // ── 8. Vignette — darken edges to draw the eye inward ───────────
5687
5682
  ctx.globalAlpha = 1;
5688
5683
  const vignetteStrength = 0.25 + rng() * 0.2;
@@ -5696,6 +5691,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5696
5691
  vigGrad.addColorStop(1, vignetteColor);
5697
5692
  ctx.fillStyle = vigGrad;
5698
5693
  ctx.fillRect(0, 0, width, height);
5694
+ _mark("8_vignette");
5699
5695
  // ── 9. Organic connecting curves — proximity-aware ───────────────
5700
5696
  // Optimized: batch all curves into alpha-quantized groups to reduce
5701
5697
  // beginPath/stroke calls from O(numCurves) to O(alphaBuckets).
@@ -5754,6 +5750,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5754
5750
  ctx.stroke();
5755
5751
  }
5756
5752
  }
5753
+ _mark("9_connecting_curves");
5757
5754
  // ── 10. Post-processing ────────────────────────────────────────
5758
5755
  // 10a. Color grading — unified tone across the whole image
5759
5756
  // Apply as a semi-transparent overlay in the grade hue
@@ -5813,6 +5810,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5813
5810
  ctx.fillRect(0, 0, width, height);
5814
5811
  ctx.globalCompositeOperation = "source-over";
5815
5812
  }
5813
+ _mark("10_post_processing");
5816
5814
  // ── 10e. Generative borders — archetype-driven decorative frames ──
5817
5815
  {
5818
5816
  ctx.save();
@@ -5979,6 +5977,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
5979
5977
  // Other archetypes: no border (intentional — not every image needs one)
5980
5978
  ctx.restore();
5981
5979
  }
5980
+ _mark("10e_borders");
5982
5981
  // ── 11. Signature mark — placed in the least-dense corner ──────
5983
5982
  {
5984
5983
  const sigRng = (0, $e4b03e131ed2a289$export$eaf9227667332084)((0, $e4b03e131ed2a289$export$e9cc707de01b7042)(gitHash, 42));
@@ -6046,6 +6045,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
6046
6045
  ctx.restore();
6047
6046
  }
6048
6047
  ctx.globalAlpha = 1;
6048
+ _mark("11_signature");
6049
6049
  }
6050
6050
 
6051
6051