@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.
- package/LICENSE +21 -0
- package/dist/animation.js +165 -0
- package/dist/animation.test.js +204 -0
- package/dist/characters.js +176 -0
- package/dist/characters.test.js +115 -0
- package/dist/colors.js +199 -0
- package/dist/index.js +79 -1850
- package/dist/index.test.js +92 -0
- package/dist/patterns.js +539 -0
- package/dist/patterns.test.js +223 -0
- package/dist/renderer.js +362 -0
- package/dist/svelte/GossamerBorder.svelte.d.ts +56 -1
- package/dist/svelte/GossamerBorder.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerClouds.svelte.d.ts +31 -1
- package/dist/svelte/GossamerClouds.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerImage.svelte.d.ts +28 -1
- package/dist/svelte/GossamerImage.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerOverlay.svelte.d.ts +32 -1
- package/dist/svelte/GossamerOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/GossamerText.svelte.d.ts +29 -1
- package/dist/svelte/GossamerText.svelte.d.ts.map +1 -0
- package/dist/svelte/index.js +31 -3646
- package/dist/svelte/presets.d.ts +4 -2
- package/dist/svelte/presets.js +161 -0
- package/dist/utils/canvas.js +139 -0
- package/dist/utils/image.js +195 -0
- package/dist/utils/performance.js +205 -0
- package/package.json +18 -22
- package/dist/index.js.map +0 -1
- package/dist/style.css +0 -124
- package/dist/svelte/index.js.map +0 -1
- package/src/animation.test.ts +0 -254
- package/src/animation.ts +0 -243
- package/src/characters.test.ts +0 -148
- package/src/characters.ts +0 -219
- package/src/colors.ts +0 -234
- package/src/index.test.ts +0 -115
- package/src/index.ts +0 -234
- package/src/patterns.test.ts +0 -273
- package/src/patterns.ts +0 -760
- package/src/renderer.ts +0 -470
- package/src/svelte/index.ts +0 -75
- package/src/svelte/presets.ts +0 -174
- package/src/utils/canvas.ts +0 -210
- package/src/utils/image.ts +0 -275
- package/src/utils/performance.ts +0 -282
- /package/{src → dist}/svelte/GossamerBorder.svelte +0 -0
- /package/{src → dist}/svelte/GossamerClouds.svelte +0 -0
- /package/{src → dist}/svelte/GossamerImage.svelte +0 -0
- /package/{src → dist}/svelte/GossamerOverlay.svelte +0 -0
- /package/{src → dist}/svelte/GossamerText.svelte +0 -0
package/dist/index.js
CHANGED
|
@@ -1,1851 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
canvas,
|
|
28
|
-
...DEFAULT_RENDER_CONFIG,
|
|
29
|
-
...config
|
|
30
|
-
};
|
|
31
|
-
this.setupCanvas();
|
|
32
|
-
this.buildCharacterAtlas();
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Build character texture atlas for fast rendering
|
|
36
|
-
* Pre-renders all characters to an offscreen canvas, then uses drawImage
|
|
37
|
-
* instead of fillText for 5-10x faster rendering
|
|
38
|
-
*/
|
|
39
|
-
buildCharacterAtlas() {
|
|
40
|
-
const { characters, cellWidth, cellHeight, color, fontFamily } = this.config;
|
|
41
|
-
if (this.atlasCharacters === characters && this.charAtlas) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const atlasWidth = characters.length * cellWidth;
|
|
45
|
-
const atlasHeight = cellHeight;
|
|
46
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
47
|
-
this.charAtlas = new OffscreenCanvas(atlasWidth, atlasHeight);
|
|
48
|
-
} else {
|
|
49
|
-
this.charAtlas = document.createElement("canvas");
|
|
50
|
-
this.charAtlas.width = atlasWidth;
|
|
51
|
-
this.charAtlas.height = atlasHeight;
|
|
52
|
-
}
|
|
53
|
-
const ctx = this.charAtlas.getContext("2d");
|
|
54
|
-
if (!ctx) {
|
|
55
|
-
this.charAtlas = null;
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
ctx.clearRect(0, 0, atlasWidth, atlasHeight);
|
|
59
|
-
ctx.fillStyle = color;
|
|
60
|
-
ctx.font = `${cellHeight}px ${fontFamily}`;
|
|
61
|
-
ctx.textBaseline = "top";
|
|
62
|
-
for (let i = 0; i < characters.length; i++) {
|
|
63
|
-
const char = characters[i];
|
|
64
|
-
if (char !== " ") {
|
|
65
|
-
ctx.fillText(char, i * cellWidth, 0);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
this.atlasCharacters = characters;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Set up the canvas with optimal rendering settings
|
|
72
|
-
*/
|
|
73
|
-
setupCanvas() {
|
|
74
|
-
const { fontFamily, cellHeight } = this.config;
|
|
75
|
-
this.ctx.font = `${cellHeight}px ${fontFamily}`;
|
|
76
|
-
this.ctx.textBaseline = "top";
|
|
77
|
-
this.ctx.imageSmoothingEnabled = true;
|
|
78
|
-
this.ctx.imageSmoothingQuality = "high";
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Update the renderer configuration
|
|
82
|
-
*/
|
|
83
|
-
updateConfig(config) {
|
|
84
|
-
const needsAtlasRebuild = config.characters !== void 0 || config.color !== void 0 || config.cellWidth !== void 0 || config.cellHeight !== void 0 || config.fontFamily !== void 0;
|
|
85
|
-
this.config = { ...this.config, ...config };
|
|
86
|
-
this.setupCanvas();
|
|
87
|
-
if (needsAtlasRebuild) {
|
|
88
|
-
this.atlasCharacters = "";
|
|
89
|
-
this.buildCharacterAtlas();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resize the canvas to match new dimensions
|
|
94
|
-
*/
|
|
95
|
-
resize(width, height) {
|
|
96
|
-
const { canvas } = this.config;
|
|
97
|
-
canvas.width = width;
|
|
98
|
-
canvas.height = height;
|
|
99
|
-
this.setupCanvas();
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Get the current canvas dimensions
|
|
103
|
-
*/
|
|
104
|
-
getDimensions() {
|
|
105
|
-
return {
|
|
106
|
-
width: this.config.canvas.width,
|
|
107
|
-
height: this.config.canvas.height
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Calculate the number of cells that fit in the canvas
|
|
112
|
-
*/
|
|
113
|
-
getCellCount() {
|
|
114
|
-
const { width, height } = this.getDimensions();
|
|
115
|
-
const { cellWidth, cellHeight } = this.config;
|
|
116
|
-
return {
|
|
117
|
-
cols: Math.ceil(width / cellWidth),
|
|
118
|
-
rows: Math.ceil(height / cellHeight)
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Clear the canvas
|
|
123
|
-
*/
|
|
124
|
-
clear() {
|
|
125
|
-
const { canvas, backgroundColor } = this.config;
|
|
126
|
-
if (backgroundColor) {
|
|
127
|
-
this.ctx.fillStyle = backgroundColor;
|
|
128
|
-
this.ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
129
|
-
} else {
|
|
130
|
-
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Render a single frame from image data
|
|
135
|
-
*/
|
|
136
|
-
renderFrame(imageData) {
|
|
137
|
-
const { canvas, characters, cellWidth, cellHeight, color, brightnessFunction } = this.config;
|
|
138
|
-
const { width, data } = imageData;
|
|
139
|
-
this.clear();
|
|
140
|
-
this.ctx.fillStyle = color;
|
|
141
|
-
for (let y = 0; y < canvas.height; y += cellHeight) {
|
|
142
|
-
for (let x = 0; x < canvas.width; x += cellWidth) {
|
|
143
|
-
const brightness = this.getCellBrightness(data, x, y, width, cellWidth, cellHeight, brightnessFunction);
|
|
144
|
-
const charIndex = Math.floor(brightness / 255 * (characters.length - 1));
|
|
145
|
-
const char = characters[Math.min(charIndex, characters.length - 1)];
|
|
146
|
-
if (char !== " ") {
|
|
147
|
-
this.ctx.fillText(char, x, y);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Render ASCII from a brightness grid (for pattern-based rendering)
|
|
154
|
-
*/
|
|
155
|
-
renderFromBrightnessGrid(grid) {
|
|
156
|
-
const { characters, cellWidth, cellHeight, color } = this.config;
|
|
157
|
-
this.clear();
|
|
158
|
-
this.ctx.fillStyle = color;
|
|
159
|
-
for (let row = 0; row < grid.length; row++) {
|
|
160
|
-
for (let col = 0; col < grid[row].length; col++) {
|
|
161
|
-
const brightness = grid[row][col];
|
|
162
|
-
const charIndex = Math.floor(brightness / 255 * (characters.length - 1));
|
|
163
|
-
const char = characters[Math.min(charIndex, characters.length - 1)];
|
|
164
|
-
if (char !== " ") {
|
|
165
|
-
this.ctx.fillText(char, col * cellWidth, row * cellHeight);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Render ASCII with per-cell colors (for colored image rendering)
|
|
172
|
-
*/
|
|
173
|
-
renderWithColors(data) {
|
|
174
|
-
this.clear();
|
|
175
|
-
for (const { char, color, x, y } of data) {
|
|
176
|
-
if (char !== " ") {
|
|
177
|
-
this.ctx.fillStyle = color;
|
|
178
|
-
this.ctx.fillText(char, x, y);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* PERFORMANCE: Render from BrightnessBuffer using texture atlas
|
|
184
|
-
*
|
|
185
|
-
* Uses pre-rendered character sprites instead of fillText calls.
|
|
186
|
-
* 5-10x faster than renderFromBrightnessGrid for large canvases.
|
|
187
|
-
*
|
|
188
|
-
* @param buffer - BrightnessBuffer from fillBrightnessBuffer
|
|
189
|
-
*/
|
|
190
|
-
renderFromBuffer(buffer) {
|
|
191
|
-
const { characters, cellWidth, cellHeight } = this.config;
|
|
192
|
-
this.clear();
|
|
193
|
-
if (!this.charAtlas) {
|
|
194
|
-
this.ctx.fillStyle = this.config.color;
|
|
195
|
-
const charLen2 = characters.length - 1;
|
|
196
|
-
let idx2 = 0;
|
|
197
|
-
for (let row = 0; row < buffer.rows; row++) {
|
|
198
|
-
for (let col = 0; col < buffer.cols; col++) {
|
|
199
|
-
const brightness = buffer.data[idx2++];
|
|
200
|
-
const charIndex = brightness / 255 * charLen2 | 0;
|
|
201
|
-
const char = characters[Math.min(charIndex, charLen2)];
|
|
202
|
-
if (char !== " ") {
|
|
203
|
-
this.ctx.fillText(char, col * cellWidth, row * cellHeight);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const charLen = characters.length - 1;
|
|
210
|
-
let idx = 0;
|
|
211
|
-
for (let row = 0; row < buffer.rows; row++) {
|
|
212
|
-
const y = row * cellHeight;
|
|
213
|
-
for (let col = 0; col < buffer.cols; col++) {
|
|
214
|
-
const brightness = buffer.data[idx++];
|
|
215
|
-
const charIndex = brightness / 255 * charLen | 0;
|
|
216
|
-
if (charIndex === 0 && characters[0] === " ") {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
this.ctx.drawImage(
|
|
220
|
-
this.charAtlas,
|
|
221
|
-
charIndex * cellWidth,
|
|
222
|
-
0,
|
|
223
|
-
cellWidth,
|
|
224
|
-
cellHeight,
|
|
225
|
-
// source
|
|
226
|
-
col * cellWidth,
|
|
227
|
-
y,
|
|
228
|
-
cellWidth,
|
|
229
|
-
cellHeight
|
|
230
|
-
// destination
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* PERFORMANCE: Render brightness grid using atlas (legacy grid format)
|
|
237
|
-
*
|
|
238
|
-
* @param grid - 2D array of brightness values
|
|
239
|
-
*/
|
|
240
|
-
renderGridFast(grid) {
|
|
241
|
-
const { characters, cellWidth, cellHeight } = this.config;
|
|
242
|
-
this.clear();
|
|
243
|
-
if (!this.charAtlas) {
|
|
244
|
-
this.renderFromBrightnessGrid(grid);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const charLen = characters.length - 1;
|
|
248
|
-
for (let row = 0; row < grid.length; row++) {
|
|
249
|
-
const y = row * cellHeight;
|
|
250
|
-
const rowData = grid[row];
|
|
251
|
-
for (let col = 0; col < rowData.length; col++) {
|
|
252
|
-
const brightness = rowData[col];
|
|
253
|
-
const charIndex = brightness / 255 * charLen | 0;
|
|
254
|
-
if (charIndex === 0 && characters[0] === " ") {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
this.ctx.drawImage(
|
|
258
|
-
this.charAtlas,
|
|
259
|
-
charIndex * cellWidth,
|
|
260
|
-
0,
|
|
261
|
-
cellWidth,
|
|
262
|
-
cellHeight,
|
|
263
|
-
col * cellWidth,
|
|
264
|
-
y,
|
|
265
|
-
cellWidth,
|
|
266
|
-
cellHeight
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Calculate average brightness for a cell region
|
|
273
|
-
*/
|
|
274
|
-
getCellBrightness(data, startX, startY, imageWidth, cellWidth, cellHeight, brightnessFunction) {
|
|
275
|
-
let total = 0;
|
|
276
|
-
let count = 0;
|
|
277
|
-
for (let cy = 0; cy < cellHeight; cy++) {
|
|
278
|
-
for (let cx = 0; cx < cellWidth; cx++) {
|
|
279
|
-
const px = ((startY + cy) * imageWidth + (startX + cx)) * 4;
|
|
280
|
-
if (px >= 0 && px + 2 < data.length) {
|
|
281
|
-
total += brightnessFunction(data[px], data[px + 1], data[px + 2]);
|
|
282
|
-
count++;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return count > 0 ? total / count : 0;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Start an animation loop with FPS limiting
|
|
290
|
-
*/
|
|
291
|
-
startAnimation(updateFn, fps = 30) {
|
|
292
|
-
if (this.isRunning) {
|
|
293
|
-
this.stopAnimation();
|
|
294
|
-
}
|
|
295
|
-
this.isRunning = true;
|
|
296
|
-
const frameInterval = 1e3 / fps;
|
|
297
|
-
this.lastFrameTime = performance.now();
|
|
298
|
-
const animate = (currentTime) => {
|
|
299
|
-
if (!this.isRunning) return;
|
|
300
|
-
const deltaTime = currentTime - this.lastFrameTime;
|
|
301
|
-
if (deltaTime >= frameInterval) {
|
|
302
|
-
const result = updateFn(currentTime, deltaTime);
|
|
303
|
-
if (result instanceof ImageData) {
|
|
304
|
-
this.renderFrame(result);
|
|
305
|
-
} else {
|
|
306
|
-
this.renderFromBrightnessGrid(result);
|
|
307
|
-
}
|
|
308
|
-
this.lastFrameTime = currentTime - deltaTime % frameInterval;
|
|
309
|
-
}
|
|
310
|
-
this.animationId = requestAnimationFrame(animate);
|
|
311
|
-
};
|
|
312
|
-
this.animationId = requestAnimationFrame(animate);
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Stop the animation loop
|
|
316
|
-
*/
|
|
317
|
-
stopAnimation() {
|
|
318
|
-
this.isRunning = false;
|
|
319
|
-
if (this.animationId !== null) {
|
|
320
|
-
cancelAnimationFrame(this.animationId);
|
|
321
|
-
this.animationId = null;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Check if animation is currently running
|
|
326
|
-
*/
|
|
327
|
-
isAnimating() {
|
|
328
|
-
return this.isRunning;
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Pause animation (can be resumed)
|
|
332
|
-
*/
|
|
333
|
-
pause() {
|
|
334
|
-
this.isRunning = false;
|
|
335
|
-
if (this.animationId !== null) {
|
|
336
|
-
cancelAnimationFrame(this.animationId);
|
|
337
|
-
this.animationId = null;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Clean up and destroy the renderer
|
|
342
|
-
*/
|
|
343
|
-
destroy() {
|
|
344
|
-
this.stopAnimation();
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
const DEFAULT_PATTERN_CONFIG = {
|
|
348
|
-
frequency: 0.05,
|
|
349
|
-
amplitude: 1,
|
|
350
|
-
speed: 0.5
|
|
351
|
-
};
|
|
352
|
-
const PERMUTATION = new Uint8Array(512);
|
|
353
|
-
const P = new Uint8Array([
|
|
354
|
-
151,
|
|
355
|
-
160,
|
|
356
|
-
137,
|
|
357
|
-
91,
|
|
358
|
-
90,
|
|
359
|
-
15,
|
|
360
|
-
131,
|
|
361
|
-
13,
|
|
362
|
-
201,
|
|
363
|
-
95,
|
|
364
|
-
96,
|
|
365
|
-
53,
|
|
366
|
-
194,
|
|
367
|
-
233,
|
|
368
|
-
7,
|
|
369
|
-
225,
|
|
370
|
-
140,
|
|
371
|
-
36,
|
|
372
|
-
103,
|
|
373
|
-
30,
|
|
374
|
-
69,
|
|
375
|
-
142,
|
|
376
|
-
8,
|
|
377
|
-
99,
|
|
378
|
-
37,
|
|
379
|
-
240,
|
|
380
|
-
21,
|
|
381
|
-
10,
|
|
382
|
-
23,
|
|
383
|
-
190,
|
|
384
|
-
6,
|
|
385
|
-
148,
|
|
386
|
-
247,
|
|
387
|
-
120,
|
|
388
|
-
234,
|
|
389
|
-
75,
|
|
390
|
-
0,
|
|
391
|
-
26,
|
|
392
|
-
197,
|
|
393
|
-
62,
|
|
394
|
-
94,
|
|
395
|
-
252,
|
|
396
|
-
219,
|
|
397
|
-
203,
|
|
398
|
-
117,
|
|
399
|
-
35,
|
|
400
|
-
11,
|
|
401
|
-
32,
|
|
402
|
-
57,
|
|
403
|
-
177,
|
|
404
|
-
33,
|
|
405
|
-
88,
|
|
406
|
-
237,
|
|
407
|
-
149,
|
|
408
|
-
56,
|
|
409
|
-
87,
|
|
410
|
-
174,
|
|
411
|
-
20,
|
|
412
|
-
125,
|
|
413
|
-
136,
|
|
414
|
-
171,
|
|
415
|
-
168,
|
|
416
|
-
68,
|
|
417
|
-
175,
|
|
418
|
-
74,
|
|
419
|
-
165,
|
|
420
|
-
71,
|
|
421
|
-
134,
|
|
422
|
-
139,
|
|
423
|
-
48,
|
|
424
|
-
27,
|
|
425
|
-
166,
|
|
426
|
-
77,
|
|
427
|
-
146,
|
|
428
|
-
158,
|
|
429
|
-
231,
|
|
430
|
-
83,
|
|
431
|
-
111,
|
|
432
|
-
229,
|
|
433
|
-
122,
|
|
434
|
-
60,
|
|
435
|
-
211,
|
|
436
|
-
133,
|
|
437
|
-
230,
|
|
438
|
-
220,
|
|
439
|
-
105,
|
|
440
|
-
92,
|
|
441
|
-
41,
|
|
442
|
-
55,
|
|
443
|
-
46,
|
|
444
|
-
245,
|
|
445
|
-
40,
|
|
446
|
-
244,
|
|
447
|
-
102,
|
|
448
|
-
143,
|
|
449
|
-
54,
|
|
450
|
-
65,
|
|
451
|
-
25,
|
|
452
|
-
63,
|
|
453
|
-
161,
|
|
454
|
-
1,
|
|
455
|
-
216,
|
|
456
|
-
80,
|
|
457
|
-
73,
|
|
458
|
-
209,
|
|
459
|
-
76,
|
|
460
|
-
132,
|
|
461
|
-
187,
|
|
462
|
-
208,
|
|
463
|
-
89,
|
|
464
|
-
18,
|
|
465
|
-
169,
|
|
466
|
-
200,
|
|
467
|
-
196,
|
|
468
|
-
135,
|
|
469
|
-
130,
|
|
470
|
-
116,
|
|
471
|
-
188,
|
|
472
|
-
159,
|
|
473
|
-
86,
|
|
474
|
-
164,
|
|
475
|
-
100,
|
|
476
|
-
109,
|
|
477
|
-
198,
|
|
478
|
-
173,
|
|
479
|
-
186,
|
|
480
|
-
3,
|
|
481
|
-
64,
|
|
482
|
-
52,
|
|
483
|
-
217,
|
|
484
|
-
226,
|
|
485
|
-
250,
|
|
486
|
-
124,
|
|
487
|
-
123,
|
|
488
|
-
5,
|
|
489
|
-
202,
|
|
490
|
-
38,
|
|
491
|
-
147,
|
|
492
|
-
118,
|
|
493
|
-
126,
|
|
494
|
-
255,
|
|
495
|
-
82,
|
|
496
|
-
85,
|
|
497
|
-
212,
|
|
498
|
-
207,
|
|
499
|
-
206,
|
|
500
|
-
59,
|
|
501
|
-
227,
|
|
502
|
-
47,
|
|
503
|
-
16,
|
|
504
|
-
58,
|
|
505
|
-
17,
|
|
506
|
-
182,
|
|
507
|
-
189,
|
|
508
|
-
28,
|
|
509
|
-
42,
|
|
510
|
-
223,
|
|
511
|
-
183,
|
|
512
|
-
170,
|
|
513
|
-
213,
|
|
514
|
-
119,
|
|
515
|
-
248,
|
|
516
|
-
152,
|
|
517
|
-
2,
|
|
518
|
-
44,
|
|
519
|
-
154,
|
|
520
|
-
163,
|
|
521
|
-
70,
|
|
522
|
-
221,
|
|
523
|
-
153,
|
|
524
|
-
101,
|
|
525
|
-
155,
|
|
526
|
-
167,
|
|
527
|
-
43,
|
|
528
|
-
172,
|
|
529
|
-
9,
|
|
530
|
-
129,
|
|
531
|
-
22,
|
|
532
|
-
39,
|
|
533
|
-
253,
|
|
534
|
-
19,
|
|
535
|
-
98,
|
|
536
|
-
108,
|
|
537
|
-
110,
|
|
538
|
-
79,
|
|
539
|
-
113,
|
|
540
|
-
224,
|
|
541
|
-
232,
|
|
542
|
-
178,
|
|
543
|
-
185,
|
|
544
|
-
112,
|
|
545
|
-
104,
|
|
546
|
-
218,
|
|
547
|
-
246,
|
|
548
|
-
97,
|
|
549
|
-
228,
|
|
550
|
-
251,
|
|
551
|
-
34,
|
|
552
|
-
242,
|
|
553
|
-
193,
|
|
554
|
-
238,
|
|
555
|
-
210,
|
|
556
|
-
144,
|
|
557
|
-
12,
|
|
558
|
-
191,
|
|
559
|
-
179,
|
|
560
|
-
162,
|
|
561
|
-
241,
|
|
562
|
-
81,
|
|
563
|
-
51,
|
|
564
|
-
145,
|
|
565
|
-
235,
|
|
566
|
-
249,
|
|
567
|
-
14,
|
|
568
|
-
239,
|
|
569
|
-
107,
|
|
570
|
-
49,
|
|
571
|
-
192,
|
|
572
|
-
214,
|
|
573
|
-
31,
|
|
574
|
-
181,
|
|
575
|
-
199,
|
|
576
|
-
106,
|
|
577
|
-
157,
|
|
578
|
-
184,
|
|
579
|
-
84,
|
|
580
|
-
204,
|
|
581
|
-
176,
|
|
582
|
-
115,
|
|
583
|
-
121,
|
|
584
|
-
50,
|
|
585
|
-
45,
|
|
586
|
-
127,
|
|
587
|
-
4,
|
|
588
|
-
150,
|
|
589
|
-
254,
|
|
590
|
-
138,
|
|
591
|
-
236,
|
|
592
|
-
205,
|
|
593
|
-
93,
|
|
594
|
-
222,
|
|
595
|
-
114,
|
|
596
|
-
67,
|
|
597
|
-
29,
|
|
598
|
-
24,
|
|
599
|
-
72,
|
|
600
|
-
243,
|
|
601
|
-
141,
|
|
602
|
-
128,
|
|
603
|
-
195,
|
|
604
|
-
78,
|
|
605
|
-
66,
|
|
606
|
-
215,
|
|
607
|
-
61,
|
|
608
|
-
156,
|
|
609
|
-
180
|
|
610
|
-
]);
|
|
611
|
-
for (let i = 0; i < 256; i++) {
|
|
612
|
-
PERMUTATION[i] = P[i];
|
|
613
|
-
PERMUTATION[i + 256] = P[i];
|
|
614
|
-
}
|
|
615
|
-
function fade(t) {
|
|
616
|
-
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
617
|
-
}
|
|
618
|
-
function lerp(a, b, t) {
|
|
619
|
-
return a + t * (b - a);
|
|
620
|
-
}
|
|
621
|
-
function grad(hash, x, y) {
|
|
622
|
-
const h = hash & 3;
|
|
623
|
-
const u = h < 2 ? x : y;
|
|
624
|
-
const v = h < 2 ? y : x;
|
|
625
|
-
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
|
626
|
-
}
|
|
627
|
-
function perlinNoise2D(x, y) {
|
|
628
|
-
const xi = Math.floor(x) & 255;
|
|
629
|
-
const yi = Math.floor(y) & 255;
|
|
630
|
-
const xf = x - Math.floor(x);
|
|
631
|
-
const yf = y - Math.floor(y);
|
|
632
|
-
const u = fade(xf);
|
|
633
|
-
const v = fade(yf);
|
|
634
|
-
const aa = PERMUTATION[PERMUTATION[xi] + yi];
|
|
635
|
-
const ab = PERMUTATION[PERMUTATION[xi] + yi + 1];
|
|
636
|
-
const ba = PERMUTATION[PERMUTATION[xi + 1] + yi];
|
|
637
|
-
const bb = PERMUTATION[PERMUTATION[xi + 1] + yi + 1];
|
|
638
|
-
const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
|
|
639
|
-
const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);
|
|
640
|
-
return lerp(x1, x2, v);
|
|
641
|
-
}
|
|
642
|
-
function fbmNoise(x, y, octaves = 4, persistence = 0.5) {
|
|
643
|
-
let total = 0;
|
|
644
|
-
let frequency = 1;
|
|
645
|
-
let amplitude = 1;
|
|
646
|
-
let maxValue = 0;
|
|
647
|
-
for (let i = 0; i < octaves; i++) {
|
|
648
|
-
total += perlinNoise2D(x * frequency, y * frequency) * amplitude;
|
|
649
|
-
maxValue += amplitude;
|
|
650
|
-
amplitude *= persistence;
|
|
651
|
-
frequency *= 2;
|
|
652
|
-
}
|
|
653
|
-
return total / maxValue;
|
|
654
|
-
}
|
|
655
|
-
function wavePattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
656
|
-
const { frequency, amplitude, speed } = config;
|
|
657
|
-
const wave1 = Math.sin(x * frequency + time * speed);
|
|
658
|
-
const wave2 = Math.cos(y * frequency + time * speed * 0.7);
|
|
659
|
-
const wave3 = Math.sin((x + y) * frequency * 0.5 + time * speed * 0.5);
|
|
660
|
-
return (wave1 + wave2 + wave3) / 3 * amplitude;
|
|
661
|
-
}
|
|
662
|
-
function ripplePattern(x, y, centerX, centerY, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
663
|
-
const { frequency, amplitude, speed } = config;
|
|
664
|
-
const dx = x - centerX;
|
|
665
|
-
const dy = y - centerY;
|
|
666
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
667
|
-
return Math.sin(distance * frequency - time * speed) * amplitude;
|
|
668
|
-
}
|
|
669
|
-
function staticNoise(seed) {
|
|
670
|
-
if (seed !== void 0) {
|
|
671
|
-
const x = Math.sin(seed * 12.9898) * 43758.5453;
|
|
672
|
-
return x - Math.floor(x);
|
|
673
|
-
}
|
|
674
|
-
return Math.random();
|
|
675
|
-
}
|
|
676
|
-
function seededNoise2D(x, y, seed = 0) {
|
|
677
|
-
const n = Math.sin(x * 12.9898 + y * 78.233 + seed) * 43758.5453;
|
|
678
|
-
return n - Math.floor(n);
|
|
679
|
-
}
|
|
680
|
-
function cloudsPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
681
|
-
const { frequency, amplitude, speed } = config;
|
|
682
|
-
const drift = time * speed * 0.02;
|
|
683
|
-
const nx = x * frequency * 0.5 + drift;
|
|
684
|
-
const ny = y * frequency * 0.5 + drift * 0.7;
|
|
685
|
-
const base = fbmNoise(nx, ny, 5, 0.6);
|
|
686
|
-
const detail = fbmNoise(nx * 2 + drift * 0.5, ny * 2 - drift * 0.3, 3, 0.5) * 0.3;
|
|
687
|
-
const combined = base + detail;
|
|
688
|
-
return Math.tanh(combined * 1.5) * amplitude;
|
|
689
|
-
}
|
|
690
|
-
function plasmaPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
691
|
-
const { frequency, amplitude, speed } = config;
|
|
692
|
-
const t = time * speed;
|
|
693
|
-
const v1 = Math.sin(x * frequency + t);
|
|
694
|
-
const v2 = Math.sin(y * frequency + t * 0.7);
|
|
695
|
-
const v3 = Math.sin((x + y) * frequency * 0.5 + t * 0.5);
|
|
696
|
-
const v4 = Math.sin(Math.sqrt(x * x + y * y) * frequency * 0.3 + t * 0.8);
|
|
697
|
-
const cx = x - 40;
|
|
698
|
-
const cy = y - 20;
|
|
699
|
-
const v5 = Math.sin(Math.atan2(cy, cx) * 3 + t * 0.4);
|
|
700
|
-
return (v1 + v2 + v3 + v4 + v5) / 5 * amplitude;
|
|
701
|
-
}
|
|
702
|
-
function vortexPattern(x, y, centerX, centerY, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
703
|
-
const { frequency, amplitude, speed } = config;
|
|
704
|
-
const dx = x - centerX;
|
|
705
|
-
const dy = y - centerY;
|
|
706
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
707
|
-
const angle = Math.atan2(dy, dx);
|
|
708
|
-
const spiral = angle + distance * frequency * 0.1 - time * speed;
|
|
709
|
-
const turbulence = perlinNoise2D(distance * 0.1, time * speed * 0.5) * 0.3;
|
|
710
|
-
return (Math.sin(spiral * 3) + turbulence) * amplitude;
|
|
711
|
-
}
|
|
712
|
-
function matrixPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
713
|
-
const { frequency, amplitude, speed } = config;
|
|
714
|
-
const columnSeed = seededNoise2D(x, 0, 42);
|
|
715
|
-
const columnSpeed = 0.5 + columnSeed * 1.5;
|
|
716
|
-
const columnPhase = columnSeed * 100;
|
|
717
|
-
const fallPosition = (y * frequency + time * speed * columnSpeed + columnPhase) % 20;
|
|
718
|
-
const headBrightness = fallPosition < 1 ? 1 : 0;
|
|
719
|
-
const trailLength = 8;
|
|
720
|
-
const trailBrightness = fallPosition < trailLength ? Math.pow(1 - fallPosition / trailLength, 2) : 0;
|
|
721
|
-
const flicker = seededNoise2D(x, Math.floor(time * 10), y) * 0.2;
|
|
722
|
-
const value = Math.max(headBrightness, trailBrightness) + flicker;
|
|
723
|
-
return (value * 2 - 1) * amplitude;
|
|
724
|
-
}
|
|
725
|
-
function gradientPattern(x, y, cols, rows, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
726
|
-
const { frequency, amplitude, speed } = config;
|
|
727
|
-
const nx = x / cols;
|
|
728
|
-
const ny = y / rows;
|
|
729
|
-
const angle = time * speed * 0.2;
|
|
730
|
-
const cos = Math.cos(angle);
|
|
731
|
-
const sin = Math.sin(angle);
|
|
732
|
-
const rotated = nx * cos + ny * sin;
|
|
733
|
-
const distortion = Math.sin(ny * Math.PI * 2 * frequency + time * speed) * 0.1;
|
|
734
|
-
const value = (rotated + distortion) * 2 - 1;
|
|
735
|
-
return Math.sin(value * Math.PI) * amplitude;
|
|
736
|
-
}
|
|
737
|
-
function diamondPattern(x, y, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
738
|
-
const { frequency, amplitude, speed } = config;
|
|
739
|
-
const t = time * speed;
|
|
740
|
-
const wave1 = Math.sin((Math.abs(x - 40) + Math.abs(y - 20)) * frequency + t);
|
|
741
|
-
const wave2 = Math.sin((Math.abs(x - 40) - Math.abs(y - 20)) * frequency * 0.7 - t * 0.8);
|
|
742
|
-
const angle = t * 0.1;
|
|
743
|
-
const rx = x * Math.cos(angle) - y * Math.sin(angle);
|
|
744
|
-
const ry = x * Math.sin(angle) + y * Math.cos(angle);
|
|
745
|
-
const wave3 = Math.sin((Math.abs(rx) + Math.abs(ry)) * frequency * 0.5 + t * 0.5);
|
|
746
|
-
return (wave1 + wave2 + wave3) / 3 * amplitude;
|
|
747
|
-
}
|
|
748
|
-
function fractalPattern(x, y, cols, rows, time, config = DEFAULT_PATTERN_CONFIG) {
|
|
749
|
-
const { frequency, amplitude, speed } = config;
|
|
750
|
-
const scale = 3.5 * frequency;
|
|
751
|
-
const zx = x / cols * scale - scale / 2 + Math.sin(time * speed * 0.1) * 0.2;
|
|
752
|
-
const zy = y / rows * scale - scale / 2 + Math.cos(time * speed * 0.1) * 0.2;
|
|
753
|
-
const cx = -0.7 + Math.sin(time * speed * 0.05) * 0.1;
|
|
754
|
-
const cy = 0.27 + Math.cos(time * speed * 0.07) * 0.1;
|
|
755
|
-
let zrx = zx;
|
|
756
|
-
let zry = zy;
|
|
757
|
-
const maxIter = 20;
|
|
758
|
-
let iter = 0;
|
|
759
|
-
while (zrx * zrx + zry * zry < 4 && iter < maxIter) {
|
|
760
|
-
const tmp = zrx * zrx - zry * zry + cx;
|
|
761
|
-
zry = 2 * zrx * zry + cy;
|
|
762
|
-
zrx = tmp;
|
|
763
|
-
iter++;
|
|
764
|
-
}
|
|
765
|
-
if (iter === maxIter) {
|
|
766
|
-
return -1 * amplitude;
|
|
767
|
-
}
|
|
768
|
-
const smooth = iter - Math.log2(Math.log2(zrx * zrx + zry * zry));
|
|
769
|
-
return (smooth / maxIter * 2 - 1) * amplitude;
|
|
770
|
-
}
|
|
771
|
-
function generateBrightnessGrid(cols, rows, pattern, time = 0, config = DEFAULT_PATTERN_CONFIG) {
|
|
772
|
-
const grid = [];
|
|
773
|
-
const { frequency, amplitude, speed } = config;
|
|
774
|
-
for (let row = 0; row < rows; row++) {
|
|
775
|
-
grid[row] = [];
|
|
776
|
-
for (let col = 0; col < cols; col++) {
|
|
777
|
-
let value;
|
|
778
|
-
switch (pattern) {
|
|
779
|
-
case "perlin":
|
|
780
|
-
value = perlinNoise2D(
|
|
781
|
-
col * frequency + time * speed * 0.1,
|
|
782
|
-
row * frequency + time * speed * 0.05
|
|
783
|
-
);
|
|
784
|
-
break;
|
|
785
|
-
case "fbm":
|
|
786
|
-
value = fbmNoise(
|
|
787
|
-
col * frequency + time * speed * 0.1,
|
|
788
|
-
row * frequency + time * speed * 0.05,
|
|
789
|
-
4,
|
|
790
|
-
0.5
|
|
791
|
-
);
|
|
792
|
-
break;
|
|
793
|
-
case "waves":
|
|
794
|
-
value = wavePattern(col, row, time, config);
|
|
795
|
-
break;
|
|
796
|
-
case "ripple":
|
|
797
|
-
value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
|
|
798
|
-
break;
|
|
799
|
-
case "static":
|
|
800
|
-
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
801
|
-
break;
|
|
802
|
-
case "clouds":
|
|
803
|
-
value = cloudsPattern(col, row, time, config);
|
|
804
|
-
break;
|
|
805
|
-
case "plasma":
|
|
806
|
-
value = plasmaPattern(col, row, time, config);
|
|
807
|
-
break;
|
|
808
|
-
case "vortex":
|
|
809
|
-
value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
|
|
810
|
-
break;
|
|
811
|
-
case "matrix":
|
|
812
|
-
value = matrixPattern(col, row, time, config);
|
|
813
|
-
break;
|
|
814
|
-
case "gradient":
|
|
815
|
-
value = gradientPattern(col, row, cols, rows, time, config);
|
|
816
|
-
break;
|
|
817
|
-
case "diamond":
|
|
818
|
-
value = diamondPattern(col, row, time, config);
|
|
819
|
-
break;
|
|
820
|
-
case "fractal":
|
|
821
|
-
value = fractalPattern(col, row, cols, rows, time, config);
|
|
822
|
-
break;
|
|
823
|
-
default:
|
|
824
|
-
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
const normalized = (value + 1) * 0.5 * amplitude;
|
|
828
|
-
const brightness = Math.max(0, Math.min(255, Math.floor(normalized * 255)));
|
|
829
|
-
grid[row][col] = brightness;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
return grid;
|
|
833
|
-
}
|
|
834
|
-
function gridToImageData(grid, cellWidth, cellHeight) {
|
|
835
|
-
var _a;
|
|
836
|
-
const rows = grid.length;
|
|
837
|
-
const cols = ((_a = grid[0]) == null ? void 0 : _a.length) || 0;
|
|
838
|
-
const width = cols * cellWidth;
|
|
839
|
-
const height = rows * cellHeight;
|
|
840
|
-
const data = new Uint8ClampedArray(width * height * 4);
|
|
841
|
-
for (let row = 0; row < rows; row++) {
|
|
842
|
-
for (let col = 0; col < cols; col++) {
|
|
843
|
-
const brightness = grid[row][col];
|
|
844
|
-
for (let cy = 0; cy < cellHeight; cy++) {
|
|
845
|
-
for (let cx = 0; cx < cellWidth; cx++) {
|
|
846
|
-
const px = ((row * cellHeight + cy) * width + (col * cellWidth + cx)) * 4;
|
|
847
|
-
data[px] = brightness;
|
|
848
|
-
data[px + 1] = brightness;
|
|
849
|
-
data[px + 2] = brightness;
|
|
850
|
-
data[px + 3] = 255;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return new ImageData(data, width, height);
|
|
856
|
-
}
|
|
857
|
-
function createBrightnessBuffer(cols, rows) {
|
|
858
|
-
return {
|
|
859
|
-
data: new Uint8Array(cols * rows),
|
|
860
|
-
cols,
|
|
861
|
-
rows
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
function fillBrightnessBuffer(buffer, pattern, time = 0, config = DEFAULT_PATTERN_CONFIG) {
|
|
865
|
-
const { data, cols, rows } = buffer;
|
|
866
|
-
const { frequency, amplitude, speed } = config;
|
|
867
|
-
let idx = 0;
|
|
868
|
-
for (let row = 0; row < rows; row++) {
|
|
869
|
-
for (let col = 0; col < cols; col++) {
|
|
870
|
-
let value;
|
|
871
|
-
switch (pattern) {
|
|
872
|
-
case "perlin":
|
|
873
|
-
value = perlinNoise2D(
|
|
874
|
-
col * frequency + time * speed * 0.1,
|
|
875
|
-
row * frequency + time * speed * 0.05
|
|
876
|
-
);
|
|
877
|
-
break;
|
|
878
|
-
case "fbm":
|
|
879
|
-
value = fbmNoise(
|
|
880
|
-
col * frequency + time * speed * 0.1,
|
|
881
|
-
row * frequency + time * speed * 0.05,
|
|
882
|
-
4,
|
|
883
|
-
0.5
|
|
884
|
-
);
|
|
885
|
-
break;
|
|
886
|
-
case "waves":
|
|
887
|
-
value = wavePattern(col, row, time, config);
|
|
888
|
-
break;
|
|
889
|
-
case "ripple":
|
|
890
|
-
value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
|
|
891
|
-
break;
|
|
892
|
-
case "static":
|
|
893
|
-
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
894
|
-
break;
|
|
895
|
-
case "clouds":
|
|
896
|
-
value = cloudsPattern(col, row, time, config);
|
|
897
|
-
break;
|
|
898
|
-
case "plasma":
|
|
899
|
-
value = plasmaPattern(col, row, time, config);
|
|
900
|
-
break;
|
|
901
|
-
case "vortex":
|
|
902
|
-
value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
|
|
903
|
-
break;
|
|
904
|
-
case "matrix":
|
|
905
|
-
value = matrixPattern(col, row, time, config);
|
|
906
|
-
break;
|
|
907
|
-
case "gradient":
|
|
908
|
-
value = gradientPattern(col, row, cols, rows, time, config);
|
|
909
|
-
break;
|
|
910
|
-
case "diamond":
|
|
911
|
-
value = diamondPattern(col, row, time, config);
|
|
912
|
-
break;
|
|
913
|
-
case "fractal":
|
|
914
|
-
value = fractalPattern(col, row, cols, rows, time, config);
|
|
915
|
-
break;
|
|
916
|
-
default:
|
|
917
|
-
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
918
|
-
break;
|
|
919
|
-
}
|
|
920
|
-
const normalized = (value + 1) * 0.5 * amplitude;
|
|
921
|
-
data[idx++] = normalized * 255 | 0;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function getBufferValue(buffer, col, row) {
|
|
926
|
-
return buffer.data[row * buffer.cols + col];
|
|
927
|
-
}
|
|
928
|
-
const CHARACTER_SETS = {
|
|
929
|
-
standard: {
|
|
930
|
-
name: "Standard",
|
|
931
|
-
description: "Classic ASCII art character set",
|
|
932
|
-
characters: " .:-=+*#%@",
|
|
933
|
-
bestFor: ["general", "images", "patterns"]
|
|
934
|
-
},
|
|
935
|
-
dense: {
|
|
936
|
-
name: "Dense",
|
|
937
|
-
description: "High detail character set with many gradations",
|
|
938
|
-
characters: " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
|
|
939
|
-
bestFor: ["detailed images", "portraits", "high contrast"]
|
|
940
|
-
},
|
|
941
|
-
minimal: {
|
|
942
|
-
name: "Minimal",
|
|
943
|
-
description: "Clean and simple with few characters",
|
|
944
|
-
characters: " .:*#",
|
|
945
|
-
bestFor: ["backgrounds", "subtle effects", "clean aesthetic"]
|
|
946
|
-
},
|
|
947
|
-
grove: {
|
|
948
|
-
name: "Grove",
|
|
949
|
-
description: "Organic, soft characters inspired by nature",
|
|
950
|
-
characters: " ·∙•◦○◉●",
|
|
951
|
-
bestFor: ["organic patterns", "soft backgrounds", "nature themes"]
|
|
952
|
-
},
|
|
953
|
-
dots: {
|
|
954
|
-
name: "Dots",
|
|
955
|
-
description: "Braille-like dot patterns",
|
|
956
|
-
characters: " ⋅∘∙●",
|
|
957
|
-
bestFor: ["stipple effects", "pointillism", "dotted textures"]
|
|
958
|
-
},
|
|
959
|
-
blocks: {
|
|
960
|
-
name: "Blocks",
|
|
961
|
-
description: "Block-based characters for sharp edges",
|
|
962
|
-
characters: " ░▒▓█",
|
|
963
|
-
bestFor: ["retro effects", "pixel art", "bold patterns"]
|
|
964
|
-
},
|
|
965
|
-
lines: {
|
|
966
|
-
name: "Lines",
|
|
967
|
-
description: "Line-based characters for directional effects",
|
|
968
|
-
characters: " -─═╌│┃",
|
|
969
|
-
bestFor: ["rain effects", "streaks", "motion blur"]
|
|
970
|
-
},
|
|
971
|
-
stars: {
|
|
972
|
-
name: "Stars",
|
|
973
|
-
description: "Star and sparkle characters",
|
|
974
|
-
characters: " ·✧✦✫✬✯★",
|
|
975
|
-
bestFor: ["sparkle effects", "night sky", "magical themes"]
|
|
976
|
-
},
|
|
977
|
-
nature: {
|
|
978
|
-
name: "Nature",
|
|
979
|
-
description: "Nature-themed decorative characters",
|
|
980
|
-
characters: " .~≈∿⌇☘",
|
|
981
|
-
bestFor: ["decorative", "themed effects", "organic patterns"]
|
|
982
|
-
},
|
|
983
|
-
weather: {
|
|
984
|
-
name: "Weather",
|
|
985
|
-
description: "Weather-related symbols",
|
|
986
|
-
characters: " ·.:*❄❅❆",
|
|
987
|
-
bestFor: ["snow effects", "weather simulations", "seasonal themes"]
|
|
988
|
-
},
|
|
989
|
-
binary: {
|
|
990
|
-
name: "Binary",
|
|
991
|
-
description: "Digital-style binary characters",
|
|
992
|
-
characters: " 01",
|
|
993
|
-
bestFor: ["digital effects", "matrix style", "tech themes"]
|
|
994
|
-
},
|
|
995
|
-
math: {
|
|
996
|
-
name: "Math",
|
|
997
|
-
description: "Mathematical symbols",
|
|
998
|
-
characters: " +-×÷=≠≈∞",
|
|
999
|
-
bestFor: ["abstract patterns", "tech themes", "decorative"]
|
|
1000
|
-
},
|
|
1001
|
-
// ==========================================================================
|
|
1002
|
-
// GLASS-OPTIMIZED CHARACTER SETS
|
|
1003
|
-
// Designed for subtle overlays on Glass components
|
|
1004
|
-
// More characters = more visible gradations
|
|
1005
|
-
// ==========================================================================
|
|
1006
|
-
"glass-dots": {
|
|
1007
|
-
name: "Glass Dots",
|
|
1008
|
-
description: "Soft dot gradations for glass overlays",
|
|
1009
|
-
characters: " ·∘∙○•●",
|
|
1010
|
-
bestFor: ["glass overlays", "subtle backgrounds", "mist effects"]
|
|
1011
|
-
},
|
|
1012
|
-
"glass-mist": {
|
|
1013
|
-
name: "Glass Mist",
|
|
1014
|
-
description: "Ethereal mist effect for glass",
|
|
1015
|
-
characters: " .·∙•◦○◉●",
|
|
1016
|
-
bestFor: ["glass overlays", "fog effects", "ambient backgrounds"]
|
|
1017
|
-
},
|
|
1018
|
-
"glass-dust": {
|
|
1019
|
-
name: "Glass Dust",
|
|
1020
|
-
description: "Floating dust particles",
|
|
1021
|
-
characters: " ˙·∘°•◦○",
|
|
1022
|
-
bestFor: ["glass overlays", "particle effects", "light scatter"]
|
|
1023
|
-
},
|
|
1024
|
-
"glass-soft": {
|
|
1025
|
-
name: "Glass Soft",
|
|
1026
|
-
description: "Soft block gradations for glass",
|
|
1027
|
-
characters: " ·░▒▓",
|
|
1028
|
-
bestFor: ["glass overlays", "soft gradients", "frosted effect"]
|
|
1029
|
-
},
|
|
1030
|
-
"glass-sparkle": {
|
|
1031
|
-
name: "Glass Sparkle",
|
|
1032
|
-
description: "Subtle sparkles for glass",
|
|
1033
|
-
characters: " ·.✧✦✫★",
|
|
1034
|
-
bestFor: ["glass overlays", "highlight effects", "magical themes"]
|
|
1035
|
-
},
|
|
1036
|
-
"glass-wave": {
|
|
1037
|
-
name: "Glass Wave",
|
|
1038
|
-
description: "Flowing wave patterns for glass",
|
|
1039
|
-
characters: " .~∼≈≋",
|
|
1040
|
-
bestFor: ["glass overlays", "water effects", "flowing motion"]
|
|
1041
|
-
},
|
|
1042
|
-
"glass-organic": {
|
|
1043
|
-
name: "Glass Organic",
|
|
1044
|
-
description: "Natural, organic feel for glass",
|
|
1045
|
-
characters: " .·:;∘○◦•●",
|
|
1046
|
-
bestFor: ["glass overlays", "nature themes", "grove aesthetic"]
|
|
1047
|
-
}
|
|
1048
|
-
};
|
|
1049
|
-
function getCharacterSet(name) {
|
|
1050
|
-
return CHARACTER_SETS[name];
|
|
1051
|
-
}
|
|
1052
|
-
function getCharacters(name) {
|
|
1053
|
-
var _a;
|
|
1054
|
-
return ((_a = CHARACTER_SETS[name]) == null ? void 0 : _a.characters) || CHARACTER_SETS.standard.characters;
|
|
1055
|
-
}
|
|
1056
|
-
function getCharacterSetNames() {
|
|
1057
|
-
return Object.keys(CHARACTER_SETS);
|
|
1058
|
-
}
|
|
1059
|
-
function createCharacterSet(name, characters, description = "", bestFor = []) {
|
|
1060
|
-
return {
|
|
1061
|
-
name,
|
|
1062
|
-
description,
|
|
1063
|
-
characters,
|
|
1064
|
-
bestFor
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
function validateCharacterSet(characters) {
|
|
1068
|
-
if (characters.length < 2) return false;
|
|
1069
|
-
if (characters[0] !== " ") return false;
|
|
1070
|
-
return true;
|
|
1071
|
-
}
|
|
1072
|
-
function invertCharacters(characters) {
|
|
1073
|
-
return characters.split("").reverse().join("");
|
|
1074
|
-
}
|
|
1075
|
-
const GROVE_GREEN = {
|
|
1076
|
-
50: { hex: "#f0fdf4", name: "Grove Green 50", glassOpacity: 0.3 },
|
|
1077
|
-
100: { hex: "#dcfce7", name: "Grove Green 100", glassOpacity: 0.25 },
|
|
1078
|
-
200: { hex: "#bbf7d0", name: "Grove Green 200", glassOpacity: 0.2 },
|
|
1079
|
-
300: { hex: "#86efac", name: "Grove Green 300", glassOpacity: 0.18 },
|
|
1080
|
-
400: { hex: "#4ade80", name: "Grove Green 400", glassOpacity: 0.15 },
|
|
1081
|
-
500: { hex: "#22c55e", name: "Grove Green 500", glassOpacity: 0.12 },
|
|
1082
|
-
600: { hex: "#16a34a", name: "Grove Green 600", glassOpacity: 0.1 },
|
|
1083
|
-
700: { hex: "#15803d", name: "Grove Green 700", glassOpacity: 0.1 },
|
|
1084
|
-
800: { hex: "#166534", name: "Grove Green 800", glassOpacity: 0.08 },
|
|
1085
|
-
900: { hex: "#14532d", name: "Grove Green 900", glassOpacity: 0.08 }
|
|
1086
|
-
};
|
|
1087
|
-
const CREAM = {
|
|
1088
|
-
50: { hex: "#fefdfb", name: "Cream 50", glassOpacity: 0.4 },
|
|
1089
|
-
100: { hex: "#fdfcf8", name: "Cream 100", glassOpacity: 0.35 },
|
|
1090
|
-
200: { hex: "#faf8f3", name: "Cream 200", glassOpacity: 0.3 },
|
|
1091
|
-
300: { hex: "#f5f2ea", name: "Cream 300", glassOpacity: 0.25 },
|
|
1092
|
-
400: { hex: "#ede9de", name: "Cream 400", glassOpacity: 0.2 },
|
|
1093
|
-
500: { hex: "#e2ddd0", name: "Cream 500", glassOpacity: 0.18 },
|
|
1094
|
-
600: { hex: "#d4cec0", name: "Cream 600", glassOpacity: 0.15 },
|
|
1095
|
-
700: { hex: "#c4bdb0", name: "Cream 700", glassOpacity: 0.12 },
|
|
1096
|
-
800: { hex: "#b0a99c", name: "Cream 800", glassOpacity: 0.1 },
|
|
1097
|
-
900: { hex: "#9a9387", name: "Cream 900", glassOpacity: 0.08 }
|
|
1098
|
-
};
|
|
1099
|
-
const BARK = {
|
|
1100
|
-
50: { hex: "#faf7f5", name: "Bark 50", glassOpacity: 0.3 },
|
|
1101
|
-
100: { hex: "#f0ebe6", name: "Bark 100", glassOpacity: 0.25 },
|
|
1102
|
-
200: { hex: "#e0d5cc", name: "Bark 200", glassOpacity: 0.2 },
|
|
1103
|
-
300: { hex: "#ccb59c", name: "Bark 300", glassOpacity: 0.18 },
|
|
1104
|
-
400: { hex: "#b89a7a", name: "Bark 400", glassOpacity: 0.15 },
|
|
1105
|
-
500: { hex: "#a57c5a", name: "Bark 500", glassOpacity: 0.12 },
|
|
1106
|
-
600: { hex: "#8a6344", name: "Bark 600", glassOpacity: 0.1 },
|
|
1107
|
-
700: { hex: "#6f4d39", name: "Bark 700", glassOpacity: 0.1 },
|
|
1108
|
-
800: { hex: "#553a2a", name: "Bark 800", glassOpacity: 0.08 },
|
|
1109
|
-
900: { hex: "#3d2914", name: "Bark 900", glassOpacity: 0.06 }
|
|
1110
|
-
};
|
|
1111
|
-
const STATUS = {
|
|
1112
|
-
success: { hex: "#22c55e", name: "Success", glassOpacity: 0.12 },
|
|
1113
|
-
warning: { hex: "#f59e0b", name: "Warning", glassOpacity: 0.12 },
|
|
1114
|
-
error: { hex: "#dc2626", name: "Error", glassOpacity: 0.1 },
|
|
1115
|
-
info: { hex: "#0ea5e9", name: "Info", glassOpacity: 0.12 }
|
|
1116
|
-
};
|
|
1117
|
-
const GROVE_COLORS = {
|
|
1118
|
-
// Greens
|
|
1119
|
-
grove: GROVE_GREEN[500].hex,
|
|
1120
|
-
"grove-light": GROVE_GREEN[300].hex,
|
|
1121
|
-
"grove-dark": GROVE_GREEN[700].hex,
|
|
1122
|
-
"grove-muted": GROVE_GREEN[400].hex,
|
|
1123
|
-
// Creams
|
|
1124
|
-
cream: CREAM[50].hex,
|
|
1125
|
-
"cream-warm": CREAM[200].hex,
|
|
1126
|
-
"cream-deep": CREAM[500].hex,
|
|
1127
|
-
// Barks
|
|
1128
|
-
bark: BARK[900].hex,
|
|
1129
|
-
"bark-light": BARK[500].hex,
|
|
1130
|
-
"bark-medium": BARK[700].hex,
|
|
1131
|
-
// Utility
|
|
1132
|
-
white: "#ffffff",
|
|
1133
|
-
black: "#000000",
|
|
1134
|
-
transparent: "transparent"
|
|
1135
|
-
};
|
|
1136
|
-
const GLASS_SCHEMES = {
|
|
1137
|
-
// Light mode schemes (on light backgrounds)
|
|
1138
|
-
"grove-mist": {
|
|
1139
|
-
color: GROVE_GREEN[500].hex,
|
|
1140
|
-
background: "transparent",
|
|
1141
|
-
opacity: 0.12,
|
|
1142
|
-
description: "Subtle green mist for light glass"
|
|
1143
|
-
},
|
|
1144
|
-
"cream-haze": {
|
|
1145
|
-
color: CREAM[600].hex,
|
|
1146
|
-
background: "transparent",
|
|
1147
|
-
opacity: 0.15,
|
|
1148
|
-
description: "Warm cream haze for cozy glass"
|
|
1149
|
-
},
|
|
1150
|
-
"bark-shadow": {
|
|
1151
|
-
color: BARK[700].hex,
|
|
1152
|
-
background: "transparent",
|
|
1153
|
-
opacity: 0.08,
|
|
1154
|
-
description: "Soft earth shadow for depth"
|
|
1155
|
-
},
|
|
1156
|
-
// Dark mode schemes (on dark backgrounds)
|
|
1157
|
-
"grove-glow": {
|
|
1158
|
-
color: GROVE_GREEN[400].hex,
|
|
1159
|
-
background: "#1a1915",
|
|
1160
|
-
opacity: 0.15,
|
|
1161
|
-
description: "Glowing green for dark glass"
|
|
1162
|
-
},
|
|
1163
|
-
"cream-dust": {
|
|
1164
|
-
color: CREAM[300].hex,
|
|
1165
|
-
background: "#1a1915",
|
|
1166
|
-
opacity: 0.1,
|
|
1167
|
-
description: "Dusty cream particles in dark"
|
|
1168
|
-
},
|
|
1169
|
-
"moonlight": {
|
|
1170
|
-
color: "#e2e8f0",
|
|
1171
|
-
background: "#1a1915",
|
|
1172
|
-
opacity: 0.08,
|
|
1173
|
-
description: "Cool moonlight glow"
|
|
1174
|
-
},
|
|
1175
|
-
// Accent schemes
|
|
1176
|
-
"spring-fresh": {
|
|
1177
|
-
color: GROVE_GREEN[300].hex,
|
|
1178
|
-
background: "transparent",
|
|
1179
|
-
opacity: 0.18,
|
|
1180
|
-
description: "Fresh spring green overlay"
|
|
1181
|
-
},
|
|
1182
|
-
"autumn-warm": {
|
|
1183
|
-
color: "#d97706",
|
|
1184
|
-
background: "transparent",
|
|
1185
|
-
opacity: 0.1,
|
|
1186
|
-
description: "Warm autumn amber tones"
|
|
1187
|
-
},
|
|
1188
|
-
"winter-frost": {
|
|
1189
|
-
color: "#93c5fd",
|
|
1190
|
-
background: "transparent",
|
|
1191
|
-
opacity: 0.12,
|
|
1192
|
-
description: "Cool frost blue overlay"
|
|
1193
|
-
}
|
|
1194
|
-
};
|
|
1195
|
-
function getGroveColor(name) {
|
|
1196
|
-
if (name in GROVE_COLORS) {
|
|
1197
|
-
return GROVE_COLORS[name];
|
|
1198
|
-
}
|
|
1199
|
-
if (name.startsWith("#")) {
|
|
1200
|
-
return name;
|
|
1201
|
-
}
|
|
1202
|
-
return GROVE_COLORS.grove;
|
|
1203
|
-
}
|
|
1204
|
-
function getGlassScheme(name) {
|
|
1205
|
-
if (name in GLASS_SCHEMES) {
|
|
1206
|
-
const scheme = GLASS_SCHEMES[name];
|
|
1207
|
-
return {
|
|
1208
|
-
color: scheme.color,
|
|
1209
|
-
background: scheme.background,
|
|
1210
|
-
opacity: scheme.opacity
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
return {
|
|
1214
|
-
color: GLASS_SCHEMES["grove-mist"].color,
|
|
1215
|
-
background: GLASS_SCHEMES["grove-mist"].background,
|
|
1216
|
-
opacity: GLASS_SCHEMES["grove-mist"].opacity
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
1219
|
-
function getGroveColorNames() {
|
|
1220
|
-
return Object.keys(GROVE_COLORS);
|
|
1221
|
-
}
|
|
1222
|
-
function getGlassSchemeNames() {
|
|
1223
|
-
return Object.keys(GLASS_SCHEMES);
|
|
1224
|
-
}
|
|
1225
|
-
function hexToRgba(hex, opacity) {
|
|
1226
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
1227
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
1228
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
1229
|
-
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
1230
|
-
}
|
|
1231
|
-
function createAnimationLoop(options) {
|
|
1232
|
-
const { fps = 30, onStart, onStop, onFrame } = options;
|
|
1233
|
-
const state = {
|
|
1234
|
-
isRunning: false,
|
|
1235
|
-
frameId: null,
|
|
1236
|
-
lastFrameTime: 0,
|
|
1237
|
-
frameInterval: 1e3 / fps,
|
|
1238
|
-
elapsedTime: 0,
|
|
1239
|
-
frameCount: 0
|
|
1240
|
-
};
|
|
1241
|
-
let pausedTime = 0;
|
|
1242
|
-
let isPaused = false;
|
|
1243
|
-
function animate(currentTime) {
|
|
1244
|
-
if (!state.isRunning || isPaused) return;
|
|
1245
|
-
const deltaTime = currentTime - state.lastFrameTime;
|
|
1246
|
-
if (deltaTime >= state.frameInterval) {
|
|
1247
|
-
state.elapsedTime += deltaTime;
|
|
1248
|
-
state.frameCount++;
|
|
1249
|
-
const continueAnimation = onFrame(currentTime, deltaTime, state.frameCount);
|
|
1250
|
-
if (continueAnimation === false) {
|
|
1251
|
-
stop();
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
state.lastFrameTime = currentTime - deltaTime % state.frameInterval;
|
|
1255
|
-
}
|
|
1256
|
-
state.frameId = requestAnimationFrame(animate);
|
|
1257
|
-
}
|
|
1258
|
-
function start() {
|
|
1259
|
-
if (state.isRunning) return;
|
|
1260
|
-
state.isRunning = true;
|
|
1261
|
-
state.lastFrameTime = performance.now();
|
|
1262
|
-
state.elapsedTime = 0;
|
|
1263
|
-
state.frameCount = 0;
|
|
1264
|
-
isPaused = false;
|
|
1265
|
-
onStart == null ? void 0 : onStart();
|
|
1266
|
-
state.frameId = requestAnimationFrame(animate);
|
|
1267
|
-
}
|
|
1268
|
-
function stop() {
|
|
1269
|
-
state.isRunning = false;
|
|
1270
|
-
isPaused = false;
|
|
1271
|
-
if (state.frameId !== null) {
|
|
1272
|
-
cancelAnimationFrame(state.frameId);
|
|
1273
|
-
state.frameId = null;
|
|
1274
|
-
}
|
|
1275
|
-
onStop == null ? void 0 : onStop();
|
|
1276
|
-
}
|
|
1277
|
-
function pause() {
|
|
1278
|
-
if (!state.isRunning || isPaused) return;
|
|
1279
|
-
isPaused = true;
|
|
1280
|
-
pausedTime = performance.now();
|
|
1281
|
-
if (state.frameId !== null) {
|
|
1282
|
-
cancelAnimationFrame(state.frameId);
|
|
1283
|
-
state.frameId = null;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
function resume() {
|
|
1287
|
-
if (!state.isRunning || !isPaused) return;
|
|
1288
|
-
isPaused = false;
|
|
1289
|
-
state.lastFrameTime += performance.now() - pausedTime;
|
|
1290
|
-
state.frameId = requestAnimationFrame(animate);
|
|
1291
|
-
}
|
|
1292
|
-
function getState() {
|
|
1293
|
-
return { ...state };
|
|
1294
|
-
}
|
|
1295
|
-
return { start, stop, pause, resume, getState };
|
|
1296
|
-
}
|
|
1297
|
-
function throttle(fn, limit) {
|
|
1298
|
-
let lastCall = 0;
|
|
1299
|
-
let timeout = null;
|
|
1300
|
-
return (...args) => {
|
|
1301
|
-
const now = Date.now();
|
|
1302
|
-
if (now - lastCall >= limit) {
|
|
1303
|
-
lastCall = now;
|
|
1304
|
-
fn(...args);
|
|
1305
|
-
} else if (!timeout) {
|
|
1306
|
-
timeout = setTimeout(() => {
|
|
1307
|
-
lastCall = Date.now();
|
|
1308
|
-
timeout = null;
|
|
1309
|
-
fn(...args);
|
|
1310
|
-
}, limit - (now - lastCall));
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
function debounce(fn, delay) {
|
|
1315
|
-
let timeout = null;
|
|
1316
|
-
return (...args) => {
|
|
1317
|
-
if (timeout) {
|
|
1318
|
-
clearTimeout(timeout);
|
|
1319
|
-
}
|
|
1320
|
-
timeout = setTimeout(() => {
|
|
1321
|
-
timeout = null;
|
|
1322
|
-
fn(...args);
|
|
1323
|
-
}, delay);
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
function calculateFPS(frameTimes, sampleSize = 60) {
|
|
1327
|
-
if (frameTimes.length < 2) return 0;
|
|
1328
|
-
const samples = frameTimes.slice(-sampleSize);
|
|
1329
|
-
const totalTime = samples[samples.length - 1] - samples[0];
|
|
1330
|
-
const frameCount = samples.length - 1;
|
|
1331
|
-
if (totalTime <= 0) return 0;
|
|
1332
|
-
return frameCount / totalTime * 1e3;
|
|
1333
|
-
}
|
|
1334
|
-
const easings = {
|
|
1335
|
-
/** Linear - no easing */
|
|
1336
|
-
linear: (t) => t,
|
|
1337
|
-
/** Ease in - slow start */
|
|
1338
|
-
easeIn: (t) => t * t,
|
|
1339
|
-
/** Ease out - slow end */
|
|
1340
|
-
easeOut: (t) => t * (2 - t),
|
|
1341
|
-
/** Ease in/out - slow start and end */
|
|
1342
|
-
easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
|
1343
|
-
/** Sine ease in */
|
|
1344
|
-
sineIn: (t) => 1 - Math.cos(t * Math.PI / 2),
|
|
1345
|
-
/** Sine ease out */
|
|
1346
|
-
sineOut: (t) => Math.sin(t * Math.PI / 2),
|
|
1347
|
-
/** Sine ease in/out */
|
|
1348
|
-
sineInOut: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
|
|
1349
|
-
/** Bounce at end */
|
|
1350
|
-
bounceOut: (t) => {
|
|
1351
|
-
const n1 = 7.5625;
|
|
1352
|
-
const d1 = 2.75;
|
|
1353
|
-
if (t < 1 / d1) {
|
|
1354
|
-
return n1 * t * t;
|
|
1355
|
-
} else if (t < 2 / d1) {
|
|
1356
|
-
return n1 * (t -= 1.5 / d1) * t + 0.75;
|
|
1357
|
-
} else if (t < 2.5 / d1) {
|
|
1358
|
-
return n1 * (t -= 2.25 / d1) * t + 0.9375;
|
|
1359
|
-
} else {
|
|
1360
|
-
return n1 * (t -= 2.625 / d1) * t + 0.984375;
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
};
|
|
1364
|
-
function createCanvas(options = {}) {
|
|
1365
|
-
const { width = 300, height = 150, highDPI = true, className, style } = options;
|
|
1366
|
-
const canvas = document.createElement("canvas");
|
|
1367
|
-
if (highDPI) {
|
|
1368
|
-
const dpr = window.devicePixelRatio || 1;
|
|
1369
|
-
canvas.width = width * dpr;
|
|
1370
|
-
canvas.height = height * dpr;
|
|
1371
|
-
canvas.style.width = `${width}px`;
|
|
1372
|
-
canvas.style.height = `${height}px`;
|
|
1373
|
-
const ctx = canvas.getContext("2d");
|
|
1374
|
-
if (ctx) {
|
|
1375
|
-
ctx.scale(dpr, dpr);
|
|
1376
|
-
}
|
|
1377
|
-
} else {
|
|
1378
|
-
canvas.width = width;
|
|
1379
|
-
canvas.height = height;
|
|
1380
|
-
}
|
|
1381
|
-
if (className) {
|
|
1382
|
-
canvas.className = className;
|
|
1383
|
-
}
|
|
1384
|
-
if (style) {
|
|
1385
|
-
Object.assign(canvas.style, style);
|
|
1386
|
-
}
|
|
1387
|
-
return canvas;
|
|
1388
|
-
}
|
|
1389
|
-
function getDevicePixelRatio() {
|
|
1390
|
-
return typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
1391
|
-
}
|
|
1392
|
-
function resizeCanvasToContainer(canvas, container, highDPI = true) {
|
|
1393
|
-
const rect = container.getBoundingClientRect();
|
|
1394
|
-
const dpr = highDPI ? getDevicePixelRatio() : 1;
|
|
1395
|
-
const width = rect.width;
|
|
1396
|
-
const height = rect.height;
|
|
1397
|
-
canvas.width = width * dpr;
|
|
1398
|
-
canvas.height = height * dpr;
|
|
1399
|
-
canvas.style.width = `${width}px`;
|
|
1400
|
-
canvas.style.height = `${height}px`;
|
|
1401
|
-
if (highDPI) {
|
|
1402
|
-
const ctx = canvas.getContext("2d");
|
|
1403
|
-
if (ctx) {
|
|
1404
|
-
ctx.scale(dpr, dpr);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
return { width, height };
|
|
1408
|
-
}
|
|
1409
|
-
function createOffscreenCanvas(width, height) {
|
|
1410
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
1411
|
-
return new OffscreenCanvas(width, height);
|
|
1412
|
-
}
|
|
1413
|
-
const canvas = document.createElement("canvas");
|
|
1414
|
-
canvas.width = width;
|
|
1415
|
-
canvas.height = height;
|
|
1416
|
-
return canvas;
|
|
1417
|
-
}
|
|
1418
|
-
function clearCanvas(ctx, width, height, backgroundColor) {
|
|
1419
|
-
if (backgroundColor) {
|
|
1420
|
-
ctx.fillStyle = backgroundColor;
|
|
1421
|
-
ctx.fillRect(0, 0, width, height);
|
|
1422
|
-
} else {
|
|
1423
|
-
ctx.clearRect(0, 0, width, height);
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
function getImageData(ctx, x = 0, y = 0, width, height) {
|
|
1427
|
-
const canvas = ctx.canvas;
|
|
1428
|
-
const w = width ?? canvas.width;
|
|
1429
|
-
const h = height ?? canvas.height;
|
|
1430
|
-
return ctx.getImageData(x, y, w, h);
|
|
1431
|
-
}
|
|
1432
|
-
function optimizeContext(ctx) {
|
|
1433
|
-
ctx.imageSmoothingEnabled = false;
|
|
1434
|
-
ctx.globalCompositeOperation = "source-over";
|
|
1435
|
-
}
|
|
1436
|
-
function setupTextRendering(ctx, fontSize, fontFamily = "monospace", color = "#ffffff") {
|
|
1437
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
1438
|
-
ctx.textBaseline = "top";
|
|
1439
|
-
ctx.textAlign = "left";
|
|
1440
|
-
ctx.fillStyle = color;
|
|
1441
|
-
}
|
|
1442
|
-
function measureTextWidth(ctx, text, fontSize, fontFamily = "monospace") {
|
|
1443
|
-
const originalFont = ctx.font;
|
|
1444
|
-
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
1445
|
-
const metrics = ctx.measureText(text);
|
|
1446
|
-
ctx.font = originalFont;
|
|
1447
|
-
return metrics.width;
|
|
1448
|
-
}
|
|
1449
|
-
function calculateCellSize(canvasWidth, canvasHeight, targetCols) {
|
|
1450
|
-
const cellWidth = Math.floor(canvasWidth / targetCols);
|
|
1451
|
-
const cellHeight = Math.floor(cellWidth * 1.5);
|
|
1452
|
-
const cols = Math.floor(canvasWidth / cellWidth);
|
|
1453
|
-
const rows = Math.floor(canvasHeight / cellHeight);
|
|
1454
|
-
return { cellWidth, cellHeight, cols, rows };
|
|
1455
|
-
}
|
|
1456
|
-
function setBlendMode(ctx, mode) {
|
|
1457
|
-
ctx.globalCompositeOperation = mode === "normal" ? "source-over" : mode;
|
|
1458
|
-
}
|
|
1459
|
-
function loadImage(src, options = {}) {
|
|
1460
|
-
return new Promise((resolve, reject) => {
|
|
1461
|
-
const img = new Image();
|
|
1462
|
-
if (options.crossOrigin !== void 0) {
|
|
1463
|
-
img.crossOrigin = options.crossOrigin;
|
|
1464
|
-
}
|
|
1465
|
-
img.onload = () => resolve(img);
|
|
1466
|
-
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
|
1467
|
-
img.src = src;
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
async function loadAndScaleImage(src, maxWidth, maxHeight, options = {}) {
|
|
1471
|
-
const img = await loadImage(src, options);
|
|
1472
|
-
let width = img.naturalWidth;
|
|
1473
|
-
let height = img.naturalHeight;
|
|
1474
|
-
if (width > maxWidth || height > maxHeight) {
|
|
1475
|
-
const widthRatio = maxWidth / width;
|
|
1476
|
-
const heightRatio = maxHeight / height;
|
|
1477
|
-
const scale = Math.min(widthRatio, heightRatio);
|
|
1478
|
-
width = Math.floor(width * scale);
|
|
1479
|
-
height = Math.floor(height * scale);
|
|
1480
|
-
}
|
|
1481
|
-
return { image: img, width, height };
|
|
1482
|
-
}
|
|
1483
|
-
function imageToPixelData(image, width, height) {
|
|
1484
|
-
const w = width ?? (image instanceof HTMLImageElement ? image.naturalWidth : image.width);
|
|
1485
|
-
const h = height ?? (image instanceof HTMLImageElement ? image.naturalHeight : image.height);
|
|
1486
|
-
const canvas = document.createElement("canvas");
|
|
1487
|
-
canvas.width = w;
|
|
1488
|
-
canvas.height = h;
|
|
1489
|
-
const ctx = canvas.getContext("2d");
|
|
1490
|
-
if (!ctx) {
|
|
1491
|
-
throw new Error("Failed to get 2D context");
|
|
1492
|
-
}
|
|
1493
|
-
ctx.drawImage(image, 0, 0, w, h);
|
|
1494
|
-
return ctx.getImageData(0, 0, w, h);
|
|
1495
|
-
}
|
|
1496
|
-
function extractBrightness(imageData, brightnessFunction = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b) {
|
|
1497
|
-
const { data, width, height } = imageData;
|
|
1498
|
-
const brightness = new Array(width * height);
|
|
1499
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1500
|
-
brightness[i / 4] = brightnessFunction(data[i], data[i + 1], data[i + 2]);
|
|
1501
|
-
}
|
|
1502
|
-
return brightness;
|
|
1503
|
-
}
|
|
1504
|
-
function sampleImageCells(imageData, cellWidth, cellHeight, brightnessFunction = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b) {
|
|
1505
|
-
const { data, width, height } = imageData;
|
|
1506
|
-
const cols = Math.ceil(width / cellWidth);
|
|
1507
|
-
const rows = Math.ceil(height / cellHeight);
|
|
1508
|
-
const result = [];
|
|
1509
|
-
for (let row = 0; row < rows; row++) {
|
|
1510
|
-
result[row] = [];
|
|
1511
|
-
for (let col = 0; col < cols; col++) {
|
|
1512
|
-
const cellData = sampleCell(data, width, col * cellWidth, row * cellHeight, cellWidth, cellHeight);
|
|
1513
|
-
const brightness = brightnessFunction(cellData.r, cellData.g, cellData.b);
|
|
1514
|
-
result[row][col] = {
|
|
1515
|
-
brightness,
|
|
1516
|
-
color: `rgb(${Math.round(cellData.r)}, ${Math.round(cellData.g)}, ${Math.round(cellData.b)})`
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
return result;
|
|
1521
|
-
}
|
|
1522
|
-
function sampleCell(data, imageWidth, startX, startY, cellWidth, cellHeight) {
|
|
1523
|
-
let totalR = 0;
|
|
1524
|
-
let totalG = 0;
|
|
1525
|
-
let totalB = 0;
|
|
1526
|
-
let totalA = 0;
|
|
1527
|
-
let count = 0;
|
|
1528
|
-
for (let y = startY; y < startY + cellHeight; y++) {
|
|
1529
|
-
for (let x = startX; x < startX + cellWidth; x++) {
|
|
1530
|
-
const px = (y * imageWidth + x) * 4;
|
|
1531
|
-
if (px >= 0 && px + 3 < data.length) {
|
|
1532
|
-
totalR += data[px];
|
|
1533
|
-
totalG += data[px + 1];
|
|
1534
|
-
totalB += data[px + 2];
|
|
1535
|
-
totalA += data[px + 3];
|
|
1536
|
-
count++;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
if (count === 0) {
|
|
1541
|
-
return { r: 0, g: 0, b: 0, a: 0 };
|
|
1542
|
-
}
|
|
1543
|
-
return {
|
|
1544
|
-
r: totalR / count,
|
|
1545
|
-
g: totalG / count,
|
|
1546
|
-
b: totalB / count,
|
|
1547
|
-
a: totalA / count
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
function rgbToHex(r, g, b) {
|
|
1551
|
-
const toHex = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
1552
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
1553
|
-
}
|
|
1554
|
-
function hexToRgb(hex) {
|
|
1555
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
1556
|
-
return result ? {
|
|
1557
|
-
r: parseInt(result[1], 16),
|
|
1558
|
-
g: parseInt(result[2], 16),
|
|
1559
|
-
b: parseInt(result[3], 16)
|
|
1560
|
-
} : null;
|
|
1561
|
-
}
|
|
1562
|
-
function adjustBrightness(imageData, amount) {
|
|
1563
|
-
const { data, width, height } = imageData;
|
|
1564
|
-
const adjusted = new Uint8ClampedArray(data.length);
|
|
1565
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1566
|
-
adjusted[i] = Math.min(255, Math.max(0, data[i] + amount));
|
|
1567
|
-
adjusted[i + 1] = Math.min(255, Math.max(0, data[i + 1] + amount));
|
|
1568
|
-
adjusted[i + 2] = Math.min(255, Math.max(0, data[i + 2] + amount));
|
|
1569
|
-
adjusted[i + 3] = data[i + 3];
|
|
1570
|
-
}
|
|
1571
|
-
return new ImageData(adjusted, width, height);
|
|
1572
|
-
}
|
|
1573
|
-
function adjustContrast(imageData, amount) {
|
|
1574
|
-
const { data, width, height } = imageData;
|
|
1575
|
-
const adjusted = new Uint8ClampedArray(data.length);
|
|
1576
|
-
const factor = 259 * (amount + 255) / (255 * (259 - amount));
|
|
1577
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1578
|
-
adjusted[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
|
|
1579
|
-
adjusted[i + 1] = Math.min(255, Math.max(0, factor * (data[i + 1] - 128) + 128));
|
|
1580
|
-
adjusted[i + 2] = Math.min(255, Math.max(0, factor * (data[i + 2] - 128) + 128));
|
|
1581
|
-
adjusted[i + 3] = data[i + 3];
|
|
1582
|
-
}
|
|
1583
|
-
return new ImageData(adjusted, width, height);
|
|
1584
|
-
}
|
|
1585
|
-
function invertColors(imageData) {
|
|
1586
|
-
const { data, width, height } = imageData;
|
|
1587
|
-
const inverted = new Uint8ClampedArray(data.length);
|
|
1588
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1589
|
-
inverted[i] = 255 - data[i];
|
|
1590
|
-
inverted[i + 1] = 255 - data[i + 1];
|
|
1591
|
-
inverted[i + 2] = 255 - data[i + 2];
|
|
1592
|
-
inverted[i + 3] = data[i + 3];
|
|
1593
|
-
}
|
|
1594
|
-
return new ImageData(inverted, width, height);
|
|
1595
|
-
}
|
|
1596
|
-
function toGrayscale(imageData) {
|
|
1597
|
-
const { data, width, height } = imageData;
|
|
1598
|
-
const grayscale = new Uint8ClampedArray(data.length);
|
|
1599
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
1600
|
-
const gray = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2];
|
|
1601
|
-
grayscale[i] = gray;
|
|
1602
|
-
grayscale[i + 1] = gray;
|
|
1603
|
-
grayscale[i + 2] = gray;
|
|
1604
|
-
grayscale[i + 3] = data[i + 3];
|
|
1605
|
-
}
|
|
1606
|
-
return new ImageData(grayscale, width, height);
|
|
1607
|
-
}
|
|
1608
|
-
function createVisibilityObserver(element, callback, threshold = 0.1) {
|
|
1609
|
-
const observer = new IntersectionObserver(
|
|
1610
|
-
(entries) => {
|
|
1611
|
-
for (const entry of entries) {
|
|
1612
|
-
callback(entry.isIntersecting, entry);
|
|
1613
|
-
}
|
|
1614
|
-
},
|
|
1615
|
-
{ threshold }
|
|
1616
|
-
);
|
|
1617
|
-
observer.observe(element);
|
|
1618
|
-
return () => {
|
|
1619
|
-
observer.disconnect();
|
|
1620
|
-
};
|
|
1621
|
-
}
|
|
1622
|
-
function createResizeObserver(element, callback, debounceMs = 100) {
|
|
1623
|
-
let timeout = null;
|
|
1624
|
-
const observer = new ResizeObserver((entries) => {
|
|
1625
|
-
if (timeout) {
|
|
1626
|
-
clearTimeout(timeout);
|
|
1627
|
-
}
|
|
1628
|
-
timeout = setTimeout(() => {
|
|
1629
|
-
const entry = entries[0];
|
|
1630
|
-
if (entry) {
|
|
1631
|
-
const { width, height } = entry.contentRect;
|
|
1632
|
-
callback(width, height, entry);
|
|
1633
|
-
}
|
|
1634
|
-
}, debounceMs);
|
|
1635
|
-
});
|
|
1636
|
-
observer.observe(element);
|
|
1637
|
-
return () => {
|
|
1638
|
-
if (timeout) {
|
|
1639
|
-
clearTimeout(timeout);
|
|
1640
|
-
}
|
|
1641
|
-
observer.disconnect();
|
|
1642
|
-
};
|
|
1643
|
-
}
|
|
1644
|
-
function prefersReducedMotion() {
|
|
1645
|
-
if (typeof window === "undefined") return false;
|
|
1646
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1647
|
-
}
|
|
1648
|
-
function onReducedMotionChange(callback) {
|
|
1649
|
-
if (typeof window === "undefined") {
|
|
1650
|
-
return () => {
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1653
|
-
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
1654
|
-
const handler = (event) => {
|
|
1655
|
-
callback(event.matches);
|
|
1656
|
-
};
|
|
1657
|
-
mediaQuery.addEventListener("change", handler);
|
|
1658
|
-
callback(mediaQuery.matches);
|
|
1659
|
-
return () => {
|
|
1660
|
-
mediaQuery.removeEventListener("change", handler);
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
|
-
function isLowPowerMode() {
|
|
1664
|
-
if (typeof navigator !== "undefined" && "getBattery" in navigator) {
|
|
1665
|
-
return false;
|
|
1666
|
-
}
|
|
1667
|
-
if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
|
|
1668
|
-
return navigator.hardwareConcurrency <= 2;
|
|
1669
|
-
}
|
|
1670
|
-
return false;
|
|
1671
|
-
}
|
|
1672
|
-
function getRecommendedFPS() {
|
|
1673
|
-
if (prefersReducedMotion()) {
|
|
1674
|
-
return 0;
|
|
1675
|
-
}
|
|
1676
|
-
if (isLowPowerMode()) {
|
|
1677
|
-
return 15;
|
|
1678
|
-
}
|
|
1679
|
-
return 30;
|
|
1680
|
-
}
|
|
1681
|
-
function createFPSCounter() {
|
|
1682
|
-
const frameTimes = [];
|
|
1683
|
-
const maxSamples = 60;
|
|
1684
|
-
let droppedFrames = 0;
|
|
1685
|
-
let lastFrameTime = performance.now();
|
|
1686
|
-
function tick() {
|
|
1687
|
-
const now = performance.now();
|
|
1688
|
-
const delta = now - lastFrameTime;
|
|
1689
|
-
lastFrameTime = now;
|
|
1690
|
-
frameTimes.push(now);
|
|
1691
|
-
if (frameTimes.length > maxSamples) {
|
|
1692
|
-
frameTimes.shift();
|
|
1693
|
-
}
|
|
1694
|
-
if (delta > 20) {
|
|
1695
|
-
droppedFrames += Math.floor(delta / 16.67) - 1;
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
function getFPS() {
|
|
1699
|
-
if (frameTimes.length < 2) return 0;
|
|
1700
|
-
const oldest = frameTimes[0];
|
|
1701
|
-
const newest = frameTimes[frameTimes.length - 1];
|
|
1702
|
-
const elapsed = newest - oldest;
|
|
1703
|
-
if (elapsed === 0) return 0;
|
|
1704
|
-
return (frameTimes.length - 1) / elapsed * 1e3;
|
|
1705
|
-
}
|
|
1706
|
-
function getMetrics() {
|
|
1707
|
-
const fps = getFPS();
|
|
1708
|
-
const frameTime = fps > 0 ? 1e3 / fps : 0;
|
|
1709
|
-
return {
|
|
1710
|
-
fps: Math.round(fps * 10) / 10,
|
|
1711
|
-
frameTime: Math.round(frameTime * 100) / 100,
|
|
1712
|
-
droppedFrames
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
function reset() {
|
|
1716
|
-
frameTimes.length = 0;
|
|
1717
|
-
droppedFrames = 0;
|
|
1718
|
-
lastFrameTime = performance.now();
|
|
1719
|
-
}
|
|
1720
|
-
return { tick, getFPS, getMetrics, reset };
|
|
1721
|
-
}
|
|
1722
|
-
function requestIdleCallback(callback, options) {
|
|
1723
|
-
const global = globalThis;
|
|
1724
|
-
if (typeof global.requestIdleCallback === "function") {
|
|
1725
|
-
return global.requestIdleCallback(callback, options);
|
|
1726
|
-
}
|
|
1727
|
-
return global.setTimeout(callback, (options == null ? void 0 : options.timeout) ?? 1);
|
|
1728
|
-
}
|
|
1729
|
-
function cancelIdleCallback(id) {
|
|
1730
|
-
const global = globalThis;
|
|
1731
|
-
if (typeof global.cancelIdleCallback === "function") {
|
|
1732
|
-
global.cancelIdleCallback(id);
|
|
1733
|
-
} else {
|
|
1734
|
-
global.clearTimeout(id);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
function isBrowser() {
|
|
1738
|
-
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
1739
|
-
}
|
|
1740
|
-
function isCanvasSupported() {
|
|
1741
|
-
if (!isBrowser()) return false;
|
|
1742
|
-
const canvas = document.createElement("canvas");
|
|
1743
|
-
return !!(canvas.getContext && canvas.getContext("2d"));
|
|
1744
|
-
}
|
|
1745
|
-
function isOffscreenCanvasSupported() {
|
|
1746
|
-
return typeof OffscreenCanvas !== "undefined";
|
|
1747
|
-
}
|
|
1748
|
-
const DEFAULT_CHARACTERS = " .:-=+*#%@";
|
|
1749
|
-
const DEFAULT_CONFIG = {
|
|
1750
|
-
characters: DEFAULT_CHARACTERS,
|
|
1751
|
-
cellWidth: 8,
|
|
1752
|
-
cellHeight: 12,
|
|
1753
|
-
color: "#ffffff",
|
|
1754
|
-
backgroundColor: "",
|
|
1755
|
-
fontFamily: "monospace",
|
|
1756
|
-
animate: false,
|
|
1757
|
-
fps: 30
|
|
1758
|
-
};
|
|
1759
|
-
function calculateBrightness(r, g, b) {
|
|
1760
|
-
return 0.21 * r + 0.72 * g + 0.07 * b;
|
|
1761
|
-
}
|
|
1762
|
-
function brightnessToChar(brightness, characters = DEFAULT_CHARACTERS) {
|
|
1763
|
-
const index = Math.floor(brightness / 255 * (characters.length - 1));
|
|
1764
|
-
return characters[Math.min(index, characters.length - 1)];
|
|
1765
|
-
}
|
|
1766
|
-
const VERSION = "0.1.0";
|
|
1767
|
-
export {
|
|
1768
|
-
BARK,
|
|
1769
|
-
CHARACTER_SETS,
|
|
1770
|
-
CREAM,
|
|
1771
|
-
DEFAULT_CHARACTERS,
|
|
1772
|
-
DEFAULT_CONFIG,
|
|
1773
|
-
DEFAULT_PATTERN_CONFIG,
|
|
1774
|
-
GLASS_SCHEMES,
|
|
1775
|
-
GROVE_COLORS,
|
|
1776
|
-
GROVE_GREEN,
|
|
1777
|
-
GossamerRenderer,
|
|
1778
|
-
STATUS,
|
|
1779
|
-
VERSION,
|
|
1780
|
-
adjustBrightness,
|
|
1781
|
-
adjustContrast,
|
|
1782
|
-
brightnessToChar,
|
|
1783
|
-
calculateBrightness,
|
|
1784
|
-
calculateCellSize,
|
|
1785
|
-
calculateFPS,
|
|
1786
|
-
cancelIdleCallback,
|
|
1787
|
-
clearCanvas,
|
|
1788
|
-
cloudsPattern,
|
|
1789
|
-
createAnimationLoop,
|
|
1790
|
-
createBrightnessBuffer,
|
|
1791
|
-
createCanvas,
|
|
1792
|
-
createCharacterSet,
|
|
1793
|
-
createFPSCounter,
|
|
1794
|
-
createOffscreenCanvas,
|
|
1795
|
-
createResizeObserver,
|
|
1796
|
-
createVisibilityObserver,
|
|
1797
|
-
debounce,
|
|
1798
|
-
diamondPattern,
|
|
1799
|
-
easings,
|
|
1800
|
-
extractBrightness,
|
|
1801
|
-
fbmNoise,
|
|
1802
|
-
fillBrightnessBuffer,
|
|
1803
|
-
fractalPattern,
|
|
1804
|
-
generateBrightnessGrid,
|
|
1805
|
-
getBufferValue,
|
|
1806
|
-
getCharacterSet,
|
|
1807
|
-
getCharacterSetNames,
|
|
1808
|
-
getCharacters,
|
|
1809
|
-
getDevicePixelRatio,
|
|
1810
|
-
getGlassScheme,
|
|
1811
|
-
getGlassSchemeNames,
|
|
1812
|
-
getGroveColor,
|
|
1813
|
-
getGroveColorNames,
|
|
1814
|
-
getImageData,
|
|
1815
|
-
getRecommendedFPS,
|
|
1816
|
-
gradientPattern,
|
|
1817
|
-
gridToImageData,
|
|
1818
|
-
hexToRgb,
|
|
1819
|
-
hexToRgba,
|
|
1820
|
-
imageToPixelData,
|
|
1821
|
-
invertCharacters,
|
|
1822
|
-
invertColors,
|
|
1823
|
-
isBrowser,
|
|
1824
|
-
isCanvasSupported,
|
|
1825
|
-
isLowPowerMode,
|
|
1826
|
-
isOffscreenCanvasSupported,
|
|
1827
|
-
loadAndScaleImage,
|
|
1828
|
-
loadImage,
|
|
1829
|
-
matrixPattern,
|
|
1830
|
-
measureTextWidth,
|
|
1831
|
-
onReducedMotionChange,
|
|
1832
|
-
optimizeContext,
|
|
1833
|
-
perlinNoise2D,
|
|
1834
|
-
plasmaPattern,
|
|
1835
|
-
prefersReducedMotion,
|
|
1836
|
-
requestIdleCallback,
|
|
1837
|
-
resizeCanvasToContainer,
|
|
1838
|
-
rgbToHex,
|
|
1839
|
-
ripplePattern,
|
|
1840
|
-
sampleImageCells,
|
|
1841
|
-
seededNoise2D,
|
|
1842
|
-
setBlendMode,
|
|
1843
|
-
setupTextRendering,
|
|
1844
|
-
staticNoise,
|
|
1845
|
-
throttle,
|
|
1846
|
-
toGrayscale,
|
|
1847
|
-
validateCharacterSet,
|
|
1848
|
-
vortexPattern,
|
|
1849
|
-
wavePattern
|
|
1
|
+
/**
|
|
2
|
+
* Gossamer - ASCII Visual Effects Library
|
|
3
|
+
*
|
|
4
|
+
* Threads of light. Delicate textures woven through your space.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Constants
|
|
10
|
+
// =============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Default character set ordered from light to dark
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_CHARACTERS = ' .:-=+*#%@';
|
|
15
|
+
/**
|
|
16
|
+
* Default configuration values
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_CONFIG = {
|
|
19
|
+
characters: DEFAULT_CHARACTERS,
|
|
20
|
+
cellWidth: 8,
|
|
21
|
+
cellHeight: 12,
|
|
22
|
+
color: '#ffffff',
|
|
23
|
+
backgroundColor: '',
|
|
24
|
+
fontFamily: 'monospace',
|
|
25
|
+
animate: false,
|
|
26
|
+
fps: 30,
|
|
1850
27
|
};
|
|
1851
|
-
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Core Functions
|
|
30
|
+
// =============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Calculate brightness from RGB values using luminance formula
|
|
33
|
+
* Uses the standard luminance coefficients: 0.21 R + 0.72 G + 0.07 B
|
|
34
|
+
*/
|
|
35
|
+
export function calculateBrightness(r, g, b) {
|
|
36
|
+
return 0.21 * r + 0.72 * g + 0.07 * b;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Map a brightness value (0-255) to an ASCII character
|
|
40
|
+
*/
|
|
41
|
+
export function brightnessToChar(brightness, characters = DEFAULT_CHARACTERS) {
|
|
42
|
+
const index = Math.floor((brightness / 255) * (characters.length - 1));
|
|
43
|
+
return characters[Math.min(index, characters.length - 1)];
|
|
44
|
+
}
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Module Exports
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Renderer
|
|
49
|
+
export { GossamerRenderer } from './renderer';
|
|
50
|
+
// Patterns
|
|
51
|
+
export {
|
|
52
|
+
// Core noise functions
|
|
53
|
+
perlinNoise2D, fbmNoise, staticNoise, seededNoise2D,
|
|
54
|
+
// Pattern generators
|
|
55
|
+
wavePattern, ripplePattern, cloudsPattern, plasmaPattern, vortexPattern, matrixPattern, gradientPattern, diamondPattern, fractalPattern,
|
|
56
|
+
// Grid generation (legacy API)
|
|
57
|
+
generateBrightnessGrid, gridToImageData,
|
|
58
|
+
// Performance-optimized API
|
|
59
|
+
createBrightnessBuffer, fillBrightnessBuffer, getBufferValue,
|
|
60
|
+
// Config
|
|
61
|
+
DEFAULT_PATTERN_CONFIG, } from './patterns';
|
|
62
|
+
// Characters
|
|
63
|
+
export { CHARACTER_SETS, getCharacterSet, getCharacters, getCharacterSetNames, createCharacterSet, validateCharacterSet, invertCharacters, } from './characters';
|
|
64
|
+
// Colors (Grove palette)
|
|
65
|
+
export { GROVE_GREEN, CREAM, BARK, STATUS, GROVE_COLORS, GLASS_SCHEMES, getGroveColor, getGlassScheme, getGroveColorNames, getGlassSchemeNames, hexToRgba, } from './colors';
|
|
66
|
+
// Animation
|
|
67
|
+
export { createAnimationLoop, throttle, debounce, calculateFPS, easings, } from './animation';
|
|
68
|
+
// Canvas Utilities
|
|
69
|
+
export { createCanvas, getDevicePixelRatio, resizeCanvasToContainer, createOffscreenCanvas, clearCanvas, getImageData, optimizeContext, setupTextRendering, measureTextWidth, calculateCellSize, setBlendMode, } from './utils/canvas';
|
|
70
|
+
// Image Utilities
|
|
71
|
+
export { loadImage, loadAndScaleImage, imageToPixelData, extractBrightness, sampleImageCells, rgbToHex, hexToRgb, adjustBrightness, adjustContrast, invertColors, toGrayscale, } from './utils/image';
|
|
72
|
+
// Performance Utilities
|
|
73
|
+
export { createVisibilityObserver, createResizeObserver, prefersReducedMotion, onReducedMotionChange, isLowPowerMode, getRecommendedFPS, createFPSCounter, requestIdleCallback, cancelIdleCallback, isBrowser, isCanvasSupported, isOffscreenCanvasSupported, } from './utils/performance';
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// Version
|
|
76
|
+
// =============================================================================
|
|
77
|
+
/**
|
|
78
|
+
* Gossamer version
|
|
79
|
+
*/
|
|
80
|
+
export const VERSION = '0.1.0';
|