@autumnsgrove/gossamer 0.1.1 → 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 (51) 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.js +176 -0
  5. package/dist/characters.test.js +115 -0
  6. package/dist/colors.js +199 -0
  7. package/dist/index.js +79 -1850
  8. package/dist/index.test.js +92 -0
  9. package/dist/patterns.js +539 -0
  10. package/dist/patterns.test.js +223 -0
  11. package/dist/renderer.js +362 -0
  12. package/dist/svelte/GossamerBorder.svelte.d.ts +56 -1
  13. package/dist/svelte/GossamerBorder.svelte.d.ts.map +1 -0
  14. package/dist/svelte/GossamerClouds.svelte.d.ts +31 -1
  15. package/dist/svelte/GossamerClouds.svelte.d.ts.map +1 -0
  16. package/dist/svelte/GossamerImage.svelte.d.ts +28 -1
  17. package/dist/svelte/GossamerImage.svelte.d.ts.map +1 -0
  18. package/dist/svelte/GossamerOverlay.svelte.d.ts +32 -1
  19. package/dist/svelte/GossamerOverlay.svelte.d.ts.map +1 -0
  20. package/dist/svelte/GossamerText.svelte.d.ts +29 -1
  21. package/dist/svelte/GossamerText.svelte.d.ts.map +1 -0
  22. package/dist/svelte/index.js +31 -3646
  23. package/dist/svelte/presets.d.ts +4 -2
  24. package/dist/svelte/presets.js +161 -0
  25. package/dist/utils/canvas.js +139 -0
  26. package/dist/utils/image.js +195 -0
  27. package/dist/utils/performance.js +205 -0
  28. package/package.json +18 -22
  29. package/dist/index.js.map +0 -1
  30. package/dist/style.css +0 -124
  31. package/dist/svelte/index.js.map +0 -1
  32. package/src/animation.test.ts +0 -254
  33. package/src/animation.ts +0 -243
  34. package/src/characters.test.ts +0 -148
  35. package/src/characters.ts +0 -219
  36. package/src/colors.ts +0 -234
  37. package/src/index.test.ts +0 -115
  38. package/src/index.ts +0 -234
  39. package/src/patterns.test.ts +0 -273
  40. package/src/patterns.ts +0 -760
  41. package/src/renderer.ts +0 -470
  42. package/src/svelte/index.ts +0 -75
  43. package/src/svelte/presets.ts +0 -174
  44. package/src/utils/canvas.ts +0 -210
  45. package/src/utils/image.ts +0 -275
  46. package/src/utils/performance.ts +0 -282
  47. /package/{src → dist}/svelte/GossamerBorder.svelte +0 -0
  48. /package/{src → dist}/svelte/GossamerClouds.svelte +0 -0
  49. /package/{src → dist}/svelte/GossamerImage.svelte +0 -0
  50. /package/{src → dist}/svelte/GossamerOverlay.svelte +0 -0
  51. /package/{src → dist}/svelte/GossamerText.svelte +0 -0
