git-hash-art 0.5.0 → 0.6.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 CHANGED
@@ -9,20 +9,23 @@ Hash String
9
9
 
10
10
  ├─► Seed (mulberry32 PRNG)
11
11
 
12
- ├─► Color Scheme (hash-driven variation + scheme type + temperature mode)
12
+ ├─► Archetype Selection (1 of 10 visual personalities)
13
13
 
14
- └─► Rendering Pipeline
14
+ ├─► Color Scheme (palette mode from archetype + temperature mode)
15
+
16
+ └─► Rendering Pipeline (parameters overridden by archetype)
15
17
 
16
- 1. Background Layer (radial gradient, temperature-shifted)
18
+ 0. Archetype Override (gridSize, layers, opacity, sizes, styles)
19
+ 1. Background Layer (7 styles: radial, linear, solid, multi-stop)
17
20
  1b. Layered Background (faint shapes + concentric rings)
18
21
  2. Composition Mode Selection
19
22
  2b. Symmetry Mode Selection (none / bilateral / quad)
20
23
  3. Focal Points (rule-of-thirds biased) + Void Zones
21
24
  4. Flow Field Initialization
22
- 4b. Hero Shape (large focal anchor, ~60% of images)
23
- 5. Shape Layers (× N layers)
25
+ 4b. Hero Shape (large focal anchor, archetype-controlled)
26
+ 5. Shape Layers (× N layers, archetype-tuned)
24
27
  │ ├─ Blend Mode (per-layer compositing)
25
- │ ├─ Render Style (fill+stroke, wireframe, dashed, watercolor, hatched, incomplete, etc.)
28
+ │ ├─ Render Style (archetype-preferred + random mix)
26
29
  │ ├─ Position (composition mode + focal bias + density check)
27
30
  │ ├─ Shape Selection (layer-weighted)
28
31
  │ ├─ Atmospheric Depth (desaturation on later layers)
@@ -30,7 +33,7 @@ Hash String
30
33
  │ ├─ Styling (transparency, glow, gradients, color jitter)
31
34
  │ ├─ Organic Edges (~15% watercolor bleed)
32
35
  │ └─ Recursive Nesting (~15% of large shapes)
33
- 6. Flow-Line Pass (tapered brush strokes)
36
+ 6. Flow-Line Pass (tapered brush strokes, archetype-scaled)
34
37
  6b. Symmetry Mirroring (bilateral-x, bilateral-y, or quad)
35
38
  7. Noise Texture Overlay
36
39
  8. Vignette (radial edge darkening)
@@ -48,7 +51,44 @@ rng() → float in [0, 1)
48
51
 
49
52
  The old approach extracted 2-char hex pairs from the hash (only ~20 unique values in a 40-char hash). Mulberry32 produces a full 32-bit uniform stream from any seed, eliminating correlation artifacts.
50
53
 
