@autumnsgrove/gossamer 0.1.0 → 0.2.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/dist/animation.js +165 -0
  3. package/dist/animation.test.js +204 -0
  4. package/dist/characters.d.ts.map +1 -1
  5. package/dist/characters.js +176 -0
  6. package/dist/characters.test.js +115 -0
  7. package/dist/colors.d.ts +312 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +199 -0
  10. package/dist/index.d.ts +5 -3
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +79 -1308
  13. package/dist/index.test.js +92 -0
  14. package/dist/patterns.d.ts +119 -2
  15. package/dist/patterns.d.ts.map +1 -1
  16. package/dist/patterns.js +539 -0
  17. package/dist/patterns.test.js +223 -0
  18. package/dist/renderer.d.ts +27 -0
  19. package/dist/renderer.d.ts.map +1 -1
  20. package/dist/renderer.js +362 -0
  21. package/dist/svelte/GossamerBorder.svelte.d.ts +56 -1
  22. package/dist/svelte/GossamerBorder.svelte.d.ts.map +1 -0
  23. package/{src → dist}/svelte/GossamerClouds.svelte +6 -6
  24. package/dist/svelte/GossamerClouds.svelte.d.ts +31 -1
  25. package/dist/svelte/GossamerClouds.svelte.d.ts.map +1 -0
  26. package/dist/svelte/GossamerImage.svelte.d.ts +28 -1
  27. package/dist/svelte/GossamerImage.svelte.d.ts.map +1 -0
  28. package/dist/svelte/GossamerOverlay.svelte.d.ts +32 -1
  29. package/dist/svelte/GossamerOverlay.svelte.d.ts.map +1 -0
  30. package/dist/svelte/GossamerText.svelte.d.ts +29 -1
  31. package/dist/svelte/GossamerText.svelte.d.ts.map +1 -0
  32. package/dist/svelte/index.js +31 -3649
  33. package/dist/svelte/presets.d.ts +4 -2
  34. package/dist/svelte/presets.js +161 -0
  35. package/dist/utils/canvas.js +139 -0
  36. package/dist/utils/image.js +195 -0
  37. package/dist/utils/performance.js +205 -0
  38. package/package.json +20 -15
  39. package/dist/index.js.map +0 -1
  40. package/dist/style.css +0 -124
  41. package/dist/svelte/index.js.map +0 -1
  42. package/src/animation.test.ts +0 -254
  43. package/src/animation.ts +0 -243
  44. package/src/characters.test.ts +0 -148
  45. package/src/characters.ts +0 -164
  46. package/src/index.test.ts +0 -115
  47. package/src/index.ts +0 -203
  48. package/src/patterns.test.ts +0 -273
  49. package/src/patterns.ts +0 -316
  50. package/src/renderer.ts +0 -309
  51. package/src/svelte/index.ts +0 -75
  52. package/src/svelte/presets.ts +0 -174
  53. package/src/utils/canvas.ts +0 -210
  54. package/src/utils/image.ts +0 -275
  55. package/src/utils/performance.ts +0 -282
  56. /package/{src → dist}/svelte/GossamerBorder.svelte +0 -0
  57. /package/{src → dist}/svelte/GossamerImage.svelte +0 -0
  58. /package/{src → dist}/svelte/GossamerOverlay.svelte +0 -0
  59. /package/{src → dist}/svelte/GossamerText.svelte +0 -0
