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/module.js
CHANGED
|
@@ -13,14 +13,17 @@ import $4RUNL$colorscheme from "color-scheme";
|
|
|
13
13
|
* identically in Node (@napi-rs/canvas) and browsers.
|
|
14
14
|
*
|
|
15
15
|
* Generation pipeline:
|
|
16
|
-
* 1.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* 1. Background — radial gradient from hash-derived dark palette
|
|
17
|
+
* 1b. Layered background — large faint shapes / subtle pattern for depth
|
|
18
|
+
* 2. Composition mode — hash selects: radial, flow-field, spiral, grid-subdivision, or clustered
|
|
19
|
+
* 3. Focal points + void zones (negative space)
|
|
20
|
+
* 4. Flow field seed values
|
|
21
|
+
* 5. Shape layers — blend modes, render styles, weighted selection,
|
|
22
|
+
* focal-point placement, atmospheric depth, organic edges
|
|
23
|
+
* 5b. Recursive nesting
|
|
24
|
+
* 6. Flow-line pass — tapered brush-stroke curves
|
|
25
|
+
* 7. Noise texture overlay
|
|
26
|
+
* 8. Organic connecting curves
|
|
24
27
|
*/
|
|
25
28
|
// declare module 'color-scheme';
|
|
26
29
|
|
|
@@ -78,51 +81,68 @@ class $461134e0b6ce0619$export$da2372f11bc66b3f {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
// ── Color variation modes ───────────────────────────────────────────
|
|
85
|
+
// The hash deterministically selects a variation, producing dramatically
|
|
86
|
+
// different palettes from the same hue.
|
|
87
|
+
const $9d614e7d77fc2947$var$COLOR_VARIATIONS = [
|
|
88
|
+
"soft",
|
|
89
|
+
"hard",
|
|
90
|
+
"pastel",
|
|
91
|
+
"light",
|
|
92
|
+
"dark",
|
|
93
|
+
"default"
|
|
94
|
+
];
|
|
95
|
+
/**
|
|
96
|
+
* Pick a color variation mode deterministically from a seed.
|
|
97
|
+
*/ function $9d614e7d77fc2947$var$pickVariation(seed) {
|
|
98
|
+
return $9d614e7d77fc2947$var$COLOR_VARIATIONS[Math.abs(seed) % $9d614e7d77fc2947$var$COLOR_VARIATIONS.length];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Scheme type also varies — some hashes get near-monochromatic palettes,
|
|
102
|
+
* others get high-contrast complementary schemes.
|
|
103
|
+
*/ const $9d614e7d77fc2947$var$SCHEME_TYPES = [
|
|
104
|
+
"analogic",
|
|
105
|
+
"mono",
|
|
106
|
+
"contrast",
|
|
107
|
+
"triade",
|
|
108
|
+
"tetrade"
|
|
109
|
+
];
|
|
110
|
+
function $9d614e7d77fc2947$var$pickSchemeType(seed) {
|
|
111
|
+
return $9d614e7d77fc2947$var$SCHEME_TYPES[Math.abs(seed >> 4) % $9d614e7d77fc2947$var$SCHEME_TYPES.length];
|
|
91
112
|
}
|
|
92
113
|
class $9d614e7d77fc2947$export$ab958c550f521376 {
|
|
93
114
|
seed;
|
|
115
|
+
rng;
|
|
116
|
+
variation;
|
|
117
|
+
schemeType;
|
|
94
118
|
baseScheme;
|
|
95
119
|
complementaryScheme;
|
|
96
120
|
triadicScheme;
|
|
97
|
-
metallic;
|
|
98
121
|
constructor(gitHash){
|
|
99
122
|
this.seed = (0, $461134e0b6ce0619$export$39a95c82b20fdf81)(gitHash);
|
|
123
|
+
this.rng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 42));
|
|
124
|
+
// Hash-driven variation and scheme type for palette diversity
|
|
125
|
+
this.variation = $9d614e7d77fc2947$var$pickVariation(this.seed);
|
|
126
|
+
this.schemeType = $9d614e7d77fc2947$var$pickSchemeType(this.seed);
|
|
100
127
|
this.baseScheme = this.generateBaseScheme();
|
|
101
128
|
this.complementaryScheme = this.generateComplementaryScheme();
|
|
102
129
|
this.triadicScheme = this.generateTriadicScheme();
|
|
103
|
-
this.metallic = this.generateMetallicColors();
|
|
104
130
|
}
|
|
105
131
|
generateBaseScheme() {
|
|
106
132
|
const scheme = new (0, $4RUNL$colorscheme)();
|
|
107
|
-
return scheme.from_hue(this.seed % 360).scheme(
|
|
133
|
+
return scheme.from_hue(this.seed % 360).scheme(this.schemeType).variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
108
134
|
}
|
|
109
135
|
generateComplementaryScheme() {
|
|
110
136
|
const complementaryHue = (this.seed + 180) % 360;
|
|
137
|
+
// Complementary uses a contrasting variation for tension
|
|
138
|
+
const compVariation = this.variation === "soft" ? "hard" : this.variation === "dark" ? "light" : this.variation;
|
|
111
139
|
const scheme = new (0, $4RUNL$colorscheme)();
|
|
112
|
-
return scheme.from_hue(complementaryHue).scheme("mono").variation(
|
|
140
|
+
return scheme.from_hue(complementaryHue).scheme("mono").variation(compVariation).colors().map((hex)=>`#${hex}`);
|
|
113
141
|
}
|
|
114
142
|
generateTriadicScheme() {
|
|
115
143
|
const triadicHue = (this.seed + 120) % 360;
|
|
116
144
|
const scheme = new (0, $4RUNL$colorscheme)();
|
|
117
|
-
return scheme.from_hue(triadicHue).scheme("triade").variation(
|
|
118
|
-
}
|
|
119
|
-
generateMetallicColors() {
|
|
120
|
-
return {
|
|
121
|
-
gold: "#FFD700",
|
|
122
|
-
silver: "#C0C0C0",
|
|
123
|
-
copper: "#B87333",
|
|
124
|
-
bronze: "#CD7F32"
|
|
125
|
-
};
|
|
145
|
+
return scheme.from_hue(triadicHue).scheme("triade").variation(this.variation).colors().map((hex)=>`#${hex}`);
|
|
126
146
|
}
|
|
127
147
|
/**
|
|
128
148
|
* Returns a flat array of hash-derived colors suitable for art generation.
|
|
@@ -180,6 +200,12 @@ function $9d614e7d77fc2947$export$59539d800dbe6858(hex, rng, amount = 0.1) {
|
|
|
180
200
|
const jit = ()=>(rng() - 0.5) * 2 * amount * 255;
|
|
181
201
|
return $9d614e7d77fc2947$var$rgbToHex(r + jit(), g + jit(), b + jit());
|
|
182
202
|
}
|
|
203
|
+
function $9d614e7d77fc2947$export$fb75607d98509d9(hex, amount) {
|
|
204
|
+
const [r, g, b] = $9d614e7d77fc2947$var$hexToRgb(hex);
|
|
205
|
+
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
206
|
+
const mix = (c)=>c + (gray - c) * amount;
|
|
207
|
+
return $9d614e7d77fc2947$var$rgbToHex(mix(r), mix(g), mix(b));
|
|
208
|
+
}
|
|
183
209
|
|
|
184
210
|
|
|
185
211
|
|
|
@@ -967,6 +993,32 @@ const $701ba7c7229ef06d$export$4ff7fc6f1af248b5 = {
|
|
|
967
993
|
};
|
|
968
994
|
|
|
969
995
|
|
|
996
|
+
const $9beb8f41637c29fd$export$f821c68fe9beaecf = [
|
|
997
|
+
"source-over",
|
|
998
|
+
"screen",
|
|
999
|
+
"multiply",
|
|
1000
|
+
"overlay",
|
|
1001
|
+
"soft-light",
|
|
1002
|
+
"color-dodge",
|
|
1003
|
+
"color-burn",
|
|
1004
|
+
"lighter"
|
|
1005
|
+
];
|
|
1006
|
+
function $9beb8f41637c29fd$export$7bb7bff4e26fa06b(rng) {
|
|
1007
|
+
if (rng() < 0.4) return "source-over";
|
|
1008
|
+
return $9beb8f41637c29fd$export$f821c68fe9beaecf[1 + Math.floor(rng() * ($9beb8f41637c29fd$export$f821c68fe9beaecf.length - 1))];
|
|
1009
|
+
}
|
|
1010
|
+
const $9beb8f41637c29fd$var$RENDER_STYLES = [
|
|
1011
|
+
"fill-and-stroke",
|
|
1012
|
+
"fill-and-stroke",
|
|
1013
|
+
"fill-only",
|
|
1014
|
+
"stroke-only",
|
|
1015
|
+
"double-stroke",
|
|
1016
|
+
"dashed",
|
|
1017
|
+
"watercolor"
|
|
1018
|
+
];
|
|
1019
|
+
function $9beb8f41637c29fd$export$9fd4e64b2acd410e(rng) {
|
|
1020
|
+
return $9beb8f41637c29fd$var$RENDER_STYLES[Math.floor(rng() * $9beb8f41637c29fd$var$RENDER_STYLES.length)];
|
|
1021
|
+
}
|
|
970
1022
|
function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
971
1023
|
const { fillColor: fillColor, strokeColor: strokeColor, strokeWidth: strokeWidth, size: size, rotation: rotation } = config;
|
|
972
1024
|
ctx.save();
|
|
@@ -983,8 +1035,71 @@ function $9beb8f41637c29fd$export$71b514a25c47df50(ctx, shape, x, y, config) {
|
|
|
983
1035
|
}
|
|
984
1036
|
ctx.restore();
|
|
985
1037
|
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Apply the chosen render style to the current path.
|
|
1040
|
+
*/ function $9beb8f41637c29fd$var$applyRenderStyle(ctx, style, fillColor, strokeColor, strokeWidth, size, rng) {
|
|
1041
|
+
switch(style){
|
|
1042
|
+
case "fill-only":
|
|
1043
|
+
ctx.fill();
|
|
1044
|
+
break;
|
|
1045
|
+
case "stroke-only":
|
|
1046
|
+
ctx.fill(); // transparent fill to define the path
|
|
1047
|
+
ctx.globalAlpha *= 0.3; // ghost fill
|
|
1048
|
+
ctx.fill();
|
|
1049
|
+
ctx.globalAlpha /= 0.3;
|
|
1050
|
+
ctx.stroke();
|
|
1051
|
+
break;
|
|
1052
|
+
case "double-stroke":
|
|
1053
|
+
ctx.fill();
|
|
1054
|
+
// Outer stroke
|
|
1055
|
+
ctx.lineWidth = strokeWidth * 2;
|
|
1056
|
+
ctx.globalAlpha *= 0.5;
|
|
1057
|
+
ctx.stroke();
|
|
1058
|
+
ctx.globalAlpha /= 0.5;
|
|
1059
|
+
// Inner stroke
|
|
1060
|
+
ctx.lineWidth = strokeWidth * 0.5;
|
|
1061
|
+
ctx.strokeStyle = fillColor;
|
|
1062
|
+
ctx.stroke();
|
|
1063
|
+
break;
|
|
1064
|
+
case "dashed":
|
|
1065
|
+
ctx.fill();
|
|
1066
|
+
ctx.setLineDash([
|
|
1067
|
+
size * 0.05,
|
|
1068
|
+
size * 0.03
|
|
1069
|
+
]);
|
|
1070
|
+
ctx.stroke();
|
|
1071
|
+
ctx.setLineDash([]);
|
|
1072
|
+
break;
|
|
1073
|
+
case "watercolor":
|
|
1074
|
+
{
|
|
1075
|
+
// Draw 3-4 slightly offset passes at low opacity for a bleed effect
|
|
1076
|
+
const passes = 3 + (rng ? Math.floor(rng() * 2) : 0);
|
|
1077
|
+
const savedAlpha = ctx.globalAlpha;
|
|
1078
|
+
ctx.globalAlpha = savedAlpha * (0.3 / passes * 2);
|
|
1079
|
+
for(let p = 0; p < passes; p++){
|
|
1080
|
+
const jx = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1081
|
+
const jy = rng ? (rng() - 0.5) * size * 0.06 : 0;
|
|
1082
|
+
ctx.save();
|
|
1083
|
+
ctx.translate(jx, jy);
|
|
1084
|
+
ctx.fill();
|
|
1085
|
+
ctx.restore();
|
|
1086
|
+
}
|
|
1087
|
+
ctx.globalAlpha = savedAlpha;
|
|
1088
|
+
// Light stroke on top
|
|
1089
|
+
ctx.globalAlpha *= 0.4;
|
|
1090
|
+
ctx.stroke();
|
|
1091
|
+
ctx.globalAlpha /= 0.4;
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
case "fill-and-stroke":
|
|
1095
|
+
default:
|
|
1096
|
+
ctx.fill();
|
|
1097
|
+
ctx.stroke();
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
986
1101
|
function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
987
|
-
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;
|
|
1102
|
+
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;
|
|
988
1103
|
ctx.save();
|
|
989
1104
|
ctx.translate(x, y);
|
|
990
1105
|
ctx.rotate(rotation * Math.PI / 180);
|
|
@@ -1007,8 +1122,7 @@ function $9beb8f41637c29fd$export$bb35a6995ddbf32d(ctx, shape, x, y, config) {
|
|
|
1007
1122
|
const drawFunction = (0, $701ba7c7229ef06d$export$4ff7fc6f1af248b5)[shape];
|
|
1008
1123
|
if (drawFunction) {
|
|
1009
1124
|
drawFunction(ctx, size);
|
|
1010
|
-
ctx
|
|
1011
|
-
ctx.stroke();
|
|
1125
|
+
$9beb8f41637c29fd$var$applyRenderStyle(ctx, renderStyle, fillColor, strokeColor, strokeWidth, size, rng);
|
|
1012
1126
|
}
|
|
1013
1127
|
// Reset shadow so patterns aren't double-glowed
|
|
1014
1128
|
if (glowRadius > 0) ctx.shadowBlur = 0;
|
|
@@ -1093,13 +1207,6 @@ function $b623126c6e9cbb71$var$pickShape(rng, layerRatio, shapeNames) {
|
|
|
1093
1207
|
if (available.length === 0) return shapeNames[Math.floor(rng() * shapeNames.length)];
|
|
1094
1208
|
return available[Math.floor(rng() * available.length)];
|
|
1095
1209
|
}
|
|
1096
|
-
// ── Helper: simple 2D value noise (hash-seeded) ─────────────────────
|
|
1097
|
-
function $b623126c6e9cbb71$var$valueNoise(x, y, scale, rng) {
|
|
1098
|
-
// Cheap pseudo-noise: combine sin waves at different frequencies
|
|
1099
|
-
const nx = x / scale;
|
|
1100
|
-
const ny = y / scale;
|
|
1101
|
-
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;
|
|
1102
|
-
}
|
|
1103
1210
|
// ── Helper: get position based on composition mode ──────────────────
|
|
1104
1211
|
function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height, shapeIndex, totalShapes, cx, cy) {
|
|
1105
1212
|
switch(mode){
|
|
@@ -1139,10 +1246,8 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1139
1246
|
}
|
|
1140
1247
|
case "clustered":
|
|
1141
1248
|
{
|
|
1142
|
-
// Pick one of 3-5 cluster centers, then scatter around it
|
|
1143
1249
|
const numClusters = 3 + Math.floor(rng() * 3);
|
|
1144
1250
|
const ci = Math.floor(rng() * numClusters);
|
|
1145
|
-
// Deterministic cluster center from index
|
|
1146
1251
|
const clusterRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(String(ci), 999));
|
|
1147
1252
|
const clx = width * (0.15 + clusterRng() * 0.7);
|
|
1148
1253
|
const cly = height * (0.15 + clusterRng() * 0.7);
|
|
@@ -1154,7 +1259,6 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1154
1259
|
}
|
|
1155
1260
|
case "flow-field":
|
|
1156
1261
|
default:
|
|
1157
|
-
// Random position, will be adjusted by flow field direction later
|
|
1158
1262
|
return {
|
|
1159
1263
|
x: rng() * width,
|
|
1160
1264
|
y: rng() * height
|
|
@@ -1163,15 +1267,25 @@ function $b623126c6e9cbb71$var$getCompositionPosition(mode, rng, width, height,
|
|
|
1163
1267
|
}
|
|
1164
1268
|
// ── Helper: positional color blending ───────────────────────────────
|
|
1165
1269
|
function $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, colors, rng) {
|
|
1166
|
-
// Blend between palette colors based on position
|
|
1167
1270
|
const nx = x / width;
|
|
1168
1271
|
const ny = y / height;
|
|
1169
|
-
// Use position to bias which palette color is chosen
|
|
1170
1272
|
const posIndex = (nx * 0.6 + ny * 0.4) * (colors.length - 1);
|
|
1171
1273
|
const baseIdx = Math.floor(posIndex) % colors.length;
|
|
1172
|
-
// Then jitter it slightly
|
|
1173
1274
|
return (0, $9d614e7d77fc2947$export$59539d800dbe6858)(colors[baseIdx], rng, 0.08);
|
|
1174
1275
|
}
|
|
1276
|
+
// ── Helper: check if a position is inside a void zone (Feature E) ───
|
|
1277
|
+
function $b623126c6e9cbb71$var$isInVoidZone(x, y, voidZones) {
|
|
1278
|
+
for (const zone of voidZones){
|
|
1279
|
+
if (Math.hypot(x - zone.x, y - zone.y) < zone.radius) return true;
|
|
1280
|
+
}
|
|
1281
|
+
return false;
|
|
1282
|
+
}
|
|
1283
|
+
// ── Helper: density check for negative space (Feature E) ────────────
|
|
1284
|
+
function $b623126c6e9cbb71$var$localDensity(x, y, positions, radius) {
|
|
1285
|
+
let count = 0;
|
|
1286
|
+
for (const p of positions)if (Math.hypot(x - p.x, y - p.y) < radius) count++;
|
|
1287
|
+
return count;
|
|
1288
|
+
}
|
|
1175
1289
|
function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
1176
1290
|
const finalConfig = {
|
|
1177
1291
|
...(0, $2bfb6a1ccb7a82ae$export$c2f8e0cc249a8d8f),
|
|
@@ -1196,9 +1310,36 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1196
1310
|
bgGrad.addColorStop(1, bgEnd);
|
|
1197
1311
|
ctx.fillStyle = bgGrad;
|
|
1198
1312
|
ctx.fillRect(0, 0, width, height);
|
|
1313
|
+
// ── 1b. Layered background (Feature G) ─────────────────────────
|
|
1314
|
+
// Draw large, very faint shapes to give the background texture
|
|
1315
|
+
const bgShapeCount = 3 + Math.floor(rng() * 4);
|
|
1316
|
+
ctx.globalCompositeOperation = "soft-light";
|
|
1317
|
+
for(let i = 0; i < bgShapeCount; i++){
|
|
1318
|
+
const bx = rng() * width;
|
|
1319
|
+
const by = rng() * height;
|
|
1320
|
+
const bSize = width * 0.3 + rng() * width * 0.5;
|
|
1321
|
+
const bColor = colors[Math.floor(rng() * colors.length)];
|
|
1322
|
+
ctx.globalAlpha = 0.03 + rng() * 0.05;
|
|
1323
|
+
ctx.fillStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(bColor, 0.15);
|
|
1324
|
+
ctx.beginPath();
|
|
1325
|
+
ctx.arc(bx, by, bSize / 2, 0, Math.PI * 2);
|
|
1326
|
+
ctx.fill();
|
|
1327
|
+
}
|
|
1328
|
+
// Subtle concentric rings from center
|
|
1329
|
+
const ringCount = 2 + Math.floor(rng() * 3);
|
|
1330
|
+
ctx.globalAlpha = 0.02 + rng() * 0.03;
|
|
1331
|
+
ctx.strokeStyle = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colors[0], 0.1);
|
|
1332
|
+
ctx.lineWidth = 1 * scaleFactor;
|
|
1333
|
+
for(let i = 1; i <= ringCount; i++){
|
|
1334
|
+
const r = Math.min(width, height) * 0.15 * i;
|
|
1335
|
+
ctx.beginPath();
|
|
1336
|
+
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
1337
|
+
ctx.stroke();
|
|
1338
|
+
}
|
|
1339
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1199
1340
|
// ── 2. Composition mode ────────────────────────────────────────
|
|
1200
1341
|
const compositionMode = $b623126c6e9cbb71$var$COMPOSITION_MODES[Math.floor(rng() * $b623126c6e9cbb71$var$COMPOSITION_MODES.length)];
|
|
1201
|
-
// ── 3. Focal points
|
|
1342
|
+
// ── 3. Focal points + void zones ───────────────────────────────
|
|
1202
1343
|
const numFocal = 1 + Math.floor(rng() * 2);
|
|
1203
1344
|
const focalPoints = [];
|
|
1204
1345
|
for(let f = 0; f < numFocal; f++)focalPoints.push({
|
|
@@ -1206,6 +1347,14 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1206
1347
|
y: height * (0.2 + rng() * 0.6),
|
|
1207
1348
|
strength: 0.3 + rng() * 0.4
|
|
1208
1349
|
});
|
|
1350
|
+
// Feature E: 1-2 void zones where shapes are sparse (negative space)
|
|
1351
|
+
const numVoids = Math.floor(rng() * 2) + 1;
|
|
1352
|
+
const voidZones = [];
|
|
1353
|
+
for(let v = 0; v < numVoids; v++)voidZones.push({
|
|
1354
|
+
x: width * (0.15 + rng() * 0.7),
|
|
1355
|
+
y: height * (0.15 + rng() * 0.7),
|
|
1356
|
+
radius: Math.min(width, height) * (0.06 + rng() * 0.1)
|
|
1357
|
+
});
|
|
1209
1358
|
function applyFocalBias(rx, ry) {
|
|
1210
1359
|
let nearest = focalPoints[0];
|
|
1211
1360
|
let minDist = Infinity;
|
|
@@ -1222,7 +1371,7 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1222
1371
|
ry + (nearest.y - ry) * pull
|
|
1223
1372
|
];
|
|
1224
1373
|
}
|
|
1225
|
-
// ── 4. Flow field seed values
|
|
1374
|
+
// ── 4. Flow field seed values ──────────────────────────────────
|
|
1226
1375
|
const fieldAngleBase = rng() * Math.PI * 2;
|
|
1227
1376
|
const fieldFreq = 0.5 + rng() * 2;
|
|
1228
1377
|
function flowAngle(x, y) {
|
|
@@ -1230,15 +1379,32 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1230
1379
|
}
|
|
1231
1380
|
// ── 5. Shape layers ────────────────────────────────────────────
|
|
1232
1381
|
const shapePositions = [];
|
|
1382
|
+
const densityCheckRadius = Math.min(width, height) * 0.08;
|
|
1383
|
+
const maxLocalDensity = Math.ceil(finalConfig.shapesPerLayer * 0.15);
|
|
1233
1384
|
for(let layer = 0; layer < layers; layer++){
|
|
1234
1385
|
const layerRatio = layers > 1 ? layer / (layers - 1) : 0;
|
|
1235
1386
|
const numShapes = finalConfig.shapesPerLayer + Math.floor(rng() * finalConfig.shapesPerLayer * 0.3);
|
|
1236
1387
|
const layerOpacity = Math.max(0.15, baseOpacity - layer * opacityReduction);
|
|
1237
1388
|
const layerSizeScale = 1 - layer * 0.15;
|
|
1389
|
+
// Feature B: per-layer blend mode
|
|
1390
|
+
const layerBlend = (0, $9beb8f41637c29fd$export$7bb7bff4e26fa06b)(rng);
|
|
1391
|
+
ctx.globalCompositeOperation = layerBlend;
|
|
1392
|
+
// Feature C: per-layer render style bias
|
|
1393
|
+
const layerRenderStyle = (0, $9beb8f41637c29fd$export$9fd4e64b2acd410e)(rng);
|
|
1394
|
+
// Feature D: atmospheric desaturation for later layers
|
|
1395
|
+
const atmosphericDesat = layerRatio * 0.3; // 0 for first layer, up to 0.3 for last
|
|
1238
1396
|
for(let i = 0; i < numShapes; i++){
|
|
1239
1397
|
// Position from composition mode, then focal bias
|
|
1240
1398
|
const rawPos = $b623126c6e9cbb71$var$getCompositionPosition(compositionMode, rng, width, height, i, numShapes, cx, cy);
|
|
1241
1399
|
const [x, y] = applyFocalBias(rawPos.x, rawPos.y);
|
|
1400
|
+
// Feature E: skip shapes in void zones, reduce in dense areas
|
|
1401
|
+
if ($b623126c6e9cbb71$var$isInVoidZone(x, y, voidZones)) {
|
|
1402
|
+
// 85% chance to skip — allows a few shapes to bleed in
|
|
1403
|
+
if (rng() < 0.85) continue;
|
|
1404
|
+
}
|
|
1405
|
+
if ($b623126c6e9cbb71$var$localDensity(x, y, shapePositions, densityCheckRadius) > maxLocalDensity) {
|
|
1406
|
+
if (rng() < 0.6) continue; // thin out dense areas
|
|
1407
|
+
}
|
|
1242
1408
|
// Weighted shape selection
|
|
1243
1409
|
const shape = $b623126c6e9cbb71$var$pickShape(rng, layerRatio, shapeNames);
|
|
1244
1410
|
// Power distribution for size
|
|
@@ -1247,8 +1413,10 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1247
1413
|
// Flow-field rotation in flow-field mode, random otherwise
|
|
1248
1414
|
const rotation = compositionMode === "flow-field" ? flowAngle(x, y) * 180 / Math.PI + (rng() - 0.5) * 30 : rng() * 360;
|
|
1249
1415
|
// Positional color blending + jitter
|
|
1250
|
-
|
|
1416
|
+
let fillBase = $b623126c6e9cbb71$var$getPositionalColor(x, y, width, height, colors, rng);
|
|
1251
1417
|
const strokeBase = colors[Math.floor(rng() * colors.length)];
|
|
1418
|
+
// Feature D: desaturate colors on later layers for depth
|
|
1419
|
+
if (atmosphericDesat > 0) fillBase = (0, $9d614e7d77fc2947$export$fb75607d98509d9)(fillBase, atmosphericDesat);
|
|
1252
1420
|
const fillColor = (0, $9d614e7d77fc2947$export$59539d800dbe6858)(fillBase, rng, 0.06);
|
|
1253
1421
|
const strokeColor = (0, $9d614e7d77fc2947$export$59539d800dbe6858)(strokeBase, rng, 0.05);
|
|
1254
1422
|
// Semi-transparent fill
|
|
@@ -1264,6 +1432,11 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1264
1432
|
// Gradient fill on ~30%
|
|
1265
1433
|
const hasGradient = rng() < 0.3;
|
|
1266
1434
|
const gradientEnd = hasGradient ? (0, $9d614e7d77fc2947$export$59539d800dbe6858)(colors[Math.floor(rng() * colors.length)], rng, 0.1) : undefined;
|
|
1435
|
+
// Feature C: per-shape render style (70% use layer style, 30% pick their own)
|
|
1436
|
+
const shapeRenderStyle = rng() < 0.7 ? layerRenderStyle : (0, $9beb8f41637c29fd$export$9fd4e64b2acd410e)(rng);
|
|
1437
|
+
// Feature F: organic edge jitter — applied via watercolor style on ~15% of shapes
|
|
1438
|
+
const useOrganicEdges = rng() < 0.15 && shapeRenderStyle === "fill-and-stroke";
|
|
1439
|
+
const finalRenderStyle = useOrganicEdges ? "watercolor" : shapeRenderStyle;
|
|
1267
1440
|
(0, $9beb8f41637c29fd$export$bb35a6995ddbf32d)(ctx, shape, x, y, {
|
|
1268
1441
|
fillColor: transparentFill,
|
|
1269
1442
|
strokeColor: strokeColor,
|
|
@@ -1273,14 +1446,16 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1273
1446
|
proportionType: "GOLDEN_RATIO",
|
|
1274
1447
|
glowRadius: glowRadius,
|
|
1275
1448
|
glowColor: hasGlow ? (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(fillColor, 0.6) : undefined,
|
|
1276
|
-
gradientFillEnd: gradientEnd
|
|
1449
|
+
gradientFillEnd: gradientEnd,
|
|
1450
|
+
renderStyle: finalRenderStyle,
|
|
1451
|
+
rng: rng
|
|
1277
1452
|
});
|
|
1278
1453
|
shapePositions.push({
|
|
1279
1454
|
x: x,
|
|
1280
1455
|
y: y,
|
|
1281
1456
|
size: size
|
|
1282
1457
|
});
|
|
1283
|
-
// ── 5b. Recursive nesting
|
|
1458
|
+
// ── 5b. Recursive nesting ──────────────────────────────────
|
|
1284
1459
|
if (size > adjustedMaxSize * 0.4 && rng() < 0.15) {
|
|
1285
1460
|
const innerCount = 1 + Math.floor(rng() * 3);
|
|
1286
1461
|
for(let n = 0; n < innerCount; n++){
|
|
@@ -1297,37 +1472,49 @@ function $b623126c6e9cbb71$export$29a844702096332e(ctx, gitHash, config = {}) {
|
|
|
1297
1472
|
strokeWidth: strokeWidth * 0.6,
|
|
1298
1473
|
size: innerSize,
|
|
1299
1474
|
rotation: innerRot,
|
|
1300
|
-
proportionType: "GOLDEN_RATIO"
|
|
1475
|
+
proportionType: "GOLDEN_RATIO",
|
|
1476
|
+
renderStyle: shapeRenderStyle,
|
|
1477
|
+
rng: rng
|
|
1301
1478
|
});
|
|
1302
1479
|
}
|
|
1303
1480
|
}
|
|
1304
1481
|
}
|
|
1305
1482
|
}
|
|
1306
|
-
//
|
|
1307
|
-
|
|
1483
|
+
// Reset blend mode for post-processing passes
|
|
1484
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1485
|
+
// ── 6. Flow-line pass (Feature H: tapered brush strokes) ───────
|
|
1308
1486
|
const numFlowLines = 6 + Math.floor(rng() * 10);
|
|
1309
1487
|
for(let i = 0; i < numFlowLines; i++){
|
|
1310
1488
|
let fx = rng() * width;
|
|
1311
1489
|
let fy = rng() * height;
|
|
1312
1490
|
const steps = 30 + Math.floor(rng() * 40);
|
|
1313
1491
|
const stepLen = (3 + rng() * 5) * scaleFactor;
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1492
|
+
const startWidth = (1 + rng() * 3) * scaleFactor;
|
|
1493
|
+
const lineColor = (0, $9d614e7d77fc2947$export$f2121afcad3d553f)(colors[Math.floor(rng() * colors.length)], 0.4);
|
|
1494
|
+
const lineAlpha = 0.06 + rng() * 0.1;
|
|
1495
|
+
// Draw as individual segments with tapering width
|
|
1496
|
+
let prevX = fx;
|
|
1497
|
+
let prevY = fy;
|
|
1319
1498
|
for(let s = 0; s < steps; s++){
|
|
1320
1499
|
const angle = flowAngle(fx, fy) + (rng() - 0.5) * 0.3;
|
|
1321
1500
|
fx += Math.cos(angle) * stepLen;
|
|
1322
1501
|
fy += Math.sin(angle) * stepLen;
|
|
1323
|
-
// Stay in bounds
|
|
1324
1502
|
if (fx < 0 || fx > width || fy < 0 || fy > height) break;
|
|
1503
|
+
// Taper: thick at start, thin at end
|
|
1504
|
+
const taper = 1 - s / steps * 0.8;
|
|
1505
|
+
ctx.globalAlpha = lineAlpha * taper;
|
|
1506
|
+
ctx.strokeStyle = lineColor;
|
|
1507
|
+
ctx.lineWidth = startWidth * taper;
|
|
1508
|
+
ctx.lineCap = "round";
|
|
1509
|
+
ctx.beginPath();
|
|
1510
|
+
ctx.moveTo(prevX, prevY);
|
|
1325
1511
|
ctx.lineTo(fx, fy);
|
|
1512
|
+
ctx.stroke();
|
|
1513
|
+
prevX = fx;
|
|
1514
|
+
prevY = fy;
|
|
1326
1515
|
}
|
|
1327
|
-
ctx.stroke();
|
|
1328
1516
|
}
|
|
1329
1517
|
// ── 7. Noise texture overlay ───────────────────────────────────
|
|
1330
|
-
// Subtle grain rendered as tiny semi-transparent dots
|
|
1331
1518
|
const noiseRng = (0, $461134e0b6ce0619$export$eaf9227667332084)((0, $461134e0b6ce0619$export$e9cc707de01b7042)(gitHash, 777));
|
|
1332
1519
|
const noiseDensity = Math.floor(width * height / 800);
|
|
1333
1520
|
for(let i = 0; i < noiseDensity; i++){
|