51
- ## 2. Color Scheme
54
+ ## 2. Archetype System
55
+
56
+ Before any rendering begins, the hash deterministically selects one of 10 **visual archetypes** — fundamentally different rendering personalities that override key parameters. This is the primary mechanism for visual diversity: two hashes that select different archetypes will look like they came from entirely different generators.
57
+
58
+ Each archetype controls:
59
+
60
+ | Parameter | Effect |
61
+ |-----------|--------|
62
+ | `gridSize` | Shape density (2 = sparse, 9 = packed) |
63
+ | `layers` | Rendering depth (2 = flat, 5 = deep) |
64
+ | `baseOpacity` / `opacityReduction` | Transparency character |
65
+ | `minShapeSize` / `maxShapeSize` | Scale range |
66
+ | `backgroundStyle` | One of 7 background rendering modes |
67
+ | `paletteMode` | One of 7 color palette strategies |
68
+ | `preferredStyles` | Weighted render style selection |
69
+ | `flowLineMultiplier` | Flow line density (0 = none, 4 = heavy) |
70
+ | `heroShape` | Whether to draw a dominant focal shape |
71
+ | `glowMultiplier` | Glow probability scaling (0 = none, 3 = heavy) |
72
+ | `sizePower` | Size distribution curve (0.5 = uniform, 2.5 = many tiny) |
73
+
74
+ ### The 10 Archetypes
75
+
76
+ | Archetype | Character | Background | Palette | Key Traits |
77
+ |-----------|-----------|------------|---------|------------|
78
+ | **dense-chaotic** | Packed, energetic | radial-dark | harmonious | 9×9 grid, 5 layers, heavy flow lines, low glow |
79
+ | **minimal-spacious** | Clean, deliberate | solid-light | duotone | 2×2 grid, 2 layers, large shapes, no glow |
80
+ | **organic-flow** | Natural, flowing | radial-dark | earth | Heavy flow lines (4×), watercolor style, no hero |
81
+ | **geometric-precision** | Technical, structured | solid-dark | high-contrast | Stroke-only/dashed/hatched styles, no flow lines |
82
+ | **ethereal** | Dreamy, luminous | radial-light | pastel-light | High glow (2×), watercolor, hero shape |
83
+ | **bold-graphic** | Poster-like, impactful | linear-diagonal | duotone | 2 layers, very large shapes, no flow lines |
84
+ | **neon-glow** | Electric, vibrant | solid-dark | neon | Heavy glow (3×), stroke-heavy styles, hero shape |
85
+ | **monochrome-ink** | Pen-and-ink, textural | solid-light | monochrome | Hatched/incomplete styles, no glow |
86
+ | **cosmic** | Deep space, vast | radial-dark | neon | 8×8 grid, 5 layers, heavy glow, many tiny shapes |
87
+ | **classic** | Balanced, familiar | radial-dark | harmonious | Preserves the original rendering look |
88
+
89
+ Archetype values serve as defaults — explicit user config always wins. The `classic` archetype preserves backward compatibility with the original rendering style.
90
+
91
+ ## 3. Color Scheme
52
92
 
53
93
  The `SacredColorScheme` class derives three harmonious palettes from the hash:
54
94
 
@@ -60,6 +100,22 @@ The `SacredColorScheme` class derives three harmonious palettes from the hash:
60
100
 
61
101
  These are merged and deduplicated into a single 6-8 color palette. Background colors are darkened variants (65% and 55% brightness) of the base scheme, with optional temperature shifting.
62
102
 
103
+ ### Palette Modes
104
+
105
+ The archetype's `paletteMode` reshapes the color palette to match the visual personality:
106
+
107
+ | Mode | Colors | Character |
108
+ |------|--------|-----------|
109
+ | **harmonious** | Full base + complementary + triadic | Rich, balanced (default) |
110
+ | **monochrome** | Single hue, 5 lightness steps | Elegant, focused |
111
+ | **duotone** | Two contrasting hues + tints | Bold, graphic |
112
+ | **neon** | 4 hues at full saturation | Electric, vivid |
113
+ | **pastel-light** | 4 hues at low saturation, high lightness | Soft, dreamy |
114
+ | **earth** | Warm muted naturals (browns, olives, sage) | Organic, grounded |
115
+ | **high-contrast** | Black + white + one accent | Technical, stark |
116
+
117
+ Each mode also provides matching background colors (e.g., neon gets near-black backgrounds, pastel-light gets warm off-whites).
118
+
63
119
  ### Temperature Contrast
64
120
 
65
121
  The hash deterministically selects a **temperature mode** that creates warm/cool tension across the image:
@@ -95,9 +151,21 @@ Scheme types also vary: `analogic`, `mono`, `contrast`, `triade`, `tetrade`. The
95
151
  - **`shiftTemperature(hex, target, amount)`** — shifts hue toward warm (orange) or cool (blue)
96
152
  - **Positional blending** — shape fill color is biased by canvas position, creating smooth color flow across the image
97
153
 
98
- ## 3. Background
154
+ ## 4. Background
155
+
156
+ The archetype's `backgroundStyle` selects one of 7 rendering modes:
157
+
158
+ | Style | Description |
159
+ |-------|-------------|
160
+ | **radial-dark** | Radial gradient from dark center to darker edges (original default) |
161
+ | **radial-light** | Light off-white center fading to the base palette |
162
+ | **linear-horizontal** | Left-to-right gradient between two palette colors |
163
+ | **linear-diagonal** | Corner-to-corner gradient with color reversal |
164
+ | **solid-dark** | Flat dark color fill |
165
+ | **solid-light** | Flat warm off-white fill |
166
+ | **multi-stop** | 3-4 color gradient with a darkened mid-palette accent |
99
167
 