package/src/patterns.ts DELETED
@@ -1,760 +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
- * Clouds pattern - soft, billowy fbm with gentle movement
216
- * The signature Gossamer effect!
217
- *
218
- * @param x - X coordinate
219
- * @param y - Y coordinate
220
- * @param time - Time value for animation
221
- * @param config - Pattern configuration
222
- * @returns Value between -1 and 1
223
- */
224
- export function cloudsPattern(
225
- x: number,
226
- y: number,
227
- time: number,
228
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
229
- ): number {
230
- const { frequency, amplitude, speed } = config;
231
-
232
- // Slow, drifting movement
233
- const drift = time * speed * 0.02;
234
- const nx = x * frequency * 0.5 + drift;
235
- const ny = y * frequency * 0.5 + drift * 0.7;
236
-
237
- // Layer multiple octaves with high persistence for soft, puffy look
238
- const base = fbmNoise(nx, ny, 5, 0.6);
239
-
240
- // Add subtle secondary drift layer
241
- const detail = fbmNoise(nx * 2 + drift * 0.5, ny * 2 - drift * 0.3, 3, 0.5) * 0.3;
242
-
243
- // Combine and bias toward lighter values (more sky, less dense cloud)
244
- const combined = base + detail;
245
- return Math.tanh(combined * 1.5) * amplitude;
246
- }
247
-
248
- /**
249
- * Plasma pattern - classic demoscene effect
250
- * Combines sine waves at different frequencies and phases
251
- *
252
- * @param x - X coordinate
253
- * @param y - Y coordinate
254
- * @param time - Time value for animation
255
- * @param config - Pattern configuration
256
- * @returns Value between -1 and 1
257
- */
258
- export function plasmaPattern(
259
- x: number,
260
- y: number,
261
- time: number,
262
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
263
- ): number {
264
- const { frequency, amplitude, speed } = config;
265
- const t = time * speed;
266
-
267
- // Classic plasma: sum of sines at different scales and rotations
268
- const v1 = Math.sin(x * frequency + t);
269
- const v2 = Math.sin(y * frequency + t * 0.7);
270
- const v3 = Math.sin((x + y) * frequency * 0.5 + t * 0.5);
271
- const v4 = Math.sin(Math.sqrt(x * x + y * y) * frequency * 0.3 + t * 0.8);
272
-
273
- // Add some swirl
274
- const cx = x - 40;
275
- const cy = y - 20;
276
- const v5 = Math.sin(Math.atan2(cy, cx) * 3 + t * 0.4);
277
-
278
- return ((v1 + v2 + v3 + v4 + v5) / 5) * amplitude;
279
- }
280
-
281
- /**
282
- * Vortex pattern - swirling spiral around center
283
- *
284
- * @param x - X coordinate
285
- * @param y - Y coordinate
286
- * @param centerX - Vortex center X
287
- * @param centerY - Vortex center Y
288
- * @param time - Time value for animation
289
- * @param config - Pattern configuration
290
- * @returns Value between -1 and 1
291
- */
292
- export function vortexPattern(
293
- x: number,
294
- y: number,
295
- centerX: number,
296
- centerY: number,
297
- time: number,
298
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
299
- ): number {
300
- const { frequency, amplitude, speed } = config;
301
-
302
- const dx = x - centerX;
303
- const dy = y - centerY;
304
- const distance = Math.sqrt(dx * dx + dy * dy);
305
- const angle = Math.atan2(dy, dx);
306
-
307
- // Spiral: angle + distance creates the swirl
308
- const spiral = angle + distance * frequency * 0.1 - time * speed;
309
-
310
- // Add some turbulence based on distance
311
- const turbulence = perlinNoise2D(distance * 0.1, time * speed * 0.5) * 0.3;
312
-
313
- return (Math.sin(spiral * 3) + turbulence) * amplitude;
314
- }
315
-
316
- /**
317
- * Matrix pattern - falling columns like digital rain
318
- *
319
- * @param x - X coordinate (column)
320
- * @param y - Y coordinate (row)
321
- * @param time - Time value for animation
322
- * @param config - Pattern configuration
323
- * @returns Value between -1 and 1
324
- */
325
- export function matrixPattern(
326
- x: number,
327
- y: number,
328
- time: number,
329
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
330
- ): number {
331
- const { frequency, amplitude, speed } = config;
332
-
333
- // Each column has its own speed and phase based on x position
334
- const columnSeed = seededNoise2D(x, 0, 42);
335
- const columnSpeed = 0.5 + columnSeed * 1.5;
336
- const columnPhase = columnSeed * 100;
337
-
338
- // Calculate falling position
339
- const fallPosition = (y * frequency + time * speed * columnSpeed + columnPhase) % 20;
340
-
341
- // Create "head" of the rain drop (brightest) with trailing fade
342
- const headBrightness = fallPosition < 1 ? 1 : 0;
343
- const trailLength = 8;
344
- const trailBrightness =
345
- fallPosition < trailLength ? Math.pow(1 - fallPosition / trailLength, 2) : 0;
346
-
347
- // Add some randomness for flickering effect
348
- const flicker = seededNoise2D(x, Math.floor(time * 10), y) * 0.2;
349
-
350
- const value = Math.max(headBrightness, trailBrightness) + flicker;
351
- return (value * 2 - 1) * amplitude;
352
- }
353
-
354
- /**
355
- * Gradient pattern - smooth animated gradients
356
- *
357
- * @param x - X coordinate
358
- * @param y - Y coordinate
359
- * @param cols - Total columns (for normalization)
360
- * @param rows - Total rows (for normalization)
361
- * @param time - Time value for animation
362
- * @param config - Pattern configuration
363
- * @returns Value between -1 and 1
364
- */
365
- export function gradientPattern(
366
- x: number,
367
- y: number,
368
- cols: number,
369
- rows: number,
370
- time: number,
371
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
372
- ): number {
373
- const { frequency, amplitude, speed } = config;
374
-
375
- // Normalize coordinates to 0-1
376
- const nx = x / cols;
377
- const ny = y / rows;
378
-
379
- // Animated angle for gradient direction
380
- const angle = time * speed * 0.2;
381
- const cos = Math.cos(angle);
382
- const sin = Math.sin(angle);
383
-
384
- // Rotate the gradient
385
- const rotated = nx * cos + ny * sin;
386
-
387
- // Add some wave distortion
388
- const distortion = Math.sin(ny * Math.PI * 2 * frequency + time * speed) * 0.1;
389
-
390
- const value = (rotated + distortion) * 2 - 1;
391
- return Math.sin(value * Math.PI) * amplitude;
392
- }
393
-
394
- /**
395
- * Diamond pattern - interference creating diamond shapes
396
- *
397
- * @param x - X coordinate
398
- * @param y - Y coordinate
399
- * @param time - Time value for animation
400
- * @param config - Pattern configuration
401
- * @returns Value between -1 and 1
402
- */
403
- export function diamondPattern(
404
- x: number,
405
- y: number,
406
- time: number,
407
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
408
- ): number {
409
- const { frequency, amplitude, speed } = config;
410
- const t = time * speed;
411
-
412
- // Diamond pattern using absolute value (Manhattan distance creates diamonds)
413
- const wave1 = Math.sin((Math.abs(x - 40) + Math.abs(y - 20)) * frequency + t);
414
- const wave2 = Math.sin((Math.abs(x - 40) - Math.abs(y - 20)) * frequency * 0.7 - t * 0.8);
415
-
416
- // Add some rotation over time
417
- const angle = t * 0.1;
418
- const rx = x * Math.cos(angle) - y * Math.sin(angle);
419
- const ry = x * Math.sin(angle) + y * Math.cos(angle);
420
- const wave3 = Math.sin((Math.abs(rx) + Math.abs(ry)) * frequency * 0.5 + t * 0.5);
421
-
422
- return ((wave1 + wave2 + wave3) / 3) * amplitude;
423
- }
424
-
425
- /**
426
- * Fractal pattern - animated Mandelbrot/Julia set
427
- *
428
- * @param x - X coordinate
429
- * @param y - Y coordinate
430
- * @param cols - Total columns (for centering)
431
- * @param rows - Total rows (for centering)
432
- * @param time - Time value for animation
433
- * @param config - Pattern configuration
434
- * @returns Value between -1 and 1
435
- */
436
- export function fractalPattern(
437
- x: number,
438
- y: number,
439
- cols: number,
440
- rows: number,
441
- time: number,
442
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
443
- ): number {
444
- const { frequency, amplitude, speed } = config;
445
-
446
- // Map to complex plane, centered and scaled
447
- const scale = 3.5 * frequency;
448
- const zx = ((x / cols) * scale - scale / 2) + Math.sin(time * speed * 0.1) * 0.2;
449
- const zy = ((y / rows) * scale - scale / 2) + Math.cos(time * speed * 0.1) * 0.2;
450
-
451
- // Julia set with animated c parameter
452
- const cx = -0.7 + Math.sin(time * speed * 0.05) * 0.1;
453
- const cy = 0.27 + Math.cos(time * speed * 0.07) * 0.1;
454
-
455
- let zrx = zx;
456
- let zry = zy;
457
- const maxIter = 20;
458
- let iter = 0;
459
-
460
- // Iterate z = z² + c
461
- while (zrx * zrx + zry * zry < 4 && iter < maxIter) {
462
- const tmp = zrx * zrx - zry * zry + cx;
463
- zry = 2 * zrx * zry + cy;
464
- zrx = tmp;
465
- iter++;
466
- }
467
-
468
- // Smooth coloring
469
- if (iter === maxIter) {
470
- return -1 * amplitude; // Inside the set
471
- }
472
-
473
- // Normalize iteration count to [-1, 1]
474
- const smooth = iter - Math.log2(Math.log2(zrx * zrx + zry * zry));
475
- return ((smooth / maxIter) * 2 - 1) * amplitude;
476
- }
477
-
478
- /**
479
- * Generate a brightness grid for pattern rendering
480
- *
481
- * @param cols - Number of columns
482
- * @param rows - Number of rows
483
- * @param pattern - Pattern type
484
- * @param time - Current time in seconds
485
- * @param config - Pattern configuration
486
- * @returns 2D array of brightness values (0-255)
487
- */
488
- export function generateBrightnessGrid(
489
- cols: number,
490
- rows: number,
491
- pattern: PatternType,
492
- time: number = 0,
493
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
494
- ): number[][] {
495
- const grid: number[][] = [];
496
- const { frequency, amplitude, speed } = config;
497
-
498
- for (let row = 0; row < rows; row++) {
499
- grid[row] = [];
500
- for (let col = 0; col < cols; col++) {
501
- let value: number;
502
-
503
- switch (pattern) {
504
- case 'perlin':
505
- value = perlinNoise2D(
506
- col * frequency + time * speed * 0.1,
507
- row * frequency + time * speed * 0.05
508
- );
509
- break;
510
-
511
- case 'fbm':
512
- value = fbmNoise(
513
- col * frequency + time * speed * 0.1,
514
- row * frequency + time * speed * 0.05,
515
- 4,
516
- 0.5
517
- );
518
- break;
519
-
520
- case 'waves':
521
- value = wavePattern(col, row, time, config);
522
- break;
523
-
524
- case 'ripple':
525
- value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
526
- break;
527
-
528
- case 'static':
529
- // For static, use time as seed for animated static
530
- value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
531
- break;
532
-
533
- case 'clouds':
534
- value = cloudsPattern(col, row, time, config);
535
- break;
536
-
537
- case 'plasma':
538
- value = plasmaPattern(col, row, time, config);
539
- break;
540
-
541
- case 'vortex':
542
- value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
543
- break;
544
-
545
- case 'matrix':
546
- value = matrixPattern(col, row, time, config);
547
- break;
548
-
549
- case 'gradient':
550
- value = gradientPattern(col, row, cols, rows, time, config);
551
- break;
552
-
553
- case 'diamond':
554
- value = diamondPattern(col, row, time, config);
555
- break;
556
-
557
- case 'fractal':
558
- value = fractalPattern(col, row, cols, rows, time, config);
559
- break;
560
-
561
- default:
562
- value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
563
- break;
564
- }
565
-
566
- // Normalize from [-1, 1] to [0, 255] with amplitude
567
- const normalized = (value + 1) * 0.5 * amplitude;
568
- const brightness = Math.max(0, Math.min(255, Math.floor(normalized * 255)));
569
- grid[row][col] = brightness;
570
- }
571
- }
572
-
573
- return grid;
574
- }
575
-
576
- /**
577
- * Generate ImageData from a brightness grid
578
- *
579
- * @param grid - 2D array of brightness values
580
- * @param cellWidth - Width of each cell
581
- * @param cellHeight - Height of each cell
582
- * @returns ImageData object
583
- */
584
- export function gridToImageData(grid: number[][], cellWidth: number, cellHeight: number): ImageData {
585
- const rows = grid.length;
586
- const cols = grid[0]?.length || 0;
587
- const width = cols * cellWidth;
588
- const height = rows * cellHeight;
589
- const data = new Uint8ClampedArray(width * height * 4);
590
-
591
- for (let row = 0; row < rows; row++) {
592
- for (let col = 0; col < cols; col++) {
593
- const brightness = grid[row][col];
594
-
595
- // Fill cell region with brightness value
596
- for (let cy = 0; cy < cellHeight; cy++) {
597
- for (let cx = 0; cx < cellWidth; cx++) {
598
- const px = ((row * cellHeight + cy) * width + (col * cellWidth + cx)) * 4;
599
- data[px] = brightness; // R
600
- data[px + 1] = brightness; // G
601
- data[px + 2] = brightness; // B
602
- data[px + 3] = 255; // A
603
- }
604
- }
605
- }
606
- }
607
-
608
- return new ImageData(data, width, height);
609
- }
610
-
611
- export type PatternType =
612
- | 'perlin'
613
- | 'waves'
614
- | 'static'
615
- | 'ripple'
616
- | 'fbm'
617
- | 'clouds'
618
- | 'plasma'
619
- | 'vortex'
620
- | 'matrix'
621
- | 'gradient'
622
- | 'diamond'
623
- | 'fractal';
624
-
625
- // ============================================================================
626
- // PERFORMANCE-OPTIMIZED API
627
- // ============================================================================
628
-
629
- /**
630
- * Reusable brightness buffer using flat Uint8Array
631
- * ~30% faster than number[][] due to contiguous memory and no GC pressure
632
- */
633
- export interface BrightnessBuffer {
634
- /** Flat array of brightness values (0-255), row-major order */
635
- data: Uint8Array;
636
- /** Number of columns */
637
- cols: number;
638
- /** Number of rows */
639
- rows: number;
640
- }
641
-
642
- /**
643
- * Create a reusable brightness buffer
644
- * Call once at init, then reuse with fillBrightnessBuffer
645
- *
646
- * @param cols - Number of columns
647
- * @param rows - Number of rows
648
- * @returns Reusable buffer object
649
- */
650
- export function createBrightnessBuffer(cols: number, rows: number): BrightnessBuffer {
651
- return {
652
- data: new Uint8Array(cols * rows),
653
- cols,
654
- rows,
655
- };
656
- }
657
-
658
- /**
659
- * Fill an existing brightness buffer with pattern data (zero allocation)
660
- * Use this in animation loops for best performance
661
- *
662
- * @param buffer - Pre-allocated buffer from createBrightnessBuffer
663
- * @param pattern - Pattern type to generate
664
- * @param time - Current time in seconds
665
- * @param config - Pattern configuration
666
- */
667
- export function fillBrightnessBuffer(
668
- buffer: BrightnessBuffer,
669
- pattern: PatternType,
670
- time: number = 0,
671
- config: PatternConfig = DEFAULT_PATTERN_CONFIG
672
- ): void {
673
- const { data, cols, rows } = buffer;
674
- const { frequency, amplitude, speed } = config;
675
-
676
- let idx = 0;
677
- for (let row = 0; row < rows; row++) {
678
- for (let col = 0; col < cols; col++) {
679
- let value: number;
680
-
681
- switch (pattern) {
682
- case 'perlin':
683
- value = perlinNoise2D(
684
- col * frequency + time * speed * 0.1,
685
- row * frequency + time * speed * 0.05
686
- );
687
- break;
688
-
689
- case 'fbm':
690
- value = fbmNoise(
691
- col * frequency + time * speed * 0.1,
692
- row * frequency + time * speed * 0.05,
693
- 4,
694
- 0.5
695
- );
696
- break;
697
-
698
- case 'waves':
699
- value = wavePattern(col, row, time, config);
700
- break;
701
-
702
- case 'ripple':
703
- value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
704
- break;
705
-
706
- case 'static':
707
- value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
708
- break;
709
-
710
- case 'clouds':
711
- value = cloudsPattern(col, row, time, config);
712
- break;
713
-
714
- case 'plasma':
715
- value = plasmaPattern(col, row, time, config);
716
- break;
717
-
718
- case 'vortex':
719
- value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
720
- break;
721
-
722
- case 'matrix':
723
- value = matrixPattern(col, row, time, config);
724
- break;
725
-
726
- case 'gradient':
727
- value = gradientPattern(col, row, cols, rows, time, config);
728
- break;
729
-
730
- case 'diamond':
731
- value = diamondPattern(col, row, time, config);
732
- break;
733
-
734
- case 'fractal':
735
- value = fractalPattern(col, row, cols, rows, time, config);
736
- break;
737
-
738
- default:
739
- value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
740
- break;
741
- }
742
-
743
- // Normalize from [-1, 1] to [0, 255] with amplitude
744
- // Using bitwise OR for fast floor: (x | 0) is faster than Math.floor(x)
745
- const normalized = (value + 1) * 0.5 * amplitude;
746
- data[idx++] = normalized * 255 | 0;
747
- }
748
- }
749
- }
750
-
751
- /**
752
- * Get brightness value from buffer at (col, row)
753
- * @param buffer - Brightness buffer
754
- * @param col - Column index
755
- * @param row - Row index
756
- * @returns Brightness value 0-255
757
- */
758
- export function getBufferValue(buffer: BrightnessBuffer, col: number, row: number): number {
759
- return buffer.data[row * buffer.cols + col];
760
- }