@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
@@ -0,0 +1,539 @@
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
+ * Default pattern configuration
9
+ */
10
+ export const DEFAULT_PATTERN_CONFIG = {
11
+ frequency: 0.05,
12
+ amplitude: 1.0,
13
+ speed: 0.5,
14
+ };
15
+ // Permutation table for Perlin noise
16
+ const PERMUTATION = new Uint8Array(512);
17
+ const P = new Uint8Array([
18
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
19
+ 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
20
+ 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
21
+ 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92,
22
+ 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
23
+ 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,
24
+ 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
25
+ 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155,
26
+ 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218,
27
+ 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
28
+ 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4,
29
+ 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
30
+ 180,
31
+ ]);
32
+ // Initialize permutation table
33
+ for (let i = 0; i < 256; i++) {
34
+ PERMUTATION[i] = P[i];
35
+ PERMUTATION[i + 256] = P[i];
36
+ }
37
+ /**
38
+ * Fade function for smooth interpolation (6t^5 - 15t^4 + 10t^3)
39
+ */
40
+ function fade(t) {
41
+ return t * t * t * (t * (t * 6 - 15) + 10);
42
+ }
43
+ /**
44
+ * Linear interpolation
45
+ */
46
+ function lerp(a, b, t) {
47
+ return a + t * (b - a);
48
+ }
49
+ /**
50
+ * Gradient function for Perlin noise
51
+ */
52
+ function grad(hash, x, y) {
53
+ const h = hash & 3;
54
+ const u = h < 2 ? x : y;
55
+ const v = h < 2 ? y : x;
56
+ return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
57
+ }
58
+ /**
59
+ * 2D Perlin noise function
60
+ *
61
+ * @param x - X coordinate
62
+ * @param y - Y coordinate
63
+ * @returns Noise value between -1 and 1
64
+ */
65
+ export function perlinNoise2D(x, y) {
66
+ // Find unit square containing point
67
+ const xi = Math.floor(x) & 255;
68
+ const yi = Math.floor(y) & 255;
69
+ // Find relative position in square
70
+ const xf = x - Math.floor(x);
71
+ const yf = y - Math.floor(y);
72
+ // Fade curves
73
+ const u = fade(xf);
74
+ const v = fade(yf);
75
+ // Hash coordinates of square corners
76
+ const aa = PERMUTATION[PERMUTATION[xi] + yi];
77
+ const ab = PERMUTATION[PERMUTATION[xi] + yi + 1];
78
+ const ba = PERMUTATION[PERMUTATION[xi + 1] + yi];
79
+ const bb = PERMUTATION[PERMUTATION[xi + 1] + yi + 1];
80
+ // Blend results from 4 corners
81
+ const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
82
+ const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);
83
+ return lerp(x1, x2, v);
84
+ }
85
+ /**
86
+ * Fractal Brownian Motion (fBm) using Perlin noise
87
+ * Creates more organic-looking noise by layering multiple octaves
88
+ *
89
+ * @param x - X coordinate
90
+ * @param y - Y coordinate
91
+ * @param octaves - Number of noise layers (default: 4)
92
+ * @param persistence - Amplitude decay per octave (default: 0.5)
93
+ * @returns Noise value between -1 and 1
94
+ */
95
+ export function fbmNoise(x, y, octaves = 4, persistence = 0.5) {
96
+ let total = 0;
97
+ let frequency = 1;
98
+ let amplitude = 1;
99
+ let maxValue = 0;
100
+ for (let i = 0; i < octaves; i++) {
101
+ total += perlinNoise2D(x * frequency, y * frequency) * amplitude;
102
+ maxValue += amplitude;
103
+ amplitude *= persistence;
104
+ frequency *= 2;
105
+ }
106
+ return total / maxValue;
107
+ }
108
+ /**
109
+ * Wave pattern generator
110
+ *
111
+ * @param x - X coordinate
112
+ * @param y - Y coordinate
113
+ * @param time - Time value for animation
114
+ * @param config - Pattern configuration
115
+ * @returns Value between -1 and 1
116
+ */
117
+ export function wavePattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
118
+ const { frequency, amplitude, speed } = config;
119
+ const wave1 = Math.sin(x * frequency + time * speed);
120
+ const wave2 = Math.cos(y * frequency + time * speed * 0.7);
121
+ const wave3 = Math.sin((x + y) * frequency * 0.5 + time * speed * 0.5);
122
+ return ((wave1 + wave2 + wave3) / 3) * amplitude;
123
+ }
124
+ /**
125
+ * Ripple pattern (concentric waves from center)
126
+ *
127
+ * @param x - X coordinate
128
+ * @param y - Y coordinate
129
+ * @param centerX - Ripple center X
130
+ * @param centerY - Ripple center Y
131
+ * @param time - Time value for animation
132
+ * @param config - Pattern configuration
133
+ * @returns Value between -1 and 1
134
+ */
135
+ export function ripplePattern(x, y, centerX, centerY, time, config = DEFAULT_PATTERN_CONFIG) {
136
+ const { frequency, amplitude, speed } = config;
137
+ const dx = x - centerX;
138
+ const dy = y - centerY;
139
+ const distance = Math.sqrt(dx * dx + dy * dy);
140
+ return Math.sin(distance * frequency - time * speed) * amplitude;
141
+ }
142
+ /**
143
+ * Static noise generator (random values)
144
+ *
145
+ * @param seed - Optional seed for reproducible noise
146
+ * @returns Value between 0 and 1
147
+ */
148
+ export function staticNoise(seed) {
149
+ if (seed !== undefined) {
150
+ // Simple seeded random using sine
151
+ const x = Math.sin(seed * 12.9898) * 43758.5453;
152
+ return x - Math.floor(x);
153
+ }
154
+ return Math.random();
155
+ }
156
+ /**
157
+ * Seeded 2D noise for reproducible patterns
158
+ *
159
+ * @param x - X coordinate
160
+ * @param y - Y coordinate
161
+ * @param seed - Seed value
162
+ * @returns Value between 0 and 1
163
+ */
164
+ export function seededNoise2D(x, y, seed = 0) {
165
+ const n = Math.sin(x * 12.9898 + y * 78.233 + seed) * 43758.5453;
166
+ return n - Math.floor(n);
167
+ }
168
+ /**
169
+ * Clouds pattern - soft, billowy fbm with gentle movement
170
+ * The signature Gossamer effect!
171
+ *
172
+ * @param x - X coordinate
173
+ * @param y - Y coordinate
174
+ * @param time - Time value for animation
175
+ * @param config - Pattern configuration
176
+ * @returns Value between -1 and 1
177
+ */
178
+ export function cloudsPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
179
+ const { frequency, amplitude, speed } = config;
180
+ // Slow, drifting movement
181
+ const drift = time * speed * 0.02;
182
+ const nx = x * frequency * 0.5 + drift;
183
+ const ny = y * frequency * 0.5 + drift * 0.7;
184
+ // Layer multiple octaves with high persistence for soft, puffy look
185
+ const base = fbmNoise(nx, ny, 5, 0.6);
186
+ // Add subtle secondary drift layer
187
+ const detail = fbmNoise(nx * 2 + drift * 0.5, ny * 2 - drift * 0.3, 3, 0.5) * 0.3;
188
+ // Combine and bias toward lighter values (more sky, less dense cloud)
189
+ const combined = base + detail;
190
+ return Math.tanh(combined * 1.5) * amplitude;
191
+ }
192
+ /**
193
+ * Plasma pattern - classic demoscene effect
194
+ * Combines sine waves at different frequencies and phases
195
+ *
196
+ * @param x - X coordinate
197
+ * @param y - Y coordinate
198
+ * @param time - Time value for animation
199
+ * @param config - Pattern configuration
200
+ * @returns Value between -1 and 1
201
+ */
202
+ export function plasmaPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
203
+ const { frequency, amplitude, speed } = config;
204
+ const t = time * speed;
205
+ // Classic plasma: sum of sines at different scales and rotations
206
+ const v1 = Math.sin(x * frequency + t);
207
+ const v2 = Math.sin(y * frequency + t * 0.7);
208
+ const v3 = Math.sin((x + y) * frequency * 0.5 + t * 0.5);
209
+ const v4 = Math.sin(Math.sqrt(x * x + y * y) * frequency * 0.3 + t * 0.8);
210
+ // Add some swirl
211
+ const cx = x - 40;
212
+ const cy = y - 20;
213
+ const v5 = Math.sin(Math.atan2(cy, cx) * 3 + t * 0.4);
214
+ return ((v1 + v2 + v3 + v4 + v5) / 5) * amplitude;
215
+ }
216
+ /**
217
+ * Vortex pattern - swirling spiral around center
218
+ *
219
+ * @param x - X coordinate
220
+ * @param y - Y coordinate
221
+ * @param centerX - Vortex center X
222
+ * @param centerY - Vortex center Y
223
+ * @param time - Time value for animation
224
+ * @param config - Pattern configuration
225
+ * @returns Value between -1 and 1
226
+ */
227
+ export function vortexPattern(x, y, centerX, centerY, time, config = DEFAULT_PATTERN_CONFIG) {
228
+ const { frequency, amplitude, speed } = config;
229
+ const dx = x - centerX;
230
+ const dy = y - centerY;
231
+ const distance = Math.sqrt(dx * dx + dy * dy);
232
+ const angle = Math.atan2(dy, dx);
233
+ // Spiral: angle + distance creates the swirl
234
+ const spiral = angle + distance * frequency * 0.1 - time * speed;
235
+ // Add some turbulence based on distance
236
+ const turbulence = perlinNoise2D(distance * 0.1, time * speed * 0.5) * 0.3;
237
+ return (Math.sin(spiral * 3) + turbulence) * amplitude;
238
+ }
239
+ /**
240
+ * Matrix pattern - falling columns like digital rain
241
+ *
242
+ * @param x - X coordinate (column)
243
+ * @param y - Y coordinate (row)
244
+ * @param time - Time value for animation
245
+ * @param config - Pattern configuration
246
+ * @returns Value between -1 and 1
247
+ */
248
+ export function matrixPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
249
+ const { frequency, amplitude, speed } = config;
250
+ // Each column has its own speed and phase based on x position
251
+ const columnSeed = seededNoise2D(x, 0, 42);
252
+ const columnSpeed = 0.5 + columnSeed * 1.5;
253
+ const columnPhase = columnSeed * 100;
254
+ // Calculate falling position
255
+ const fallPosition = (y * frequency + time * speed * columnSpeed + columnPhase) % 20;
256
+ // Create "head" of the rain drop (brightest) with trailing fade
257
+ const headBrightness = fallPosition < 1 ? 1 : 0;
258
+ const trailLength = 8;
259
+ const trailBrightness = fallPosition < trailLength ? Math.pow(1 - fallPosition / trailLength, 2) : 0;
260
+ // Add some randomness for flickering effect
261
+ const flicker = seededNoise2D(x, Math.floor(time * 10), y) * 0.2;
262
+ const value = Math.max(headBrightness, trailBrightness) + flicker;
263
+ return (value * 2 - 1) * amplitude;
264
+ }
265
+ /**
266
+ * Gradient pattern - smooth animated gradients
267
+ *
268
+ * @param x - X coordinate
269
+ * @param y - Y coordinate
270
+ * @param cols - Total columns (for normalization)
271
+ * @param rows - Total rows (for normalization)
272
+ * @param time - Time value for animation
273
+ * @param config - Pattern configuration
274
+ * @returns Value between -1 and 1
275
+ */
276
+ export function gradientPattern(x, y, cols, rows, time, config = DEFAULT_PATTERN_CONFIG) {
277
+ const { frequency, amplitude, speed } = config;
278
+ // Normalize coordinates to 0-1
279
+ const nx = x / cols;
280
+ const ny = y / rows;
281
+ // Animated angle for gradient direction
282
+ const angle = time * speed * 0.2;
283
+ const cos = Math.cos(angle);
284
+ const sin = Math.sin(angle);
285
+ // Rotate the gradient
286
+ const rotated = nx * cos + ny * sin;
287
+ // Add some wave distortion
288
+ const distortion = Math.sin(ny * Math.PI * 2 * frequency + time * speed) * 0.1;
289
+ const value = (rotated + distortion) * 2 - 1;
290
+ return Math.sin(value * Math.PI) * amplitude;
291
+ }
292
+ /**
293
+ * Diamond pattern - interference creating diamond shapes
294
+ *
295
+ * @param x - X coordinate
296
+ * @param y - Y coordinate
297
+ * @param time - Time value for animation
298
+ * @param config - Pattern configuration
299
+ * @returns Value between -1 and 1
300
+ */
301
+ export function diamondPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
302
+ const { frequency, amplitude, speed } = config;
303
+ const t = time * speed;
304
+ // Diamond pattern using absolute value (Manhattan distance creates diamonds)
305
+ const wave1 = Math.sin((Math.abs(x - 40) + Math.abs(y - 20)) * frequency + t);
306
+ const wave2 = Math.sin((Math.abs(x - 40) - Math.abs(y - 20)) * frequency * 0.7 - t * 0.8);
307
+ // Add some rotation over time
308
+ const angle = t * 0.1;
309
+ const rx = x * Math.cos(angle) - y * Math.sin(angle);
310
+ const ry = x * Math.sin(angle) + y * Math.cos(angle);
311
+ const wave3 = Math.sin((Math.abs(rx) + Math.abs(ry)) * frequency * 0.5 + t * 0.5);
312
+ return ((wave1 + wave2 + wave3) / 3) * amplitude;
313
+ }
314
+ /**
315
+ * Fractal pattern - animated Mandelbrot/Julia set
316
+ *
317
+ * @param x - X coordinate
318
+ * @param y - Y coordinate
319
+ * @param cols - Total columns (for centering)
320
+ * @param rows - Total rows (for centering)
321
+ * @param time - Time value for animation
322
+ * @param config - Pattern configuration
323
+ * @returns Value between -1 and 1
324
+ */
325
+ export function fractalPattern(x, y, cols, rows, time, config = DEFAULT_PATTERN_CONFIG) {
326
+ const { frequency, amplitude, speed } = config;
327
+ // Map to complex plane, centered and scaled
328
+ const scale = 3.5 * frequency;
329
+ const zx = ((x / cols) * scale - scale / 2) + Math.sin(time * speed * 0.1) * 0.2;
330
+ const zy = ((y / rows) * scale - scale / 2) + Math.cos(time * speed * 0.1) * 0.2;
331
+ // Julia set with animated c parameter
332
+ const cx = -0.7 + Math.sin(time * speed * 0.05) * 0.1;
333
+ const cy = 0.27 + Math.cos(time * speed * 0.07) * 0.1;
334
+ let zrx = zx;
335
+ let zry = zy;
336
+ const maxIter = 20;
337
+ let iter = 0;
338
+ // Iterate z = z² + c
339
+ while (zrx * zrx + zry * zry < 4 && iter < maxIter) {
340
+ const tmp = zrx * zrx - zry * zry + cx;
341
+ zry = 2 * zrx * zry + cy;
342
+ zrx = tmp;
343
+ iter++;
344
+ }
345
+ // Smooth coloring
346
+ if (iter === maxIter) {
347
+ return -1 * amplitude; // Inside the set
348
+ }
349
+ // Normalize iteration count to [-1, 1]
350
+ const smooth = iter - Math.log2(Math.log2(zrx * zrx + zry * zry));
351
+ return ((smooth / maxIter) * 2 - 1) * amplitude;
352
+ }
353
+ /**
354
+ * Generate a brightness grid for pattern rendering
355
+ *
356
+ * @param cols - Number of columns
357
+ * @param rows - Number of rows
358
+ * @param pattern - Pattern type
359
+ * @param time - Current time in seconds
360
+ * @param config - Pattern configuration
361
+ * @returns 2D array of brightness values (0-255)
362
+ */
363
+ export function generateBrightnessGrid(cols, rows, pattern, time = 0, config = DEFAULT_PATTERN_CONFIG) {
364
+ const grid = [];
365
+ const { frequency, amplitude, speed } = config;
366
+ for (let row = 0; row < rows; row++) {
367
+ grid[row] = [];
368
+ for (let col = 0; col < cols; col++) {
369
+ let value;
370
+ switch (pattern) {
371
+ case 'perlin':
372
+ value = perlinNoise2D(col * frequency + time * speed * 0.1, row * frequency + time * speed * 0.05);
373
+ break;
374
+ case 'fbm':
375
+ value = fbmNoise(col * frequency + time * speed * 0.1, row * frequency + time * speed * 0.05, 4, 0.5);
376
+ break;
377
+ case 'waves':
378
+ value = wavePattern(col, row, time, config);
379
+ break;
380
+ case 'ripple':
381
+ value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
382
+ break;
383
+ case 'static':
384
+ // For static, use time as seed for animated static
385
+ value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
386
+ break;
387
+ case 'clouds':
388
+ value = cloudsPattern(col, row, time, config);
389
+ break;
390
+ case 'plasma':
391
+ value = plasmaPattern(col, row, time, config);
392
+ break;
393
+ case 'vortex':
394
+ value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
395
+ break;
396
+ case 'matrix':
397
+ value = matrixPattern(col, row, time, config);
398
+ break;
399
+ case 'gradient':
400
+ value = gradientPattern(col, row, cols, rows, time, config);
401
+ break;
402
+ case 'diamond':
403
+ value = diamondPattern(col, row, time, config);
404
+ break;
405
+ case 'fractal':
406
+ value = fractalPattern(col, row, cols, rows, time, config);
407
+ break;
408
+ default:
409
+ value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
410
+ break;
411
+ }
412
+ // Normalize from [-1, 1] to [0, 255] with amplitude
413
+ const normalized = (value + 1) * 0.5 * amplitude;
414
+ const brightness = Math.max(0, Math.min(255, Math.floor(normalized * 255)));
415
+ grid[row][col] = brightness;
416
+ }
417
+ }
418
+ return grid;
419
+ }
420
+ /**
421
+ * Generate ImageData from a brightness grid
422
+ *
423
+ * @param grid - 2D array of brightness values
424
+ * @param cellWidth - Width of each cell
425
+ * @param cellHeight - Height of each cell
426
+ * @returns ImageData object
427
+ */
428
+ export function gridToImageData(grid, cellWidth, cellHeight) {
429
+ const rows = grid.length;
430
+ const cols = grid[0]?.length || 0;
431
+ const width = cols * cellWidth;
432
+ const height = rows * cellHeight;
433
+ const data = new Uint8ClampedArray(width * height * 4);
434
+ for (let row = 0; row < rows; row++) {
435
+ for (let col = 0; col < cols; col++) {
436
+ const brightness = grid[row][col];
437
+ // Fill cell region with brightness value
438
+ for (let cy = 0; cy < cellHeight; cy++) {
439
+ for (let cx = 0; cx < cellWidth; cx++) {
440
+ const px = ((row * cellHeight + cy) * width + (col * cellWidth + cx)) * 4;
441
+ data[px] = brightness; // R
442
+ data[px + 1] = brightness; // G
443
+ data[px + 2] = brightness; // B
444
+ data[px + 3] = 255; // A
445
+ }
446
+ }
447
+ }
448
+ }
449
+ return new ImageData(data, width, height);
450
+ }
451
+ /**
452
+ * Create a reusable brightness buffer
453
+ * Call once at init, then reuse with fillBrightnessBuffer
454
+ *
455
+ * @param cols - Number of columns
456
+ * @param rows - Number of rows
457
+ * @returns Reusable buffer object
458
+ */
459
+ export function createBrightnessBuffer(cols, rows) {
460
+ return {
461
+ data: new Uint8Array(cols * rows),
462
+ cols,
463
+ rows,
464
+ };
465
+ }
466
+ /**
467
+ * Fill an existing brightness buffer with pattern data (zero allocation)
468
+ * Use this in animation loops for best performance
469
+ *
470
+ * @param buffer - Pre-allocated buffer from createBrightnessBuffer
471
+ * @param pattern - Pattern type to generate
472
+ * @param time - Current time in seconds
473
+ * @param config - Pattern configuration
474
+ */
475
+ export function fillBrightnessBuffer(buffer, pattern, time = 0, config = DEFAULT_PATTERN_CONFIG) {
476
+ const { data, cols, rows } = buffer;
477
+ const { frequency, amplitude, speed } = config;
478
+ let idx = 0;
479
+ for (let row = 0; row < rows; row++) {
480
+ for (let col = 0; col < cols; col++) {
481
+ let value;
482
+ switch (pattern) {
483
+ case 'perlin':
484
+ value = perlinNoise2D(col * frequency + time * speed * 0.1, row * frequency + time * speed * 0.05);
485
+ break;
486
+ case 'fbm':
487
+ value = fbmNoise(col * frequency + time * speed * 0.1, row * frequency + time * speed * 0.05, 4, 0.5);
488
+ break;
489
+ case 'waves':
490
+ value = wavePattern(col, row, time, config);
491
+ break;
492
+ case 'ripple':
493
+ value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
494
+ break;
495
+ case 'static':
496
+ value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
497
+ break;
498
+ case 'clouds':
499
+ value = cloudsPattern(col, row, time, config);
500
+ break;
501
+ case 'plasma':
502
+ value = plasmaPattern(col, row, time, config);
503
+ break;
504
+ case 'vortex':
505
+ value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
506
+ break;
507
+ case 'matrix':
508
+ value = matrixPattern(col, row, time, config);
509
+ break;
510
+ case 'gradient':
511
+ value = gradientPattern(col, row, cols, rows, time, config);
512
+ break;
513
+ case 'diamond':
514
+ value = diamondPattern(col, row, time, config);
515
+ break;
516
+ case 'fractal':
517
+ value = fractalPattern(col, row, cols, rows, time, config);
518
+ break;
519
+ default:
520
+ value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
521
+ break;
522
+ }
523
+ // Normalize from [-1, 1] to [0, 255] with amplitude
524
+ // Using bitwise OR for fast floor: (x | 0) is faster than Math.floor(x)
525
+ const normalized = (value + 1) * 0.5 * amplitude;
526
+ data[idx++] = normalized * 255 | 0;
527
+ }
528
+ }
529
+ }
530
+ /**
531
+ * Get brightness value from buffer at (col, row)
532
+ * @param buffer - Brightness buffer
533
+ * @param col - Column index
534
+ * @param row - Row index
535
+ * @returns Brightness value 0-255
536
+ */
537
+ export function getBufferValue(buffer, col, row) {
538
+ return buffer.data[row * buffer.cols + col];
539
+ }