git-hash-art 0.4.1 → 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 +145 -26
- package/CHANGELOG.md +19 -0
- package/bin/cli.js +155 -0
- package/dist/browser.js +706 -26
- package/dist/browser.js.map +1 -1
- package/dist/main.js +707 -26
- package/dist/main.js.map +1 -1
- package/dist/module.js +707 -26
- package/dist/module.js.map +1 -1
- package/package.json +4 -1
- package/src/lib/archetypes.ts +248 -0
- package/src/lib/canvas/colors.ts +175 -5
- package/src/lib/canvas/draw.ts +75 -1
- package/src/lib/render.ts +228 -33
package/ALGORITHM.md
CHANGED
|
@@ -9,27 +9,35 @@ Hash String
|
|
|
9
9
|
│
|
|
10
10
|
├─► Seed (mulberry32 PRNG)
|
|
11
11
|
│
|
|
12
|
-
├─►
|
|
12
|
+
├─► Archetype Selection (1 of 10 visual personalities)
|
|
13
13
|
│
|
|
14
|
-
|
|
14
|
+
├─► Color Scheme (palette mode from archetype + temperature mode)
|
|
15
|
+
│
|
|
16
|
+
└─► Rendering Pipeline (parameters overridden by archetype)
|
|
15
17
|
│
|
|
16
|
-
|
|
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)
|
|
23
|
+
3. Focal Points (rule-of-thirds biased) + Void Zones
|
|
20
24
|
4. Flow Field Initialization
|
|
21
|
-
|
|
25
|
+
4b. Hero Shape (large focal anchor, archetype-controlled)
|
|
26
|
+
5. Shape Layers (× N layers, archetype-tuned)
|
|
22
27
|
│ ├─ Blend Mode (per-layer compositing)
|
|
23
|
-
│ ├─ Render Style (
|
|
28
|
+
│ ├─ Render Style (archetype-preferred + random mix)
|
|
24
29
|
│ ├─ Position (composition mode + focal bias + density check)
|
|
25
30
|
│ ├─ Shape Selection (layer-weighted)
|
|
26
31
|
│ ├─ Atmospheric Depth (desaturation on later layers)
|
|
32
|
+
│ ├─ Temperature Contrast (foreground opposite to background)
|
|
27
33
|
│ ├─ Styling (transparency, glow, gradients, color jitter)
|
|
28
34
|
│ ├─ Organic Edges (~15% watercolor bleed)
|
|
29
35
|
│ └─ Recursive Nesting (~15% of large shapes)
|
|
30
|
-
6. Flow-Line Pass (tapered brush strokes)
|
|
36
|
+
6. Flow-Line Pass (tapered brush strokes, archetype-scaled)
|
|
37
|
+
6b. Symmetry Mirroring (bilateral-x, bilateral-y, or quad)
|
|
31
38
|
7. Noise Texture Overlay
|
|
32
|
-
8.
|
|
39
|
+
8. Vignette (radial edge darkening)
|
|
40
|
+
9. Organic Connecting Curves
|
|
33
41
|
```
|
|
34
42
|
|
|
35
43
|
## 1. Deterministic RNG
|
|
@@ -43,7 +51,44 @@ rng() → float in [0, 1)
|
|
|
43
51
|
|
|
44
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.
|
|
45
53
|
|
|
46
|
-
## 2.
|
|
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
|
|
47
92
|
|
|
48
93
|
The `SacredColorScheme` class derives three harmonious palettes from the hash:
|
|
49
94
|
|
|
@@ -53,7 +98,35 @@ The `SacredColorScheme` class derives three harmonious palettes from the hash:
|
|
|
53
98
|
| Complementary | hue = seed + 180°, contrasting variation | Contrast accents |
|
|
54
99
|
| Triadic | hue = seed + 120° | Additional variety |
|
|
55
100
|
|
|
56
|
-
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.
|
|
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.
|
|
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
|
+
|
|
119
|
+
### Temperature Contrast
|
|
120
|
+
|
|
121
|
+
The hash deterministically selects a **temperature mode** that creates warm/cool tension across the image:
|
|
122
|
+
|
|
123
|
+
| Mode | Probability | Background | Foreground |
|
|
124
|
+
|------|-------------|------------|------------|
|
|
125
|
+
| `warm-bg` | ~40% | Hues shifted toward orange (30°) | Hues shifted toward blue (210°) |
|
|
126
|
+
| `cool-bg` | ~40% | Hues shifted toward blue (210°) | Hues shifted toward orange (30°) |
|
|
127
|
+
| `neutral` | ~20% | No temperature shift | No temperature shift |
|
|
128
|
+
|
|
129
|
+
The shift amount is subtle (15-25%) and increases on later layers, creating progressive temperature separation between foreground and background elements. This produces the kind of warm/cool interplay seen in classical painting.
|
|
57
130
|
|
|
58
131
|
### Hash-Driven Color Variation
|
|
59
132
|
|
|
@@ -75,11 +148,24 @@ Scheme types also vary: `analogic`, `mono`, `contrast`, `triade`, `tetrade`. The
|
|
|
75
148
|
- **`hexWithAlpha(hex, alpha)`** — converts hex to `rgba()` for transparency
|
|
76
149
|
- **`jitterColor(hex, rng, amount)`** — applies ±amount RGB jitter per channel for organic variation
|
|
77
150
|
- **`desaturate(hex, amount)`** — blends toward luminance gray for atmospheric depth
|
|
151
|
+
- **`shiftTemperature(hex, target, amount)`** — shifts hue toward warm (orange) or cool (blue)
|
|
78
152
|
- **Positional blending** — shape fill color is biased by canvas position, creating smooth color flow across the image
|
|
79
153
|
|
|
80
|
-
##
|
|
154
|
+
## 4. Background
|
|
155
|
+
|
|
156
|
+
The archetype's `backgroundStyle` selects one of 7 rendering modes:
|
|
81
157
|
|
|
82
|
-
|
|
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 |
|
|
167
|
+
|
|
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.
|
|
83
169
|
|
|
84
170
|
### Layered Background
|
|
85
171
|
|
|
@@ -90,7 +176,7 @@ After the gradient, a second pass adds visual texture to the background:
|
|
|
90
176
|
|
|
91
177
|
This prevents the background from feeling flat and gives the image depth before the main shape layers begin.
|
|
92
178
|
|
|
93
|
-
##
|
|
179
|
+
## 5. Composition Modes
|
|
94
180
|
|
|
95
181
|
The hash deterministically selects one of five composition strategies that control how shapes are positioned on the canvas:
|
|
96
182
|
|
|
@@ -104,9 +190,31 @@ The hash deterministically selects one of five composition strategies that contr
|
|
|
104
190
|
|
|
105
191
|
Each mode produces fundamentally different visual character from the same shape set.
|
|
106
192
|
|
|
107
|
-
|
|
193
|
+
### Symmetry Modes
|
|
194
|
+
|
|
195
|
+
~25% of hashes trigger a symmetry mode that mirrors the rendered content:
|
|
196
|
+
|
|
197
|
+
| Mode | Probability | Effect |
|
|
198
|
+
|------|-------------|--------|
|
|
199
|
+
| `bilateral-x` | 10% | Left half mirrored onto right half |
|
|
200
|
+
| `bilateral-y` | 10% | Top half mirrored onto bottom half |
|
|
201
|
+
| `quad` | 5% | Both axes mirrored (4-fold symmetry) |
|
|
202
|
+
| `none` | 75% | No mirroring |
|
|
203
|
+
|
|
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.
|
|
205
|
+
|
|
206
|
+
## 6. Focal Points & Negative Space
|
|
108
207
|
|
|
109
|
-
1-2 focal points are placed on the canvas (
|
|
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%).
|
|
209
|
+
|
|
210
|
+
### Hero Shape
|
|
211
|
+
|
|
212
|
+
~60% of images receive a **hero shape** — a large sacred or complex geometry piece anchored at the primary focal point. The hero shape:
|
|
213
|
+
- Uses sacred/complex shape types (flower of life, fibonacci spiral, merkaba, fractal, etc.)
|
|
214
|
+
- Is sized at 80-130% of the maximum shape size for visual dominance
|
|
215
|
+
- Gets glow effects, gradient fills, and often watercolor rendering
|
|
216
|
+
- Is drawn before the main shape layers so other shapes layer on top of it
|
|
217
|
+
- Creates a clear center of gravity that anchors the entire composition
|
|
110
218
|
|
|
111
219
|
### Void Zones (Negative Space)
|
|
112
220
|
|
|
@@ -116,7 +224,7 @@ Each mode produces fundamentally different visual character from the same shape
|
|
|
116
224
|
|
|
117
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.
|
|
118
226
|
|
|
119
|
-
##
|
|
227
|
+
## 7. Shape Layers
|
|
120
228
|
|
|
121
229
|
The image is built in N layers (default: 4). Each layer has its own characteristics:
|
|
122
230
|
|
|
@@ -129,7 +237,7 @@ The image is built in N layers (default: 4). Each layer has its own characterist
|
|
|
129
237
|
| Shape weights | Early layers favor basic shapes; later layers favor complex/sacred |
|
|
130
238
|
| Per-shape opacity | Additional random jitter (50-100% of layer opacity) |
|
|
131
239
|
| Blend mode | Each layer gets a hash-derived `globalCompositeOperation` (see below) |
|
|
132
|
-
| 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 |
|
|
133
241
|
| Atmospheric depth | Later layers desaturate colors by up to 30%, simulating distance |
|
|
134
242
|
|
|
135
243
|
### Blend Modes (Per-Layer Compositing)
|
|
@@ -142,12 +250,14 @@ Instead of always `fill()` + `stroke()`, each shape gets a rendering treatment:
|
|
|
142
250
|
|
|
143
251
|
| Style | Description | Probability |
|
|
144
252
|
|-------|-------------|-------------|
|
|
145
|
-
| `fill-and-stroke` | Classic solid fill with outline | ~
|
|
146
|
-
| `fill-only` | Soft shapes with no outline | ~
|
|
147
|
-
| `stroke-only` | Wireframe with ghost fill at 30% alpha | ~
|
|
148
|
-
| `double-stroke` | Outer stroke at 2× width + inner stroke in fill color | ~
|
|
149
|
-
| `dashed` | Dashed outline (5% size dash, 3% gap) | ~
|
|
150
|
-
| `watercolor` | 3-4 slightly offset passes at low opacity for bleed effect | ~
|
|
253
|
+
| `fill-and-stroke` | Classic solid fill with outline | ~22% (weighted) |
|
|
254
|
+
| `fill-only` | Soft shapes with no outline | ~11% |
|
|
255
|
+
| `stroke-only` | Wireframe with ghost fill at 30% alpha | ~11% |
|
|
256
|
+
| `double-stroke` | Outer stroke at 2× width + inner stroke in fill color | ~11% |
|
|
257
|
+
| `dashed` | Dashed outline (5% size dash, 3% gap) | ~11% |
|
|
258
|
+
| `watercolor` | 3-4 slightly offset passes at low opacity for bleed effect | ~11% |
|
|
259
|
+
| `hatched` | Cross-hatch texture fill clipped to shape boundary | ~11% |
|
|
260
|
+
| `incomplete` | Only 60-85% of outline drawn via dash patterns | ~11% |
|
|
151
261
|
|
|
152
262
|
70% of shapes in a layer use the layer's dominant style; 30% pick independently. Additionally, ~15% of `fill-and-stroke` shapes are upgraded to `watercolor` for organic edge effects.
|
|
153
263
|
|
|
@@ -188,7 +298,7 @@ Each shape receives:
|
|
|
188
298
|
- Sized at 15-40% of the parent
|
|
189
299
|
- More transparent than the parent layer
|
|
190
300
|
|
|
191
|
-
##
|
|
301
|
+
## 8. Flow-Line Pass (Tapered Brush Strokes)
|
|
192
302
|
|
|
193
303
|
6-16 flowing curves are drawn across the canvas, following the hash-derived vector field:
|
|
194
304
|
|
|
@@ -204,7 +314,7 @@ The flow field is defined by:
|
|
|
204
314
|
angle(x, y) = baseAngle + sin(x/w × freq × 2π) × π/2 + cos(y/h × freq × 2π) × π/2
|
|
205
315
|
```
|
|
206
316
|
|
|
207
|
-
##
|
|
317
|
+
## 9. Noise Texture Overlay
|
|
208
318
|
|
|
209
319
|
A dedicated noise RNG (seeded separately from the main RNG to avoid affecting shape generation) renders thousands of 1px dots across the canvas:
|
|
210
320
|
|
|
@@ -213,7 +323,16 @@ A dedicated noise RNG (seeded separately from the main RNG to avoid affecting sh
|
|
|
213
323
|
- Very low opacity (1-4%)
|
|
214
324
|
- Creates subtle film-grain texture that adds organic depth
|
|
215
325
|
|
|
216
|
-
##
|
|
326
|
+
## 9b. Vignette
|
|
327
|
+
|
|
328
|
+
A radial gradient overlay darkens the edges of the canvas, drawing the viewer's eye toward the center:
|
|
329
|
+
|
|
330
|
+
- Strength varies by hash: 25-45% maximum edge darkening
|
|
331
|
+
- The vignette begins fading at 60% of the canvas radius from center
|
|
332
|
+
- Applied after noise but before connecting curves, so the curves remain visible at edges
|
|
333
|
+
- Creates a natural "spotlight" effect that makes compositions feel more focused and photographic
|
|
334
|
+
|
|
335
|
+
## 10. Organic Connecting Curves
|
|
217
336
|
|
|
218
337
|
Quadratic bezier curves connect nearby shapes:
|
|
219
338
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,27 @@ 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
|
+
|
|
14
|
+
#### [0.5.0](https://github.com/gfargo/git-hash-art/compare/0.4.1...0.5.0)
|
|
15
|
+
|
|
16
|
+
> 19 March 2026
|
|
17
|
+
|
|
18
|
+
- feat: visual improvements — composition, color, and rendering upgrades [`#11`](https://github.com/gfargo/git-hash-art/pull/11)
|
|
19
|
+
- feat: warm/cool temperature contrast in color system [`550bc6a`](https://github.com/gfargo/git-hash-art/commit/550bc6a87929d7ebafa973079845e688a00de929)
|
|
20
|
+
- docs: update ALGORITHM.md with all new visual features [`a05de15`](https://github.com/gfargo/git-hash-art/commit/a05de1586684b91266f5a26ea7279fc601dd2202)
|
|
21
|
+
- feat: hatched and incomplete render styles for shape variety [`4f42f7e`](https://github.com/gfargo/git-hash-art/commit/4f42f7e30bac60239b69468e36ef5481dca5d007)
|
|
22
|
+
|
|
7
23
|
#### [0.4.1](https://github.com/gfargo/git-hash-art/compare/0.4.0...0.4.1)
|
|
8
24
|
|
|
25
|
+
> 19 March 2026
|
|
26
|
+
|
|
27
|
+
- chore: release v0.4.1 [`c453061`](https://github.com/gfargo/git-hash-art/commit/c4530616bc40a540ce4d87a8124a1d2665f77376)
|
|
9
28
|
- update README with new feature details [`1e3fcd7`](https://github.com/gfargo/git-hash-art/commit/1e3fcd771ea602b21c9c90cd3809d0d08ace5d3f)
|
|
10
29
|
- update color variations and complementary logic [`9c8f4ba`](https://github.com/gfargo/git-hash-art/commit/9c8f4ba51e1a5730e4c4ddd59218db23d4719129)
|
|
11
30
|
|
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();
|