git-hash-art 0.13.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 +8 -0
- package/dist/browser.js +83 -70
- package/dist/browser.js.map +1 -1
- package/dist/main.js +83 -70
- package/dist/main.js.map +1 -1
- package/dist/module.js +83 -70
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/custom-shapes.test.ts +182 -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 +38 -67
- package/src/types.ts +50 -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,
|
|
@@ -4622,7 +4651,39 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4622
4651
|
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
4623
4652
|
const colorHierarchy = (0, $9d614e7d77fc2947$export$fabac4600b87056)(colors, rng);
|
|
4624
4653
|
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
4625
|
-
|
|
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));
|
|
4626
4687
|
const shapePalette = (0, $24064302523652b1$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
4627
4688
|
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
4628
4689
|
const colorGrade = (0, $9d614e7d77fc2947$export$6d1620b367f86f7a)(rng);
|
|
@@ -4923,7 +4984,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4923
4984
|
renderStyle: heroStyle,
|
|
4924
4985
|
rng: rng,
|
|
4925
4986
|
lightAngle: lightAngle,
|
|
4926
|
-
scaleFactor: scaleFactor
|
|
4987
|
+
scaleFactor: scaleFactor,
|
|
4988
|
+
activeShapes: activeShapes
|
|
4927
4989
|
});
|
|
4928
4990
|
heroCenter = {
|
|
4929
4991
|
x: heroFocal.x,
|
|
@@ -5126,7 +5188,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5126
5188
|
renderStyle: finalRenderStyle,
|
|
5127
5189
|
rng: rng,
|
|
5128
5190
|
lightAngle: lightAngle,
|
|
5129
|
-
scaleFactor: scaleFactor
|
|
5191
|
+
scaleFactor: scaleFactor,
|
|
5192
|
+
activeShapes: activeShapes
|
|
5130
5193
|
};
|
|
5131
5194
|
if (shouldMirror) {
|
|
5132
5195
|
(0, $9beb8f41637c29fd$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
@@ -5157,7 +5220,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5157
5220
|
rotation: rotation,
|
|
5158
5221
|
proportionType: "GOLDEN_RATIO",
|
|
5159
5222
|
renderStyle: "fill-only",
|
|
5160
|
-
rng: rng
|
|
5223
|
+
rng: rng,
|
|
5224
|
+
activeShapes: activeShapes
|
|
5161
5225
|
});
|
|
5162
5226
|
}
|
|
5163
5227
|
extrasSpent += glazePasses;
|
|
@@ -5197,7 +5261,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5197
5261
|
rotation: rotation + (e + 1) * 15,
|
|
5198
5262
|
proportionType: "GOLDEN_RATIO",
|
|
5199
5263
|
renderStyle: finalRenderStyle,
|
|
5200
|
-
rng: rng
|
|
5264
|
+
rng: rng,
|
|
5265
|
+
activeShapes: activeShapes
|
|
5201
5266
|
});
|
|
5202
5267
|
shapePositions.push({
|
|
5203
5268
|
x: echoX,
|
|
@@ -5244,7 +5309,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5244
5309
|
rotation: innerRot,
|
|
5245
5310
|
proportionType: "GOLDEN_RATIO",
|
|
5246
5311
|
renderStyle: innerStyle,
|
|
5247
|
-
rng: rng
|
|
5312
|
+
rng: rng,
|
|
5313
|
+
activeShapes: activeShapes
|
|
5248
5314
|
});
|
|
5249
5315
|
extrasSpent += $b623126c6e9cbb71$var$RENDER_STYLE_COST[innerStyle] ?? 1;
|
|
5250
5316
|
}
|
|
@@ -5291,7 +5357,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5291
5357
|
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
5292
5358
|
proportionType: "GOLDEN_RATIO",
|
|
5293
5359
|
renderStyle: memberStyle,
|
|
5294
|
-
rng: rng
|
|
5360
|
+
rng: rng,
|
|
5361
|
+
activeShapes: activeShapes
|
|
5295
5362
|
});
|
|
5296
5363
|
shapePositions.push({
|
|
5297
5364
|
x: mx,
|
|
@@ -5345,7 +5412,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5345
5412
|
rotation: rotation + (r + 1) * 12,
|
|
5346
5413
|
proportionType: "GOLDEN_RATIO",
|
|
5347
5414
|
renderStyle: finalRenderStyle,
|
|
5348
|
-
rng: rng
|
|
5415
|
+
rng: rng,
|
|
5416
|
+
activeShapes: activeShapes
|
|
5349
5417
|
});
|
|
5350
5418
|
shapePositions.push({
|
|
5351
5419
|
x: rx,
|
|
@@ -5377,64 +5445,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5377
5445
|
_dt.extraCount = extrasSpent;
|
|
5378
5446
|
}
|
|
5379
5447
|
_mark("5_shape_layers");
|
|
5380
|
-
// ── 5g.
|
|
5381
|
-
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
5382
|
-
// with a tinted background wash, creating a "peek through" effect.
|
|
5383
|
-
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
5384
|
-
const portalCount = 1 + Math.floor(rng() * 2);
|
|
5385
|
-
for(let p = 0; p < portalCount; p++){
|
|
5386
|
-
// Pick a position biased toward placed shapes
|
|
5387
|
-
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5388
|
-
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5389
|
-
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5390
|
-
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
5391
|
-
// Pick a portal shape from the palette
|
|
5392
|
-
const portalShape = (0, $24064302523652b1$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
5393
|
-
const portalRotation = rng() * 360;
|
|
5394
|
-
const portalAlpha = 0.6 + rng() * 0.35;
|
|
5395
|
-
ctx.save();
|
|
5396
|
-
ctx.translate(portalX, portalY);
|
|
5397
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5398
|
-
// Step 1: Clip to the portal shape and fill with background wash
|
|
5399
|
-
ctx.beginPath();
|
|
5400
|
-
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
5401
|
-
ctx.clip();
|
|
5402
|
-
// Fill the clipped region with a radial gradient from background colors
|
|
5403
|
-
const portalColor = (0, $9d614e7d77fc2947$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
5404
|
-
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
5405
|
-
portalGrad.addColorStop(0, portalColor);
|
|
5406
|
-
portalGrad.addColorStop(1, bgEnd);
|
|
5407
|
-
ctx.globalAlpha = portalAlpha;
|
|
5408
|
-
ctx.fillStyle = portalGrad;
|
|
5409
|
-
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
5410
|
-
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
5411
|
-
if (rng() < 0.5) {
|
|
5412
|
-
const dotCount = 3 + Math.floor(rng() * 5);
|
|
5413
|
-
ctx.globalAlpha = portalAlpha * 0.3;
|
|
5414
|
-
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
5415
|
-
for(let d = 0; d < dotCount; d++){
|
|
5416
|
-
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
5417
|
-
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
5418
|
-
const dr = (1 + rng() * 3) * scaleFactor;
|
|
5419
|
-
ctx.beginPath();
|
|
5420
|
-
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
5421
|
-
ctx.fill();
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
|
-
ctx.restore();
|
|
5425
|
-
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
5426
|
-
ctx.save();
|
|
5427
|
-
ctx.translate(portalX, portalY);
|
|
5428
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5429
|
-
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
5430
|
-
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)((0, $9d614e7d77fc2947$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
5431
|
-
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
5432
|
-
ctx.beginPath();
|
|
5433
|
-
(0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
5434
|
-
ctx.stroke();
|
|
5435
|
-
ctx.restore();
|
|
5436
|
-
}
|
|
5437
|
-
}
|
|
5448
|
+
// ── 5g. (Portal/cutout feature removed — replaced by custom shapes API) ──
|
|
5438
5449
|
_mark("5g_portals");
|
|
5439
5450
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5440
5451
|
// Optimized: collect all segments into width-quantized buckets, then
|
|
@@ -6032,6 +6043,8 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
6032
6043
|
}
|
|
6033
6044
|
ctx.globalAlpha = 1;
|
|
6034
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
|
|