@@ -1,273 +0,0 @@
1
- /**
2
- * Tests for pattern generators
3
- */
4
- import { describe, it, expect } from 'vitest';
5
- import {
6
- perlinNoise2D,
7
- fbmNoise,
8
- wavePattern,
9
- ripplePattern,
10
- staticNoise,
11
- seededNoise2D,
12
- generateBrightnessGrid,
13
- gridToImageData,
14
- DEFAULT_PATTERN_CONFIG,
15
- } from './patterns';
16
-
17
- describe('perlinNoise2D', () => {
18
- it('should return value between -1 and 1', () => {
19
- for (let i = 0; i < 100; i++) {
20
- const x = Math.random() * 100;
21
- const y = Math.random() * 100;
22
- const value = perlinNoise2D(x, y);
23
- expect(value).toBeGreaterThanOrEqual(-1);
24
- expect(value).toBeLessThanOrEqual(1);
25
- }
26
- });
27
-
28
- it('should be deterministic (same input = same output)', () => {
29
- const val1 = perlinNoise2D(5.5, 3.2);
30
- const val2 = perlinNoise2D(5.5, 3.2);
31
- expect(val1).toBe(val2);
32
- });
33
-
34
- it('should vary with position', () => {
35
- // Use non-integer coordinates (Perlin returns 0 at integer coords)
36
- const val1 = perlinNoise2D(0.5, 0.5);
37
- const val2 = perlinNoise2D(10.5, 10.5);
38
- expect(val1).not.toBe(val2);
39
- });
40
-
41
- it('should produce smooth transitions', () => {
42
- const val1 = perlinNoise2D(1.0, 1.0);
43
- const val2 = perlinNoise2D(1.01, 1.01);
44
- const diff = Math.abs(val1 - val2);
45
- expect(diff).toBeLessThan(0.1);
46
- });
47
- });
48
-
49
- describe('fbmNoise', () => {
50
- it('should return value between -1 and 1', () => {
51
- for (let i = 0; i < 50; i++) {
52
- const x = Math.random() * 100;
53
- const y = Math.random() * 100;
54
- const value = fbmNoise(x, y);
55
- expect(value).toBeGreaterThanOrEqual(-1);
56
- expect(value).toBeLessThanOrEqual(1);
57
- }
58
- });
59
-
60
- it('should be deterministic', () => {
61
- const val1 = fbmNoise(3.3, 7.7);
62
- const val2 = fbmNoise(3.3, 7.7);
63
- expect(val1).toBe(val2);
64
- });
65
-
66
- it('should accept octaves parameter', () => {
67
- // Use non-integer coordinates (noise returns 0 at integer coords)
68
- const val1 = fbmNoise(5.5, 5.5, 2);
69
- const val2 = fbmNoise(5.5, 5.5, 8);
70
- // Different octaves should produce different results
71
- expect(val1).not.toBe(val2);
72
- });
73
-
74
- it('should accept persistence parameter', () => {
75
- // Use non-integer coordinates (noise returns 0 at integer coords)
76
- const val1 = fbmNoise(5.5, 5.5, 4, 0.3);
77
- const val2 = fbmNoise(5.5, 5.5, 4, 0.7);
78
- expect(val1).not.toBe(val2);
79
- });
80
- });
81
-
82
- describe('wavePattern', () => {
83
- it('should return value between -1 and 1', () => {
84
- for (let t = 0; t < 10; t++) {
85
- const value = wavePattern(50, 50, t);
86
- expect(value).toBeGreaterThanOrEqual(-1);
87
- expect(value).toBeLessThanOrEqual(1);
88
- }
89
- });
90
-
91
- it('should vary with time', () => {
92
- const val1 = wavePattern(10, 10, 0);
93
- const val2 = wavePattern(10, 10, 5);
94
- expect(val1).not.toBe(val2);
95
- });
96
-
97
- it('should use custom config', () => {
98
- const config = { frequency: 0.1, amplitude: 0.5, speed: 1.0 };
99
- const value = wavePattern(10, 10, 1, config);
100
- expect(Math.abs(value)).toBeLessThanOrEqual(0.5);
101
- });
102
- });
103
-
104
- describe('ripplePattern', () => {
105
- it('should return value between -1 and 1', () => {
106
- for (let i = 0; i < 50; i++) {
107
- const value = ripplePattern(
108
- Math.random() * 100,
109
- Math.random() * 100,
110
- 50,
111
- 50,
112
- Math.random() * 10
113
- );
114
- expect(value).toBeGreaterThanOrEqual(-1);
115
- expect(value).toBeLessThanOrEqual(1);
116
- }
117
- });
118
-
119
- it('should vary with distance from center', () => {
120
- const centerVal = ripplePattern(50, 50, 50, 50, 0);
121
- const edgeVal = ripplePattern(100, 50, 50, 50, 0);
122
- expect(centerVal).not.toBe(edgeVal);
123
- });
124
-
125
- it('should animate over time', () => {
126
- const val1 = ripplePattern(60, 60, 50, 50, 0);
127
- const val2 = ripplePattern(60, 60, 50, 50, 1);
128
- expect(val1).not.toBe(val2);
129
- });
130
- });
131
-
132
- describe('staticNoise', () => {
133
- it('should return value between 0 and 1', () => {
134
- for (let i = 0; i < 100; i++) {
135
- const value = staticNoise();
136
- expect(value).toBeGreaterThanOrEqual(0);
137
- expect(value).toBeLessThanOrEqual(1);
138
- }
139
- });
140
-
141
- it('should be deterministic with seed', () => {
142
- const val1 = staticNoise(12345);
143
- const val2 = staticNoise(12345);
144
- expect(val1).toBe(val2);
145
- });
146
-
147
- it('should vary with different seeds', () => {
148
- const val1 = staticNoise(1);
149
- const val2 = staticNoise(2);
150
- expect(val1).not.toBe(val2);
151
- });
152
- });
153
-
154
- describe('seededNoise2D', () => {
155
- it('should return value between 0 and 1', () => {
156
- for (let i = 0; i < 100; i++) {
157
- const value = seededNoise2D(Math.random() * 100, Math.random() * 100);
158
- expect(value).toBeGreaterThanOrEqual(0);
159
- expect(value).toBeLessThanOrEqual(1);
160
- }
161
- });
162
-
163
- it('should be deterministic', () => {
164
- const val1 = seededNoise2D(5, 10, 42);
165
- const val2 = seededNoise2D(5, 10, 42);
166
- expect(val1).toBe(val2);
167
- });
168
-
169
- it('should vary with coordinates', () => {
170
- const val1 = seededNoise2D(0, 0);
171
- const val2 = seededNoise2D(10, 10);
172
- expect(val1).not.toBe(val2);
173
- });
174
-
175
- it('should vary with seed', () => {
176
- const val1 = seededNoise2D(5, 5, 0);
177
- const val2 = seededNoise2D(5, 5, 100);
178
- expect(val1).not.toBe(val2);
179
- });
180
- });
181
-
182
- describe('DEFAULT_PATTERN_CONFIG', () => {
183
- it('should have expected default values', () => {
184
- expect(DEFAULT_PATTERN_CONFIG.frequency).toBe(0.05);
185
- expect(DEFAULT_PATTERN_CONFIG.amplitude).toBe(1.0);
186
- expect(DEFAULT_PATTERN_CONFIG.speed).toBe(0.5);
187
- });
188
- });
189
-
190
- describe('generateBrightnessGrid', () => {
191
- it('should generate grid with correct dimensions', () => {
192
- const grid = generateBrightnessGrid(10, 8, 'perlin');
193
- expect(grid.length).toBe(8); // rows
194
- expect(grid[0].length).toBe(10); // cols
195
- });
196
-
197
- it('should generate brightness values between 0 and 255', () => {
198
- const grid = generateBrightnessGrid(20, 15, 'perlin');
199
- for (const row of grid) {
200
- for (const value of row) {
201
- expect(value).toBeGreaterThanOrEqual(0);
202
- expect(value).toBeLessThanOrEqual(255);
203
- expect(Number.isInteger(value)).toBe(true);
204
- }
205
- }
206
- });
207
-
208
- it('should support all pattern types', () => {
209
- const patterns: Array<'perlin' | 'waves' | 'static' | 'ripple' | 'fbm'> = [
210
- 'perlin',
211
- 'waves',
212
- 'static',
213
- 'ripple',
214
- 'fbm',
215
- ];
216
-
217
- for (const pattern of patterns) {
218
- const grid = generateBrightnessGrid(5, 5, pattern);
219
- expect(grid.length).toBe(5);
220
- expect(grid[0].length).toBe(5);
221
- }
222
- });
223
-
224
- it('should use time parameter for animation', () => {
225
- const grid1 = generateBrightnessGrid(10, 10, 'perlin', 0);
226
- const grid2 = generateBrightnessGrid(10, 10, 'perlin', 100);
227
-
228
- // At least some values should differ
229
- let hasDifference = false;
230
- for (let r = 0; r < 10 && !hasDifference; r++) {
231
- for (let c = 0; c < 10 && !hasDifference; c++) {
232
- if (grid1[r][c] !== grid2[r][c]) {
233
- hasDifference = true;
234
- }
235
- }
236
- }
237
- expect(hasDifference).toBe(true);
238
- });
239
- });
240
-
241
- // Note: gridToImageData tests require browser environment with canvas support
242
- // These tests are skipped in Node.js/jsdom as ImageData is not available
243
- describe.skip('gridToImageData (browser only)', () => {
244
- it('should create ImageData with correct dimensions', () => {
245
- const grid = [
246
- [100, 150],
247
- [200, 50],
248
- ];
249
- const imageData = gridToImageData(grid, 8, 12);
250
-
251
- expect(imageData.width).toBe(16); // 2 cols * 8
252
- expect(imageData.height).toBe(24); // 2 rows * 12
253
- });
254
-
255
- it('should fill cells with brightness values', () => {
256
- const grid = [[128]]; // Single cell
257
- const imageData = gridToImageData(grid, 2, 2);
258
-
259
- // Check first pixel (should be brightness 128)
260
- expect(imageData.data[0]).toBe(128); // R
261
- expect(imageData.data[1]).toBe(128); // G
262
- expect(imageData.data[2]).toBe(128); // B
263
- expect(imageData.data[3]).toBe(255); // A (full opacity)
264
- });
265
-
266
- it('should handle empty grid', () => {
267
- const grid: number[][] = [];
268
- const imageData = gridToImageData(grid, 8, 12);
269
-
270
- expect(imageData.width).toBe(0);
271
- expect(imageData.height).toBe(0);
272
- });
273
- });
package/src/patterns.ts DELETED
@@ -1,316 +0,0 @@
1
- /**
2
- * Gossamer Pattern Generators
3
- *
4
- * Provides noise and pattern generation functions for ambient ASCII effects.
5
- * Includes Perlin noise, wave patterns, and static noise.
6
- */
7
-
8
- /**
9
- * Configuration for pattern generation
10
- */
11
- export interface PatternConfig {
12
- /** Pattern scale - higher values create finer detail */
13
- frequency: number;
14
- /** Pattern intensity multiplier */
15
- amplitude: number;
16
- /** Animation speed multiplier */
17
- speed: number;
18
- }
19
-
20
- /**
21
- * Default pattern configuration
22
- */
23
- export const DEFAULT_PATTERN_CONFIG: PatternConfig = {
24
- frequency: 0.05,
25
- amplitude: 1.0,
26
- speed: 0.5,
27
- };
28
-
29
- // Permutation table for Perlin noise
30
- const PERMUTATION = new Uint8Array(512);
31
- const P = new Uint8Array([
32
- 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
33
- 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
34
- 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
35
- 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92,
36
- 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
37
- 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,
38
- 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
39
- 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155,
40
- 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218,
41
- 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
42
- 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4,
43
- 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
44
- 180,
45
- ]);
46
-
47
- // Initialize permutation table
48
- for (let i = 0; i < 256; i++) {
49
- PERMUTATION[i] = P[i];
50
- PERMUTATION[i + 256] = P[i];
51
- }
52
-
53
- /**
54
- * Fade function for smooth interpolation (6t^5 - 15t^4 + 10t^3)
55
- */
56
- function fade(t: number): number {
57
- return t * t * t * (t * (t * 6 - 15) + 10);
58
- }
59
-
60
- /**
61
- * Linear interpolation
62
- */
63
- function lerp(a: number, b: number, t: number): number {
64
- return a + t * (b - a);
65
- }
66
-
67
- /**
68
- * Gradient function for Perlin noise
69
- */
70
- function grad(hash: number, x: number, y: number): number {
71
- const h = hash & 3;
72
- const u = h < 2 ? x : y;
73
- const v = h < 2 ? y : x;
74
- return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
75
- }
76
-
77
- /**
78
- * 2D Perlin noise function
79
- *
80
- * @param x - X coordinate
81
- * @param y - Y coordinate
82
- * @returns Noise value between -1 and 1
83
- */
84
- export function perlinNoise2D(x: number, y: number): number {
85
- // Find unit square containing point
86
- const xi = Math.floor(x) & 255;
87
- const yi = Math.floor(y) & 255;
88
-
89
- // Find relative position in square
90
- const xf = x - Math.floor(x);
91
- const yf = y - Math.floor(y);
92
-
93
- // Fade curves
94
- const u = fade(xf);
95
- const v = fade(yf);
96
-
97
- // Hash coordinates of square corners
98
- const aa = PERMUTATION[PERMUTATION[xi] + yi];
99
- const ab = PERMUTATION[PERMUTATION[xi] + yi + 1];
100
- const ba = PERMUTATION[PERMUTATION[xi + 1] + yi];
101
- const bb = PERMUTATION[PERMUTATION[xi + 1] + yi + 1];
102
-
103
- // Blend results from 4 corners
104
- const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
105
- const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);
106
-
107
- return lerp(x1, x2, v);
108
- }
109
-
110
- /**
111
- * Fractal Brownian Motion (fBm) using Perlin noise
112
- * Creates more organic-looking noise by layering multiple octaves
113
- *
114
- * @param x - X coordinate
115
- * @param y - Y coordinate
116
- * @param octaves - Number of noise layers (default: 4)
117
- * @param persistence - Amplitude decay per octave (default: 0.5)
118
- * @returns Noise value between -1 and 1
119
- */
120
- export function fbmNoise(x: number, y: number, octaves: number = 4, persistence: number = 0.5): number {
121
- let total = 0;
122
- let frequency = 1;
123
- let amplitude = 1;
124
- let maxValue = 0;
125
-
126
- for (let i = 0; i < octaves; i++) {
127
- total += perlinNoise2D(x * frequency, y * frequency) * amplitude;
128
- maxValue += amplitude;
129
- amplitude *= persistence;
130
- frequency *= 2;
131
- }
132
-
133
- return total / maxValue;
134
- }
135
-
136
- /**
137
- * Wave pattern generator
138
- *
139
- * @param x - X coordinate
140
- * @param y - Y coordinate
141
- * @param time - Time value for animation
142
- * @param config - Pattern configuration
143
- * @returns Value between -1 and 1
144
- */
145
- export function wavePattern(
146
- x: number,
147
- y: number,
148
- time: number,
149
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
150
- ): number {
151
- const { frequency, amplitude, speed } = config;
152
- const wave1 = Math.sin(x * frequency + time * speed);
153
- const wave2 = Math.cos(y * frequency + time * speed * 0.7);
154
- const wave3 = Math.sin((x + y) * frequency * 0.5 + time * speed * 0.5);
155
-
156
- return ((wave1 + wave2 + wave3) / 3) * amplitude;
157
- }
158
-
159
- /**
160
- * Ripple pattern (concentric waves from center)
161
- *
162
- * @param x - X coordinate
163
- * @param y - Y coordinate
164
- * @param centerX - Ripple center X
165
- * @param centerY - Ripple center Y
166
- * @param time - Time value for animation
167
- * @param config - Pattern configuration
168
- * @returns Value between -1 and 1
169
- */
170
- export function ripplePattern(
171
- x: number,
172
- y: number,
173
- centerX: number,
174
- centerY: number,
175
- time: number,
176
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
177
- ): number {
178
- const { frequency, amplitude, speed } = config;
179
- const dx = x - centerX;
180
- const dy = y - centerY;
181
- const distance = Math.sqrt(dx * dx + dy * dy);
182
-
183
- return Math.sin(distance * frequency - time * speed) * amplitude;
184
- }
185
-
186
- /**
187
- * Static noise generator (random values)
188
- *
189
- * @param seed - Optional seed for reproducible noise
190
- * @returns Value between 0 and 1
191
- */
192
- export function staticNoise(seed?: number): number {
193
- if (seed !== undefined) {
194
- // Simple seeded random using sine
195
- const x = Math.sin(seed * 12.9898) * 43758.5453;
196
- return x - Math.floor(x);
197
- }
198
- return Math.random();
199
- }
200
-
201
- /**
202
- * Seeded 2D noise for reproducible patterns
203
- *
204
- * @param x - X coordinate
205
- * @param y - Y coordinate
206
- * @param seed - Seed value
207
- * @returns Value between 0 and 1
208
- */
209
- export function seededNoise2D(x: number, y: number, seed: number = 0): number {
210
- const n = Math.sin(x * 12.9898 + y * 78.233 + seed) * 43758.5453;
211
- return n - Math.floor(n);
212
- }
213
-
214
- /**
215
- * Generate a brightness grid for pattern rendering
216
- *
217
- * @param cols - Number of columns
218
- * @param rows - Number of rows
219
- * @param pattern - Pattern type
220
- * @param time - Current time in seconds
221
- * @param config - Pattern configuration
222
- * @returns 2D array of brightness values (0-255)
223
- */
224
- export function generateBrightnessGrid(
225
- cols: number,
226
- rows: number,
227
- pattern: 'perlin' | 'waves' | 'static' | 'ripple' | 'fbm',
228
- time: number = 0,
229
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
230
- ): number[][] {
231
- const grid: number[][] = [];
232
- const { frequency, amplitude, speed } = config;
233
-
234
- for (let row = 0; row < rows; row++) {
235
- grid[row] = [];
236
- for (let col = 0; col < cols; col++) {
237
- let value: number;
238
-
239
- switch (pattern) {
240
- case 'perlin':
241
- value = perlinNoise2D(
242
- col * frequency + time * speed * 0.1,
243
- row * frequency + time * speed * 0.05
244
- );
245
- break;
246
-
247
- case 'fbm':
248
- value = fbmNoise(
249
- col * frequency + time * speed * 0.1,
250
- row * frequency + time * speed * 0.05,
251
- 4,
252
- 0.5
253
- );
254
- break;
255
-
256
- case 'waves':
257
- value = wavePattern(col, row, time, config);
258
- break;
259
-
260
- case 'ripple':
261
- value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
262
- break;
263
-
264
- case 'static':
265
- default:
266
- // For static, use time as seed for animated static
267
- value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
268
- break;
269
- }
270
-
271
- // Normalize from [-1, 1] to [0, 255] with amplitude
272
- const normalized = (value + 1) * 0.5 * amplitude;
273
- const brightness = Math.max(0, Math.min(255, Math.floor(normalized * 255)));
274
- grid[row][col] = brightness;
275
- }
276
- }
277
-
278
- return grid;
279
- }
280
-
281
- /**
282
- * Generate ImageData from a brightness grid
283
- *
284
- * @param grid - 2D array of brightness values
285
- * @param cellWidth - Width of each cell
286
- * @param cellHeight - Height of each cell
287
- * @returns ImageData object
288
- */
289
- export function gridToImageData(grid: number[][], cellWidth: number, cellHeight: number): ImageData {
290
- const rows = grid.length;
291
- const cols = grid[0]?.length || 0;
292
- const width = cols * cellWidth;
293
- const height = rows * cellHeight;
294
- const data = new Uint8ClampedArray(width * height * 4);
295
-
296
- for (let row = 0; row < rows; row++) {
297
- for (let col = 0; col < cols; col++) {
298
- const brightness = grid[row][col];
299
-
300
- // Fill cell region with brightness value
301
- for (let cy = 0; cy < cellHeight; cy++) {
302
- for (let cx = 0; cx < cellWidth; cx++) {
303
- const px = ((row * cellHeight + cy) * width + (col * cellWidth + cx)) * 4;
304
- data[px] = brightness; // R
305
- data[px + 1] = brightness; // G
306
- data[px + 2] = brightness; // B
307
- data[px + 3] = 255; // A
308
- }
309
- }
310
- }
311
- }
312
-
313
- return new ImageData(data, width, height);
314
- }
315
-
316
- export type PatternType = 'perlin' | 'waves' | 'static' | 'ripple' | 'fbm';