git-hash-art 0.3.0 → 0.4.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/ALGORITHM.md +81 -15
- package/CHANGELOG.md +7 -0
- package/dist/browser.js +247 -62
- package/dist/browser.js.map +1 -1
- package/dist/main.js +250 -63
- package/dist/main.js.map +1 -1
- package/dist/module.js +250 -63
- package/dist/module.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/canvas/colors.ts +59 -38
- package/src/lib/canvas/draw.ts +134 -3
- package/src/lib/render.ts +156 -51
package/dist/browser.js
CHANGED
|
@@ -12,14 +12,17 @@ import $4wRzV$colorscheme from "color-scheme";
|
|
|
12
12
|
* identically in Node (@napi-rs/canvas) and browsers.
|
|
13
13
|
*
|
|
14
14
|
* Generation pipeline:
|
|
15
|
-
* 1.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
15
|
+
* 1. Background — radial gradient from hash-derived dark palette
|
|
16
|
+
* 1b. Layered background — large faint shapes / subtle pattern for depth
|
|
17
|
+
* 2. Composition mode — hash selects: radial, flow-field, spiral, grid-subdivision, or clustered
|
|
18
|
+
* 3. Focal points + void zones (negative space)
|
|
19
|
+
* 4. Flow field seed values
|
|
20
|
+
* 5. Shape layers — blend modes, render styles, weighted selection,
|
|
21
|
+
* focal-point placement, atmospheric depth, organic edges
|
|
22
|
+
* 5b. Recursive nesting
|
|
23
|
+
* 6. Flow-line pass — tapered brush-stroke curves
|
|
24
|
+
* 7. Noise texture overlay
|
|
25
|
+
* 8. Organic connecting curves
|
|
23
26
|
*/
|
|
24
27
|
// declare module 'color-scheme';
|
|
25
28
|
|
|
@@ -77,46 +80,61 @@ class $616009579e3d72c5$export$da2372f11bc66b3f {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
// ── Color variation modes ───────────────────────────────────────────
|
|
84
|
+
// The hash deterministically selects a variation, producing dramatically
|
|
85
|
+
// different palettes from the same hue.
|
|
86
|
+
const $b5a262d09b87e373$var$COLOR_VARIATIONS = [
|
|
87
|
+
"soft",
|
|
88
|
+
"hard",
|
|
89
|
+
"pastel",
|
|
90
|
+
"light",
|
|
91
|
+
"dark",
|
|
92
|
+
"default"
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* Pick a color variation mode deterministically from a seed.
|
|
96
|
+
*/ function $b5a262d09b87e373$var$pickVariation(seed) {
|
|
97
|
+
return $b5a262d09b87e373$var$COLOR_VARIATIONS[Math.abs(seed) % $b5a262d09b87e373$var$COLOR_VARIATIONS.length];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Scheme type also varies — some hashes get near-monochromatic palettes,
|
|
101
|
+
* others get high-contrast complementary schemes.
|
|
102
|
+
*/ const $b5a262d09b87e373$var$SCHEME_TYPES = [
|
|
103
|
+
"analogic",
|
|
104
|
+
"mono",
|
|
105
|
+
"contrast",
|
|
106
|
+
"triade",
|
|
107
|
+
"tetrade"
|
|
108
|
+
];
|
|
109
|
+
function $b5a262d09b87e373$var$pickSchemeType(seed) {
|
|
110
|
+
return $b5a262d09b87e373$var$SCHEME_TYPES[Math.abs(seed >> 4) % $b5a262d09b87e373$var$SCHEME_TYPES.length];
|
|
90
111
|
}
|
|
91
112
|
class $b5a262d09b87e373$export$ab958c550f521376 {
|
|
92
113
|
constructor(gitHash){
|
|
93
114
|
this.seed = (0, $616009579e3d72c5$export$39a95c82b20fdf81)(gitHash);
|
|
115
|
+
this.rng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 42));
|
|
116
|
+
// Hash-driven variation and scheme type for palette diversity
|
|
117
|
+
this.variation = $b5a262d09b87e373$var$pickVariation(this.seed);
|
|
118
|
+
this.schemeType = $b5a262d09b87e373$var$pickSchemeType(this.seed);
|
|
94
119
|
this.baseScheme = this.generateBaseScheme();
|
|
95
120
|
this.complementaryScheme = this.generateComplementaryScheme();
|
|
96
121
|
this.triadicScheme = this.generateTriadicScheme();
|
|
97
|
-
this.metallic = this.generateMetallicColors();
|
|
98
122
|
}
|
|
99
123
|
generateBaseScheme() {
|
|
100
124
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
101
|
-
return scheme.from_hue(this.seed % 360).scheme(
|
|
125
|
+
return scheme.from_hue(this.seed % 360).scheme(this.schemeType).variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
102
126
|
}
|
|
103
127
|
generateComplementaryScheme() {
|
|
104
128
|
const complementaryHue = (this.seed + 180) % 360;
|
|
129
|
+
// Complementary uses a contrasting variation for tension
|
|
130
|
+
const compVariation = this.variation === "soft" ? "hard" : this.variation === "dark" ? "light" : this.variation;
|
|
105
131
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
106
|
-
return scheme.from_hue(complementaryHue).scheme("mono").variation(
|
|
132
|
+
return scheme.from_hue(complementaryHue).scheme("mono").variation(compVariation).colors().map((hex)=>`#${hex}`);
|
|
107
133
|
}
|
|
108
134
|
generateTriadicScheme() {
|
|
109
135
|
const triadicHue = (this.seed + 120) % 360;
|
|
110
136
|
const scheme = new (0, $4wRzV$colorscheme)();
|
|
111
|
-
return scheme.from_hue(triadicHue).scheme("triade").variation(
|
|
112
|
-
}
|
|
113
|
-
generateMetallicColors() {
|
|
114
|
-
return {
|
|
115
|
-
gold: "#FFD700",
|
|
116
|
-
silver: "#C0C0C0",
|
|
117
|
-
copper: "#B87333",
|
|
118
|
-
bronze: "#CD7F32"
|
|
119
|
-
};
|
|
137
|
+
return scheme.from_hue(triadicHue).scheme("triade").variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
120
138
|
}
|
|
121
139
|
/**
|
|
122
140
|
* Returns a flat array of hash-derived colors suitable for art generation.
|
|
@@ -174,6 +192,12 @@ function $b5a262d09b87e373$export$59539d800dbe6858(hex, rng, amount = 0.1) {
|
|
|
174
192
|
const jit = ()=>(rng() - 0.5) * 2 * amount * 255;
|
|
175
193
|
return $b5a262d09b87e373$var$rgbToHex(r + jit(), g + jit(), b + jit());
|
|
176
194
|
}
|
|
195
|
+
function $b5a262d09b87e373$export$fb75607d98509d9(hex, amount) {
|
|
196
|
+
const [r, g, b] = $b5a262d09b87e373$var$hexToRgb(hex);
|
|
197
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
198
|
+
const mix = (c)=>c + (gray - c) * amount;
|
|
199
|
+
return $b5a262d09b87e373$var$rgbToHex(mix(r), mix(g), mix(b));
|
|
200
|
+
}
|
|
177
201
|
|
|
178
202
|
|
|
179
203
|
|
|
@@ -961,6 +985,32 @@ const $e41b41d8dcf837ad$export$4ff7fc6f1af248b5 = {
|
|
|
961
985
|
};
|
|
962
986
|
|
|
963
987
|
|
|
988
|
+
const $e0f99502ff383dd8$export$f821c68fe9beaecf = [
|
|
989
|
+
"source-over",
|
|
990
|
+
"screen",
|
|
991
|
+
"multiply",
|
|
992
|
+
"overlay",
|
|
993
|
+
"soft-light",
|
|
994
|
+
"color-dodge",
|
|
995
|
+
"color-burn",
|
|
996
|
+
"lighter"
|
|
997
|
+
];
|
|
998
|
+
function $e0f99502ff383dd8$export$7bb7bff4e26fa06b(rng) {
|
|
999
|
+
if (rng() < 0.4) return "source-over";
|
|
1000
|
+
return $e0f99502ff383dd8$export$f821c68fe9beaecf[1 + Math.floor(rng() * ($e0f99502ff383dd8$export$f821c68fe9beaecf.length - 1))];
|
|
1001
|
+
}
|
|
1002
|
+
const $e0f99502ff383dd8$var$RENDER_STYLES = [
|
|
1003
|
+
"fill-and-stroke",
|
|
1004
|
+
"fill-and-stroke",
|
|
1005
|
+
"fill-only",
|
|
1006
|
+
"stroke-only",
|
|
1007
|
+
"double-stroke",
|
|
1008
|
+
"dashed",
|
|
1009
|
+
"watercolor"
|
|
1010
|
+
];
|
|
1011
|
+
function $e0f99502ff383dd8$export$9fd4e64b2acd410e(rng) {
|
|
1012
|
+
return $e0f99502ff383dd8$var$RENDER_STYLES[Math.floor(rng() * $e0f99502ff383dd8$var$RENDER_STYLES.length)];
|
|
1013
|
+
}
|
|
964
1014
|
function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
965
1015
|
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
966
1016
|
ctx.save();
|
|
@@ -977,8 +1027,71 @@ function $e0f99502ff383dd8$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
977
1027
|
}
|
|
978
1028
|
ctx.restore();
|
|
979
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Apply the chosen render style to the current path.
|
|
1032
|
+
*/ function $e0f99502ff383dd8$var$applyRenderStyle(ctx, style, fillColor, strokeColor, strokeWidth, size, rng) {
|
|
1033
|
+
switch(style){
|
|
1034
|
+
case "fill-only":
|
|
1035
|
+
ctx.fill();
|
|
1036
|
+
break;
|
|
1037
|
+
case "stroke-only":
|
|
1038
|
+
ctx.fill(); // transparent fill to define the path
|
|
1039
|
+
ctx.globalAlpha *= 0.3; // ghost fill
|
|
1040
|
+
ctx.fill();
|
|
1041
|
+
ctx.globalAlpha /= 0.3;
|
|
1042
|
+
ctx.stroke();
|
|
1043
|
+
break;
|
|
1044
|
+
case "double-stroke":
|
|
1045
|
+
ctx.fill();
|
|
1046
|
+
// Outer stroke
|
|
1047
|
+
ctx.lineWidth = strokeWidth * 2;
|
|
1048
|
+
ctx.globalAlpha *= 0.5;
|
|
1049
|
+
ctx.stroke();
|
|
1050
|
+
ctx.globalAlpha /= 0.5;
|
|
1051
|
+
// Inner stroke
|
|
1052
|
+
ctx.lineWidth = strokeWidth * 0.5;
|
|
1053
|
+
ctx.strokeStyle = fillColor;
|
|
1054
|
+
ctx.stroke();
|
|
1055
|
+
break;
|
|
1056
|
+
case "dashed":
|
|
1057
|
+
ctx.fill();
|
|
1058
|
+
ctx.setLineDash([
|
|
1059
|
+
size * 0.05,
|
|
1060
|
+
size * 0.03
|
|
1061
|
+
]);
|
|
1062
|
+
ctx.stroke();
|
|
1063
|
+
ctx.setLineDash([]);
|
|
1064
|
+
break;
|
|
1065
|
+
case "watercolor":
|
|
1066
|
+
{
|
|
1067
|
+
// Draw 3-4 slightly offset passes at low opacity for a bleed effect
|
|
1068
|
+
const passes = 3 + (rng ? Math.floor(rng() * 2) : 0);
|
|
1069
|
+
const savedAlpha = ctx.globalAlpha;
|
|
1070
|
+
ctx.globalAlpha = savedAlpha * (0.3 / passes * 2);
|
|
1071
|
+
for(let p = 0; p < passes; p++){
|
|
1072
|
+
const jx = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1073
|
+
const jy = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1074
|
+
ctx.save();
|
|
1075
|
+
ctx.translate(jx, jy);
|
|
1076
|
+
ctx.fill();
|
|
1077
|
+
ctx.restore();
|
|
1078
|
+
}
|
|
1079
|
+
ctx.globalAlpha = savedAlpha;
|
|
1080
|
+
// Light stroke on top
|
|
1081
|
+
ctx.globalAlpha *= 0.4;
|
|
1082
|
+
ctx.stroke();
|
|
1083
|
+
ctx.globalAlpha /= 0.4;
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
case "fill-and-stroke":
|
|
1087
|
+
default:
|
|
1088
|
+
ctx.fill();
|
|
1089
|
+
ctx.stroke();
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
980
1093
|
function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
981
|
-
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, patterns: patterns = [], proportionType: proportionType = "GOLDEN_RATIO", baseOpacity: baseOpacity = 0.6, opacityReduction: opacityReduction = 0.1, glowRadius: glowRadius = 0, glowColor: glowColor, gradientFillEnd: gradientFillEnd } = config;
|
|
1094
|
+
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation, patterns: patterns = [], proportionType: proportionType = "GOLDEN_RATIO", baseOpacity: baseOpacity = 0.6, opacityReduction: opacityReduction = 0.1, glowRadius: glowRadius = 0, glowColor: glowColor, gradientFillEnd: gradientFillEnd, renderStyle: renderStyle = "fill-and-stroke", rng: rng } = config;
|
|
982
1095
|
ctx.save();
|
|
983
1096
|
ctx.translate(x, y);
|
|
984
1097
|
ctx.rotate(rotation * Math.PI / 180);
|
|
@@ -1001,8 +1114,7 @@ function $e0f99502ff383dd8$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1001
1114
|
const drawFunction = (0, $e41b41d8dcf837ad$export$4ff7fc6f1af248b5)[shape];
|
|
1002
1115
|
if (drawFunction) {
|
|
1003
1116
|
drawFunction(ctx, size);
|
|
1004
|
-
ctx
|
|
1005
|
-
ctx.stroke();
|
|
1117
|
+
$e0f99502ff383dd8$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
1006
1118
|
}
|
|
1007
1119
|
// Reset shadow so patterns aren't double-glowed
|
|
1008
1120
|
if (glowRadius > 0) ctx.shadowBlur = 0;
|
|
@@ -1087,13 +1199,6 @@ function $1f63dc64b5593c73$var$pickShape(rng, layerRatio, shapeNames) {
|
|
|
1087
1199
|
if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
1088
1200
|
return available[Math.floor(rng() * available.length)];
|
|
1089
1201
|
}
|
|
1090
|
-
// ── Helper: simple 2D value noise (hash-seeded) ─────────────────────
|
|
1091
|
-
function $1f63dc64b5593c73$var$valueNoise(x, y, scale, rng) {
|
|
1092
|
-
// Cheap pseudo-noise: combine sin waves at different frequencies
|
|
1093
|
-
const nx = x / scale;
|
|
1094
|
-
const ny = y / scale;
|
|
1095
|
-
return (Math.sin(nx * 1.7 + ny * 2.3 + rng() * 0.001) * 0.5 + Math.sin(nx * 3.1 - ny * 1.9 + rng() * 0.001) * 0.3 + Math.sin(nx * 5.3 + ny * 4.7 + rng() * 0.001) * 0.2) * 0.5 + 0.5;
|
|
1096
|
-
}
|
|
1097
1202
|
// ── Helper: get position based on composition mode ──────────────────
|
|
1098
1203
|
function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
1099
1204
|
switch(mode){
|
|
@@ -1133,10 +1238,8 @@ function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1133
1238
|
}
|
|
1134
1239
|
case "clustered":
|
|
1135
1240
|
{
|
|
1136
|
-
// Pick one of 3-5 cluster centers, then scatter around it
|
|
1137
1241
|
const numClusters = 3 + Math.floor(rng() * 3);
|
|
1138
1242
|
const ci = Math.floor(rng() * numClusters);
|
|
1139
|
-
// Deterministic cluster center from index
|
|
1140
1243
|
const clusterRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(String(ci), 999));
|
|
1141
1244
|
const clx = width * (0.15 + clusterRng() * 0.7);
|
|
1142
1245
|
const cly = height * (0.15 + clusterRng() * 0.7);
|
|
@@ -1148,7 +1251,6 @@ function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1148
1251
|
}
|
|
1149
1252
|
case "flow-field":
|
|
1150
1253
|
default:
|
|
1151
|
-
// Random position, will be adjusted by flow field direction later
|
|
1152
1254
|
return {
|
|
1153
1255
|
x: rng() * width,
|
|
1154
1256
|
y: rng() * height
|
|
@@ -1157,15 +1259,25 @@ function $1f63dc64b5593c73$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1157
1259
|
}
|
|
1158
1260
|
// ── Helper: positional color blending ───────────────────────────────
|
|
1159
1261
|
function $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height, colors, rng) {
|
|
1160
|
-
// Blend between palette colors based on position
|
|
1161
1262
|
const nx = x / width;
|
|
1162
1263
|
const ny = y / height;
|
|
1163
|
-
// Use position to bias which palette color is chosen
|
|
1164
1264
|
const posIndex = (nx * 0.6 + ny * 0.4) * (colors.length - 1);
|
|
1165
1265
|
const baseIdx = Math.floor(posIndex) % colors.length;
|
|
1166
|
-
// Then jitter it slightly
|
|
1167
1266
|
return (0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[baseIdx], rng, 0.08);
|
|
1168
1267
|
}
|
|
1268
|
+
// ── Helper: check if a position is inside a void zone (Feature E) ───
|
|
1269
|
+
function $1f63dc64b5593c73$var$isInVoidZone(x, y, voidZones) {
|
|
1270
|
+
for (const zone of voidZones){
|
|
1271
|
+
if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
|
|
1272
|
+
}
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
// ── Helper: density check for negative space (Feature E) ────────────
|
|
1276
|
+
function $1f63dc64b5593c73$var$localDensity(x, y, positions, radius) {
|
|
1277
|
+
let count = 0;
|
|
1278
|
+
for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
|
|
1279
|
+
return count;
|
|
1280
|
+
}
|
|
1169
1281
|
function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
1170
1282
|
const finalConfig = {
|
|
1171
1283
|
...(0, $81c1b644006d48ec$export$c2f8e0cc249a8d8f),
|
|
@@ -1190,9 +1302,36 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1190
1302
|
bgGrad.addColorStop(1, bgEnd);
|
|
1191
1303
|
ctx.fillStyle = bgGrad;
|
|
1192
1304
|
ctx.fillRect(0, 0, width, height);
|
|
1305
|
+
// ── 1b. Layered background (Feature G) ─────────────────────────
|
|
1306
|
+
// Draw large, very faint shapes to give the background texture
|
|
1307
|
+
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
1308
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
1309
|
+
for(let i = 0; i < bgShapeCount; i++){
|
|
1310
|
+
const bx = rng() * width;
|
|
1311
|
+
const by = rng() * height;
|
|
1312
|
+
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
1313
|
+
const bColor = colors[Math.floor(rng() * colors.length)];
|
|
1314
|
+
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
1315
|
+
ctx.fillStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(bColor, 0.15);
|
|
1316
|
+
ctx.beginPath();
|
|
1317
|
+
ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
1318
|
+
ctx.fill();
|
|
1319
|
+
}
|
|
1320
|
+
// Subtle concentric rings from center
|
|
1321
|
+
const ringCount = 2 + Math.floor(rng() * 3);
|
|
1322
|
+
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
1323
|
+
ctx.strokeStyle = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[0], 0.1);
|
|
1324
|
+
ctx.lineWidth = 1 * scaleFactor;
|
|
1325
|
+
for(let i = 1; i <= ringCount; i++){
|
|
1326
|
+
const r = Math.min(width, height) * 0.15 * i;
|
|
1327
|
+
ctx.beginPath();
|
|
1328
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
1329
|
+
ctx.stroke();
|
|
1330
|
+
}
|
|
1331
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1193
1332
|
// ── 2. Composition mode ────────────────────────────────────────
|
|
1194
1333
|
const compositionMode = $1f63dc64b5593c73$var$COMPOSITION_MODES[Math.floor(rng() * $1f63dc64b5593c73$var$COMPOSITION_MODES.length)];
|
|
1195
|
-
// ── 3. Focal points
|
|
1334
|
+
// ── 3. Focal points + void zones ───────────────────────────────
|
|
1196
1335
|
const numFocal = 1 + Math.floor(rng() * 2);
|
|
1197
1336
|
const focalPoints = [];
|
|
1198
1337
|
for(let f = 0; f < numFocal; f++)focalPoints.push({
|
|
@@ -1200,6 +1339,14 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1200
1339
|
y: height * (0.2 + rng() * 0.6),
|
|
1201
1340
|
strength: 0.3 + rng() * 0.4
|
|
1202
1341
|
});
|
|
1342
|
+
// Feature E: 1-2 void zones where shapes are sparse (negative space)
|
|
1343
|
+
const numVoids = Math.floor(rng() * 2) + 1;
|
|
1344
|
+
const voidZones = [];
|
|
1345
|
+
for(let v = 0; v < numVoids; v++)voidZones.push({
|
|
1346
|
+
x: width * (0.15 + rng() * 0.7),
|
|
1347
|
+
y: height * (0.15 + rng() * 0.7),
|
|
1348
|
+
radius: Math.min(width, height) * (0.06 + rng() * 0.1)
|
|
1349
|
+
});
|
|
1203
1350
|
function applyFocalBias(rx, ry) {
|
|
1204
1351
|
let nearest = focalPoints[0];
|
|
1205
1352
|
let minDist = Infinity;
|
|
@@ -1216,7 +1363,7 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1216
1363
|
ry + (nearest.y - ry) * pull
|
|
1217
1364
|
];
|
|
1218
1365
|
}
|
|
1219
|
-
// ── 4. Flow field seed values
|
|
1366
|
+
// ── 4. Flow field seed values ──────────────────────────────────
|
|
1220
1367
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
1221
1368
|
const fieldFreq = 0.5 + rng() * 2;
|
|
1222
1369
|
function flowAngle(x, y) {
|
|
@@ -1224,15 +1371,32 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1224
1371
|
}
|
|
1225
1372
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
1226
1373
|
const shapePositions = [];
|
|
1374
|
+
const densityCheckRadius = Math.min(width, height) * 0.08;
|
|
1375
|
+
const maxLocalDensity = Math.ceil(finalConfig.shapesPerLayer * 0.15);
|
|
1227
1376
|
for(let layer = 0; layer < layers; layer++){
|
|
1228
1377
|
const layerRatio = layers > 1 ? layer / (layers - 1) : 0;
|
|
1229
1378
|
const numShapes = finalConfig.shapesPerLayer + Math.floor(rng() * finalConfig.shapesPerLayer * 0.3);
|
|
1230
1379
|
const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
|
|
1231
1380
|
const layerSizeScale = 1 - layer * 0.15;
|
|
1381
|
+
// Feature B: per-layer blend mode
|
|
1382
|
+
const layerBlend = (0, $e0f99502ff383dd8$export$7bb7bff4e26fa06b)(rng);
|
|
1383
|
+
ctx.globalCompositeOperation = layerBlend;
|
|
1384
|
+
// Feature C: per-layer render style bias
|
|
1385
|
+
const layerRenderStyle = (0, $e0f99502ff383dd8$export$9fd4e64b2acd410e)(rng);
|
|
1386
|
+
// Feature D: atmospheric desaturation for later layers
|
|
1387
|
+
const atmosphericDesat = layerRatio * 0.3; // 0 for first layer, up to 0.3 for last
|
|
1232
1388
|
for(let i = 0; i < numShapes; i++){
|
|
1233
1389
|
// Position from composition mode, then focal bias
|
|
1234
1390
|
const rawPos = $1f63dc64b5593c73$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
1235
1391
|
const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
|
|
1392
|
+
// Feature E: skip shapes in void zones, reduce in dense areas
|
|
1393
|
+
if ($1f63dc64b5593c73$var$isInVoidZone(x, y, voidZones)) {
|
|
1394
|
+
// 85% chance to skip — allows a few shapes to bleed in
|
|
1395
|
+
if (rng() < 0.85) continue;
|
|
1396
|
+
}
|
|
1397
|
+
if ($1f63dc64b5593c73$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
|
|
1398
|
+
if (rng() < 0.6) continue; // thin out dense areas
|
|
1399
|
+
}
|
|
1236
1400
|
// Weighted shape selection
|
|
1237
1401
|
const shape = $1f63dc64b5593c73$var$pickShape(rng, layerRatio, shapeNames);
|
|
1238
1402
|
// Power distribution for size
|
|
@@ -1241,8 +1405,10 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1241
1405
|
// Flow-field rotation in flow-field mode, random otherwise
|
|
1242
1406
|
const rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
|
|
1243
1407
|
// Positional color blending + jitter
|
|
1244
|
-
|
|
1408
|
+
let fillBase = $1f63dc64b5593c73$var$getPositionalColor(x, y, width, height, colors, rng);
|
|
1245
1409
|
const strokeBase = colors[Math.floor(rng() * colors.length)];
|
|
1410
|
+
// Feature D: desaturate colors on later layers for depth
|
|
1411
|
+
if (atmosphericDesat > 0) fillBase = (0, $b5a262d09b87e373$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
1246
1412
|
const fillColor = (0, $b5a262d09b87e373$export$59539d800dbe6858)(fillBase, rng, 0.06);
|
|
1247
1413
|
const strokeColor = (0, $b5a262d09b87e373$export$59539d800dbe6858)(strokeBase, rng, 0.05);
|
|
1248
1414
|
// Semi-transparent fill
|
|
@@ -1258,6 +1424,11 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1258
1424
|
// Gradient fill on ~30%
|
|
1259
1425
|
const hasGradient = rng() < 0.3;
|
|
1260
1426
|
const gradientEnd = hasGradient ? (0, $b5a262d09b87e373$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1) : undefined;
|
|
1427
|
+
// Feature C: per-shape render style (70% use layer style, 30% pick their own)
|
|
1428
|
+
const shapeRenderStyle = rng() < 0.7 ? layerRenderStyle : (0, $e0f99502ff383dd8$export$9fd4e64b2acd410e)(rng);
|
|
1429
|
+
// Feature F: organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
1430
|
+
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
1431
|
+
const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
1261
1432
|
(0, $e0f99502ff383dd8$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
|
|
1262
1433
|
fillColor: transparentFill,
|
|
1263
1434
|
strokeColor: strokeColor,
|
|
@@ -1267,14 +1438,16 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1267
1438
|
proportionType: "GOLDEN_RATIO",
|
|
1268
1439
|
glowRadius: glowRadius,
|
|
1269
1440
|
glowColor: hasGlow ? (0, $b5a262d09b87e373$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
|
|
1270
|
-
gradientFillEnd: gradientEnd
|
|
1441
|
+
gradientFillEnd: gradientEnd,
|
|
1442
|
+
renderStyle: finalRenderStyle,
|
|
1443
|
+
rng: rng
|
|
1271
1444
|
});
|
|
1272
1445
|
shapePositions.push({
|
|
1273
1446
|
x: x,
|
|
1274
1447
|
y: y,
|
|
1275
1448
|
size: size
|
|
1276
1449
|
});
|
|
1277
|
-
// ── 5b. Recursive nesting
|
|
1450
|
+
// ── 5b. Recursive nesting ──────────────────────────────────
|
|
1278
1451
|
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
1279
1452
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
1280
1453
|
for(let n = 0; n < innerCount; n++){
|
|
@@ -1291,37 +1464,49 @@ function $1f63dc64b5593c73$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1291
1464
|
strokeWidth: strokeWidth * 0.6,
|
|
1292
1465
|
size: innerSize,
|
|
1293
1466
|
rotation: innerRot,
|
|
1294
|
-
proportionType: "GOLDEN_RATIO"
|
|
1467
|
+
proportionType: "GOLDEN_RATIO",
|
|
1468
|
+
renderStyle: shapeRenderStyle,
|
|
1469
|
+
rng: rng
|
|
1295
1470
|
});
|
|
1296
1471
|
}
|
|
1297
1472
|
}
|
|
1298
1473
|
}
|
|
1299
1474
|
}
|
|
1300
|
-
//
|
|
1301
|
-
|
|
1475
|
+
// Reset blend mode for post-processing passes
|
|
1476
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1477
|
+
// ── 6. Flow-line pass (Feature H: tapered brush strokes) ───────
|
|
1302
1478
|
const numFlowLines = 6 + Math.floor(rng() * 10);
|
|
1303
1479
|
for(let i = 0; i < numFlowLines; i++){
|
|
1304
1480
|
let fx = rng() * width;
|
|
1305
1481
|
let fy = rng() * height;
|
|
1306
1482
|
const steps = 30 + Math.floor(rng() * 40);
|
|
1307
1483
|
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1484
|
+
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
1485
|
+
const lineColor = (0, $b5a262d09b87e373$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.4);
|
|
1486
|
+
const lineAlpha = 0.06 + rng() * 0.1;
|
|
1487
|
+
// Draw as individual segments with tapering width
|
|
1488
|
+
let prevX = fx;
|
|
1489
|
+
let prevY = fy;
|
|
1313
1490
|
for(let s = 0; s < steps; s++){
|
|
1314
1491
|
const angle = flowAngle(fx, fy) + (rng() - 0.5) * 0.3;
|
|
1315
1492
|
fx += Math.cos(angle) * stepLen;
|
|
1316
1493
|
fy += Math.sin(angle) * stepLen;
|
|
1317
|
-
// Stay in bounds
|
|
1318
1494
|
if (fx < 0 || fx > width || fy < 0 || fy > height) break;
|
|
1495
|
+
// Taper: thick at start, thin at end
|
|
1496
|
+
const taper = 1 - s / steps * 0.8;
|
|
1497
|
+
ctx.globalAlpha = lineAlpha * taper;
|
|
1498
|
+
ctx.strokeStyle = lineColor;
|
|
1499
|
+
ctx.lineWidth = startWidth * taper;
|
|
1500
|
+
ctx.lineCap = "round";
|
|
1501
|
+
ctx.beginPath();
|
|
1502
|
+
ctx.moveTo(prevX, prevY);
|
|
1319
1503
|
ctx.lineTo(fx, fy);
|
|
1504
|
+
ctx.stroke();
|
|
1505
|
+
prevX = fx;
|
|
1506
|
+
prevY = fy;
|
|
1320
1507
|
}
|
|
1321
|
-
ctx.stroke();
|
|
1322
1508
|
}
|
|
1323
1509
|
// ── 7. Noise texture overlay ───────────────────────────────────
|
|
1324
|
-
// Subtle grain rendered as tiny semi-transparent dots
|
|
1325
1510
|
const noiseRng = (0, $616009579e3d72c5$export$eaf9227667332084)((0, $616009579e3d72c5$export$e9cc707de01b7042)(gitHash, 777));
|
|
1326
1511
|
const noiseDensity = Math.floor(width * height / 800);
|
|
1327
1512
|
for(let i = 0; i < noiseDensity; i++){
|