100
- A radial gradient fills the canvas from center to corners using two darkened base-scheme colors. This creates depth before any shapes are drawn.
168
+ This single change has an outsized impact on visual diversity a solid-light background with monochrome shapes looks nothing like a radial-dark background with neon glow.
101
169
 
102
170
  ### Layered Background
103
171
 
@@ -108,7 +176,7 @@ After the gradient, a second pass adds visual texture to the background:
108
176
 
109
177
  This prevents the background from feeling flat and gives the image depth before the main shape layers begin.
110
178
 
111
- ## 4. Composition Modes
179
+ ## 5. Composition Modes
112
180
 
113
181
  The hash deterministically selects one of five composition strategies that control how shapes are positioned on the canvas:
114
182
 
@@ -135,7 +203,7 @@ Each mode produces fundamentally different visual character from the same shape
135
203
 
136
204
  Symmetry is applied after shape layers and flow lines but before post-processing (noise, vignette, connecting curves). This means the mirrored content gets the same noise texture and vignette as the original, maintaining visual consistency. Symmetrical generative art is disproportionately appealing to humans due to our innate preference for bilateral symmetry.
137
205
 
138
- ## 5. Focal Points & Negative Space
206
+ ## 6. Focal Points & Negative Space
139
207
 
140
208
  1-2 focal points are placed on the canvas. **70% of the time**, focal points snap to **rule-of-thirds intersection points** (with slight jitter to avoid a mechanical look), creating compositions that feel intentionally designed. The remaining 30% use free placement within the central 60% of the canvas. Every shape position is pulled toward the nearest focal point by a strength factor (30-70%).
141
209
 
@@ -156,7 +224,7 @@ Symmetry is applied after shape layers and flow lines but before post-processing
156
224
 
157
225
  Before placing each shape, the renderer checks how many shapes already exist nearby. If local density exceeds ~15% of the per-layer shape count, there's a 60% chance the shape is skipped. This prevents areas from becoming an opaque blob and creates natural visual rhythm.
158
226
 
159
- ## 6. Shape Layers
227
+ ## 7. Shape Layers
160
228
 
161
229
  The image is built in N layers (default: 4). Each layer has its own characteristics:
162
230
 
@@ -169,7 +237,7 @@ The image is built in N layers (default: 4). Each layer has its own characterist
169
237
  | Shape weights | Early layers favor basic shapes; later layers favor complex/sacred |
170
238
  | Per-shape opacity | Additional random jitter (50-100% of layer opacity) |
171
239
  | Blend mode | Each layer gets a hash-derived `globalCompositeOperation` (see below) |
172
- | Render style | Each layer has a dominant render style; 30% of shapes pick their own |
240
+ | Render style | Each layer has a dominant render style (60% from archetype preferences, 40% random); 30% of shapes pick their own |
173
241
  | Atmospheric depth | Later layers desaturate colors by up to 30%, simulating distance |
174
242
 
175
243
  ### Blend Modes (Per-Layer Compositing)
@@ -230,7 +298,7 @@ Each shape receives:
230
298
  - Sized at 15-40% of the parent
231
299
  - More transparent than the parent layer
232
300
 
233
- ## 7. Flow-Line Pass (Tapered Brush Strokes)
301
+ ## 8. Flow-Line Pass (Tapered Brush Strokes)
234
302
 
235
303
  6-16 flowing curves are drawn across the canvas, following the hash-derived vector field:
236
304
 
@@ -246,7 +314,7 @@ The flow field is defined by:
246
314
  angle(x, y) = baseAngle + sin(x/w × freq × 2π) × π/2 + cos(y/h × freq × 2π) × π/2
