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/main.js
CHANGED
|
@@ -2095,14 +2095,15 @@ function $c3de8257a8baa3b0$export$909ab0580e273f19(style) {
|
|
|
2095
2095
|
}
|
|
2096
2096
|
}
|
|
2097
2097
|
function $c3de8257a8baa3b0$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
2098
|
-
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
2098
|
+
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, activeShapes: activeShapes } = config;
|
|
2099
2099
|
ctx.save();
|
|
2100
2100
|
ctx.translate(x, y);
|
|
2101
2101
|
ctx.rotate(rotation * Math.PI / 180);
|
|
2102
2102
|
ctx.fillStyle = fillColor;
|
|
2103
2103
|
ctx.strokeStyle = strokeColor;
|
|
2104
2104
|
ctx.lineWidth = strokeWidth;
|
|
2105
|
-
const
|
|
2105
|
+
const registry = activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5);
|
|
2106
|
+
const drawFunction = registry[shape];
|
|
2106
2107
|
if (drawFunction) {
|
|
2107
2108
|
drawFunction(ctx, size);
|
|
2108
2109
|
ctx.fill();
|
|
@@ -2612,7 +2613,8 @@ function $c3de8257a8baa3b0$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
2612
2613
|
} else ctx.fillStyle = fillColor;
|
|
2613
2614
|
ctx.strokeStyle = strokeColor;
|
|
2614
2615
|
ctx.lineWidth = strokeWidth;
|
|
2615
|
-
const
|
|
2616
|
+
const registry = config.activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5);
|
|
2617
|
+
const drawFunction = registry[shape];
|
|
2616
2618
|
if (drawFunction) {
|
|
2617
2619
|
drawFunction(ctx, size, {
|
|
2618
2620
|
rng: rng
|
|
@@ -3476,6 +3478,26 @@ const $e73976f898150d4d$export$4343b39fe47bd82c = {
|
|
|
3476
3478
|
]
|
|
3477
3479
|
}
|
|
3478
3480
|
};
|
|
3481
|
+
function $e73976f898150d4d$export$90912290d628650f(name, partial) {
|
|
3482
|
+
$e73976f898150d4d$export$4343b39fe47bd82c[name] = {
|
|
3483
|
+
tier: partial?.tier ?? 2,
|
|
3484
|
+
minSizeFraction: partial?.minSizeFraction ?? 0.05,
|
|
3485
|
+
maxSizeFraction: partial?.maxSizeFraction ?? 1.0,
|
|
3486
|
+
affinities: partial?.affinities ?? [
|
|
3487
|
+
"circle",
|
|
3488
|
+
"square"
|
|
3489
|
+
],
|
|
3490
|
+
category: "procedural",
|
|
3491
|
+
heroCandidate: partial?.heroCandidate ?? false,
|
|
3492
|
+
bestStyles: partial?.bestStyles ?? [
|
|
3493
|
+
"fill-and-stroke",
|
|
3494
|
+
"watercolor"
|
|
3495
|
+
]
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
function $e73976f898150d4d$export$f4ca68bd046f15ae(name) {
|
|
3499
|
+
delete $e73976f898150d4d$export$4343b39fe47bd82c[name];
|
|
3500
|
+
}
|
|
3479
3501
|
function $e73976f898150d4d$export$4a95df8944b5033b(rng, shapeNames, archetypeName) {
|
|
3480
3502
|
const available = shapeNames.filter((s)=>$e73976f898150d4d$export$4343b39fe47bd82c[s]);
|
|
3481
3503
|
// Pick a seed shape — tier 1 shapes that are hero candidates
|
|
@@ -3665,7 +3687,14 @@ function $e73976f898150d4d$export$ab873bb6fb56c1a8(shapeName, layerStyle, rng) {
|
|
|
3665
3687
|
|
|
3666
3688
|
|
|
3667
3689
|
/**
|
|
3668
|
-
*
|
|
3690
|
+
* Draw function signature for custom shapes.
|
|
3691
|
+
* The function should build a canvas path (moveTo/lineTo/arc/etc.)
|
|
3692
|
+
* centered at the origin. The pipeline handles translate, rotate,
|
|
3693
|
+
* fill, and stroke — your function just defines the geometry.
|
|
3694
|
+
*
|
|
3695
|
+
* @param ctx - Canvas 2D rendering context (already translated to shape center)
|
|
3696
|
+
* @param size - Bounding size in pixels
|
|
3697
|
+
* @param rng - Deterministic RNG seeded from the git hash — use this instead of Math.random()
|
|
3669
3698
|
*/ const $93cf69256c93baa9$export$c2f8e0cc249a8d8f = {
|
|
3670
3699
|
width: 2048,
|
|
3671
3700
|
height: 2048,
|
|
@@ -4636,7 +4665,39 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4636
4665
|
// ── 0b. Color hierarchy — dominant/secondary/accent weighting ──
|
|
4637
4666
|
const colorHierarchy = (0, $d016ad53434219a1$export$fabac4600b87056)(colors, rng);
|
|
4638
4667
|
// ── 0c. Shape palette — curated shapes that work well together ──
|
|
4639
|
-
|
|
4668
|
+
// Merge custom shapes into a combined registry
|
|
4669
|
+
const customShapeNames = [];
|
|
4670
|
+
let activeShapes;
|
|
4671
|
+
if (finalConfig.customShapes && Object.keys(finalConfig.customShapes).length > 0) {
|
|
4672
|
+
activeShapes = {
|
|
4673
|
+
...(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)
|
|
4674
|
+
};
|
|
4675
|
+
for (const [name, def] of Object.entries(finalConfig.customShapes)){
|
|
4676
|
+
// Wrap CustomDrawFunction (ctx, size, rng) into DrawFunction (ctx, size, config?)
|
|
4677
|
+
const customDraw = def.draw;
|
|
4678
|
+
activeShapes[name] = (ctx, size, config)=>{
|
|
4679
|
+
customDraw(ctx, size, config?.rng ?? Math.random);
|
|
4680
|
+
};
|
|
4681
|
+
// Register profile for affinity system (inlined to avoid ESM interop issues)
|
|
4682
|
+
(0, $e73976f898150d4d$export$4343b39fe47bd82c)[name] = {
|
|
4683
|
+
tier: def.profile?.tier ?? 2,
|
|
4684
|
+
minSizeFraction: def.profile?.minSizeFraction ?? 0.05,
|
|
4685
|
+
maxSizeFraction: def.profile?.maxSizeFraction ?? 1.0,
|
|
4686
|
+
affinities: def.profile?.affinities ?? [
|
|
4687
|
+
"circle",
|
|
4688
|
+
"square"
|
|
4689
|
+
],
|
|
4690
|
+
category: "procedural",
|
|
4691
|
+
heroCandidate: def.profile?.heroCandidate ?? false,
|
|
4692
|
+
bestStyles: def.profile?.bestStyles ?? [
|
|
4693
|
+
"fill-and-stroke",
|
|
4694
|
+
"watercolor"
|
|
4695
|
+
]
|
|
4696
|
+
};
|
|
4697
|
+
customShapeNames.push(name);
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
const shapeNames = Object.keys(activeShapes ?? (0, $9c828bde2acaae64$export$4ff7fc6f1af248b5));
|
|
4640
4701
|
const shapePalette = (0, $e73976f898150d4d$export$4a95df8944b5033b)(rng, shapeNames, archetype.name);
|
|
4641
4702
|
// ── 0d. Color grading — unified tone for the whole image ───────
|
|
4642
4703
|
const colorGrade = (0, $d016ad53434219a1$export$6d1620b367f86f7a)(rng);
|
|
@@ -4937,7 +4998,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
4937
4998
|
renderStyle: heroStyle,
|
|
4938
4999
|
rng: rng,
|
|
4939
5000
|
lightAngle: lightAngle,
|
|
4940
|
-
scaleFactor: scaleFactor
|
|
5001
|
+
scaleFactor: scaleFactor,
|
|
5002
|
+
activeShapes: activeShapes
|
|
4941
5003
|
});
|
|
4942
5004
|
heroCenter = {
|
|
4943
5005
|
x: heroFocal.x,
|
|
@@ -5140,7 +5202,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5140
5202
|
renderStyle: finalRenderStyle,
|
|
5141
5203
|
rng: rng,
|
|
5142
5204
|
lightAngle: lightAngle,
|
|
5143
|
-
scaleFactor: scaleFactor
|
|
5205
|
+
scaleFactor: scaleFactor,
|
|
5206
|
+
activeShapes: activeShapes
|
|
5144
5207
|
};
|
|
5145
5208
|
if (shouldMirror) {
|
|
5146
5209
|
(0, $c3de8257a8baa3b0$export$8bd8bbd1a8e53689)(ctx, shape, finalX, finalY, {
|
|
@@ -5171,7 +5234,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5171
5234
|
rotation: rotation,
|
|
5172
5235
|
proportionType: "GOLDEN_RATIO",
|
|
5173
5236
|
renderStyle: "fill-only",
|
|
5174
|
-
rng: rng
|
|
5237
|
+
rng: rng,
|
|
5238
|
+
activeShapes: activeShapes
|
|
5175
5239
|
});
|
|
5176
5240
|
}
|
|
5177
5241
|
extrasSpent += glazePasses;
|
|
@@ -5211,7 +5275,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5211
5275
|
rotation: rotation + (e + 1) * 15,
|
|
5212
5276
|
proportionType: "GOLDEN_RATIO",
|
|
5213
5277
|
renderStyle: finalRenderStyle,
|
|
5214
|
-
rng: rng
|
|
5278
|
+
rng: rng,
|
|
5279
|
+
activeShapes: activeShapes
|
|
5215
5280
|
});
|
|
5216
5281
|
shapePositions.push({
|
|
5217
5282
|
x: echoX,
|
|
@@ -5258,7 +5323,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5258
5323
|
rotation: innerRot,
|
|
5259
5324
|
proportionType: "GOLDEN_RATIO",
|
|
5260
5325
|
renderStyle: innerStyle,
|
|
5261
|
-
rng: rng
|
|
5326
|
+
rng: rng,
|
|
5327
|
+
activeShapes: activeShapes
|
|
5262
5328
|
});
|
|
5263
5329
|
extrasSpent += $4f72c5a314eddf25$var$RENDER_STYLE_COST[innerStyle] ?? 1;
|
|
5264
5330
|
}
|
|
@@ -5305,7 +5371,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5305
5371
|
rotation: member.rotation + groupRotation * 180 / Math.PI,
|
|
5306
5372
|
proportionType: "GOLDEN_RATIO",
|
|
5307
5373
|
renderStyle: memberStyle,
|
|
5308
|
-
rng: rng
|
|
5374
|
+
rng: rng,
|
|
5375
|
+
activeShapes: activeShapes
|
|
5309
5376
|
});
|
|
5310
5377
|
shapePositions.push({
|
|
5311
5378
|
x: mx,
|
|
@@ -5359,7 +5426,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5359
5426
|
rotation: rotation + (r + 1) * 12,
|
|
5360
5427
|
proportionType: "GOLDEN_RATIO",
|
|
5361
5428
|
renderStyle: finalRenderStyle,
|
|
5362
|
-
rng: rng
|
|
5429
|
+
rng: rng,
|
|
5430
|
+
activeShapes: activeShapes
|
|
5363
5431
|
});
|
|
5364
5432
|
shapePositions.push({
|
|
5365
5433
|
x: rx,
|
|
@@ -5391,64 +5459,7 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
5391
5459
|
_dt.extraCount = extrasSpent;
|
|
5392
5460
|
}
|
|
5393
5461
|
_mark("5_shape_layers");
|
|
5394
|
-
// ── 5g.
|
|
5395
|
-
// ~18% of images get 1-3 portal windows that paint over foreground
|
|
5396
|
-
// with a tinted background wash, creating a "peek through" effect.
|
|
5397
|
-
if (rng() < 0.18 && shapePositions.length > 3) {
|
|
5398
|
-
const portalCount = 1 + Math.floor(rng() * 2);
|
|
5399
|
-
for(let p = 0; p < portalCount; p++){
|
|
5400
|
-
// Pick a position biased toward placed shapes
|
|
5401
|
-
const sourceShape = shapePositions[Math.floor(rng() * shapePositions.length)];
|
|
5402
|
-
const portalX = sourceShape.x + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5403
|
-
const portalY = sourceShape.y + (rng() - 0.5) * sourceShape.size * 0.5;
|
|
5404
|
-
const portalSize = adjustedMaxSize * (0.15 + rng() * 0.25);
|
|
5405
|
-
// Pick a portal shape from the palette
|
|
5406
|
-
const portalShape = (0, $e73976f898150d4d$export$3c37d9a045754d0e)(shapePalette, rng, portalSize / adjustedMaxSize);
|
|
5407
|
-
const portalRotation = rng() * 360;
|
|
5408
|
-
const portalAlpha = 0.6 + rng() * 0.35;
|
|
5409
|
-
ctx.save();
|
|
5410
|
-
ctx.translate(portalX, portalY);
|
|
5411
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5412
|
-
// Step 1: Clip to the portal shape and fill with background wash
|
|
5413
|
-
ctx.beginPath();
|
|
5414
|
-
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize);
|
|
5415
|
-
ctx.clip();
|
|
5416
|
-
// Fill the clipped region with a radial gradient from background colors
|
|
5417
|
-
const portalColor = (0, $d016ad53434219a1$export$18a34c25ea7e724b)(bgStart, rng, 15, 0.1);
|
|
5418
|
-
const portalGrad = ctx.createRadialGradient(0, 0, 0, 0, 0, portalSize);
|
|
5419
|
-
portalGrad.addColorStop(0, portalColor);
|
|
5420
|
-
portalGrad.addColorStop(1, bgEnd);
|
|
5421
|
-
ctx.globalAlpha = portalAlpha;
|
|
5422
|
-
ctx.fillStyle = portalGrad;
|
|
5423
|
-
ctx.fillRect(-portalSize, -portalSize, portalSize * 2, portalSize * 2);
|
|
5424
|
-
// Optional: subtle inner texture — a few tiny dots inside the portal
|
|
5425
|
-
if (rng() < 0.5) {
|
|
5426
|
-
const dotCount = 3 + Math.floor(rng() * 5);
|
|
5427
|
-
ctx.globalAlpha = portalAlpha * 0.3;
|
|
5428
|
-
ctx.fillStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.2);
|
|
5429
|
-
for(let d = 0; d < dotCount; d++){
|
|
5430
|
-
const dx = (rng() - 0.5) * portalSize * 1.4;
|
|
5431
|
-
const dy = (rng() - 0.5) * portalSize * 1.4;
|
|
5432
|
-
const dr = (1 + rng() * 3) * scaleFactor;
|
|
5433
|
-
ctx.beginPath();
|
|
5434
|
-
ctx.arc(dx, dy, dr, 0, Math.PI * 2);
|
|
5435
|
-
ctx.fill();
|
|
5436
|
-
}
|
|
5437
|
-
}
|
|
5438
|
-
ctx.restore();
|
|
5439
|
-
// Step 2: Draw a border ring around the portal (outside the clip)
|
|
5440
|
-
ctx.save();
|
|
5441
|
-
ctx.translate(portalX, portalY);
|
|
5442
|
-
ctx.rotate(portalRotation * Math.PI / 180);
|
|
5443
|
-
ctx.globalAlpha = 0.15 + rng() * 0.2;
|
|
5444
|
-
ctx.strokeStyle = (0, $d016ad53434219a1$export$f2121afcad3d553f)((0, $d016ad53434219a1$export$b49f62f0a99da0e8)(colorHierarchy, rng), 0.5);
|
|
5445
|
-
ctx.lineWidth = (1.5 + rng() * 2.5) * scaleFactor;
|
|
5446
|
-
ctx.beginPath();
|
|
5447
|
-
(0, $9c828bde2acaae64$export$4ff7fc6f1af248b5)[portalShape]?.(ctx, portalSize * 1.06);
|
|
5448
|
-
ctx.stroke();
|
|
5449
|
-
ctx.restore();
|
|
5450
|
-
}
|
|
5451
|
-
}
|
|
5462
|
+
// ── 5g. (Portal/cutout feature removed — replaced by custom shapes API) ──
|
|
5452
5463
|
_mark("5g_portals");
|
|
5453
5464
|
// ── 6. Flow-line pass — variable color, branching, pressure ────
|
|
5454
5465
|
// Optimized: collect all segments into width-quantized buckets, then
|
|
@@ -6046,6 +6057,8 @@ function $4f72c5a314eddf25$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
6046
6057
|
}
|
|
6047
6058
|
ctx.globalAlpha = 1;
|
|
6048
6059
|
_mark("11_signature");
|
|
6060
|
+
// Clean up custom shape profiles to avoid leaking into subsequent renders
|
|
6061
|
+
for (const name of customShapeNames)delete (0, $e73976f898150d4d$export$4343b39fe47bd82c)[name];
|
|
6049
6062
|
}
|
|
6050
6063
|
|
|
6051
6064
|
|