247
315
  ```
248
316
 
249
- ## 8. Noise Texture Overlay
317
+ ## 9. Noise Texture Overlay
250
318
 
251
319
  A dedicated noise RNG (seeded separately from the main RNG to avoid affecting shape generation) renders thousands of 1px dots across the canvas:
252
320
 
@@ -255,7 +323,7 @@ A dedicated noise RNG (seeded separately from the main RNG to avoid affecting sh
255
323
  - Very low opacity (1-4%)
256
324
  - Creates subtle film-grain texture that adds organic depth
257
325
 
258
- ## 8b. Vignette
326
+ ## 9b. Vignette
259
327
 
260
328
  A radial gradient overlay darkens the edges of the canvas, drawing the viewer's eye toward the center:
261
329
 
@@ -264,7 +332,7 @@ A radial gradient overlay darkens the edges of the canvas, drawing the viewer's
264
332
  - Applied after noise but before connecting curves, so the curves remain visible at edges
265
333
  - Creates a natural "spotlight" effect that makes compositions feel more focused and photographic
266
334
 
267
- ## 9. Organic Connecting Curves
335
+ ## 10. Organic Connecting Curves
268
336
 
269
337
  Quadratic bezier curves connect nearby shapes:
270
338
 
package/CHANGELOG.md CHANGED
@@ -4,8 +4,17 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [0.6.0](https://github.com/gfargo/git-hash-art/compare/0.5.0...0.6.0)
8
+
9
+ - feat: archetype system, palette modes, background variety, and CLI [`#12`](https://github.com/gfargo/git-hash-art/pull/12)
10
+ - feat: archetype system for dramatically different visual personalities [`2a1b919`](https://github.com/gfargo/git-hash-art/commit/2a1b919c51d3bfbd9d5c7a381ea5e104f81df2f6)
11
+ - feat: add CLI for generating art from terminal [`184372a`](https://github.com/gfargo/git-hash-art/commit/184372a584f68031bf44379f385f2e78691f98aa)
12
+ - docs: update ALGORITHM.md with archetype system, palette modes, and background styles [`4eccd07`](https://github.com/gfargo/git-hash-art/commit/4eccd077be9469c27caf60a0d8c463124ad7f9fa)
13
+
7
14
  #### [0.5.0](https://github.com/gfargo/git-hash-art/compare/0.4.1...0.5.0)
8
15
 
16
+ > 19 March 2026
17
+
9
18
  - feat: visual improvements — composition, color, and rendering upgrades [`#11`](https://github.com/gfargo/git-hash-art/pull/11)
10
19
  - feat: warm/cool temperature contrast in color system [`550bc6a`](https://github.com/gfargo/git-hash-art/commit/550bc6a87929d7ebafa973079845e688a00de929)
11
20
  - docs: update ALGORITHM.md with all new visual features [`a05de15`](https://github.com/gfargo/git-hash-art/commit/a05de1586684b91266f5a26ea7279fc601dd2202)
package/bin/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const { generateImageFromHash, saveImageToFile } = require("../dist/main.js");
5
+ const { PRESETS } = require("../dist/main.js");
6
+
7
+ const HELP = `
8
+ git-hash-art — Generate deterministic abstract art from git hashes.
9
+
10
+ Usage:
11
+ git-hash-art current [options] Generate art from HEAD commit
12
+ git-hash-art generate <hash> [options] Generate art from a specific hash
13
+
14
+ Options:
15
+ --width <n> Canvas width in pixels (default: 2048)
16
+ --height <n> Canvas height in pixels (default: 2048)
17
+ --output <dir> Output directory (default: ./output)
18
+ --preset <name> Use a built-in preset (overrides width/height)
19
+ --layers <n> Number of layers (default: 4)
20
+ --grid <n> Grid density (default: 5)
21
+ --list-presets List available presets
22
+
23
+ Examples:
24
+ npx git-hash-art current
25
+ npx git-hash-art current --preset instagram-square
26
+ npx git-hash-art generate abc123ff --width 1920 --height 1080
27
+ `;
28
+
29
+ function parseArgs(argv) {
30
+ const args = argv.slice(2);
31
+ const command = args[0];
32
+ const parsed = { command, hash: null, options: {} };
33
+
34
+ let i = 1;
35
+ if (command === "generate") {
36
+ parsed.hash = args[1];
37
+ i = 2;
38
+ }
39
+
40
+ while (i < args.length) {
41
+ const arg = args[i];
42
+ if (arg === "--width" && args[i + 1]) {
43
+ parsed.options.width = parseInt(args[i + 1], 10);
44
+ i += 2;
45
+ } else if (arg === "--height" && args[i + 1]) {
46
+ parsed.options.height = parseInt(args[i + 1], 10);
47
+ i += 2;
48
+ } else if (arg === "--output" && args[i + 1]) {
49
+ parsed.options.output = args[i + 1];
50
+ i += 2;
51
+ } else if (arg === "--preset" && args[i + 1]) {
52
+ parsed.options.preset = args[i + 1];
53
+ i += 2;
54
+ } else if (arg === "--layers" && args[i + 1]) {
55
+ parsed.options.layers = parseInt(args[i + 1], 10);
56
+ i += 2;
57
+ } else if (arg === "--grid" && args[i + 1]) {
58
+ parsed.options.gridSize = parseInt(args[i + 1], 10);
59
+ i += 2;
60
+ } else if (arg === "--list-presets") {
61
+ parsed.command = "list-presets";
62
+ i += 1;
63
+ } else if (arg === "--help" || arg === "-h") {
64
+ parsed.command = "help";
65
+ i += 1;
66
+ } else {
67
+ i += 1;
68
+ }
69
+ }
70
+
71
+ return parsed;
72
+ }
73
+
74
+ function getHeadHash() {
75
+ try {
76
+ return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
77
+ } catch {
78
+ console.error("Error: Not a git repository or git is not installed.");
79
+ process.exit(1);
80
+ }
81
+ }
82
+
83
+ function listPresets() {
84
+ console.log("\nAvailable presets:\n");
85
+ for (const [name, config] of Object.entries(PRESETS)) {
86
+ console.log(` ${name.padEnd(20)} ${config.width}x${config.height}`);
87
+ }
88
+ console.log("");
89
+ }
90
+
91
+ function run() {
92
+ const { command, hash, options } = parseArgs(process.argv);
93
+
94
+ if (!command || command === "help") {
95
+ console.log(HELP);
96
+ process.exit(0);
97
+ }
98
+
99
+ if (command === "list-presets") {
100
+ listPresets();
101
+ process.exit(0);
102
+ }
103
+
104
+ if (command !== "current" && command !== "generate") {
105
+ console.error(`Unknown command: ${command}\n`);
106
+ console.log(HELP);
107
+ process.exit(1);
108
+ }
109
+
110
+ let gitHash;
111
+ if (command === "current") {
112
+ gitHash = getHeadHash();
113
+ console.log(`Using HEAD: ${gitHash}`);
114
+ } else {
115
+ if (!hash) {
116
+ console.error("Error: Please provide a hash.\n");
117
+ console.log("Usage: git-hash-art generate <hash> [options]");
118
+ process.exit(1);
119
+ }
120
+ gitHash = hash;
121
+ }
122
+
123
+ // Build config from preset + overrides
124
+ let config = {};
125
+ if (options.preset) {
126
+ const preset = PRESETS[options.preset];
127
+ if (!preset) {
128
+ console.error(`Unknown preset: ${options.preset}`);
129
+ console.log("Use --list-presets to see available presets.");
130
+ process.exit(1);
131
+ }
132
+ config = { ...preset };
133
+ delete config.hash;
134
+ }
135
+
136
+ if (options.width) config.width = options.width;
137
+ if (options.height) config.height = options.height;
138
+ if (options.layers) config.layers = options.layers;
139
+ if (options.gridSize) config.gridSize = options.gridSize;
140
+
141
+ const outputDir = options.output || "./output";
142
+
143
+ try {
144
+ const buffer = generateImageFromHash(gitHash, config);
145
+ const w = config.width || 2048;
146
+ const h = config.height || 2048;
147
+ const outputPath = saveImageToFile(buffer, outputDir, gitHash, "", w, h);
148
+ console.log(`Done! Image saved to ${outputPath}`);
149
+ } catch (error) {
150
+ console.error("Generation failed:", error.message);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ run();