@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
@@ -1,210 +0,0 @@
1
- /**
2
- * Canvas Utilities
3
- *
4
- * Helper functions for canvas creation, setup, and manipulation.
5
- */
6
-
7
- /**
8
- * Options for canvas creation
9
- */
10
- export interface CanvasOptions {
11
- /** Canvas width in pixels */
12
- width?: number;
13
- /** Canvas height in pixels */
14
- height?: number;
15
- /** Whether to use high DPI scaling */
16
- highDPI?: boolean;
17
- /** CSS class to add to canvas */
18
- className?: string;
19
- /** Inline styles to apply */
20
- style?: Partial<CSSStyleDeclaration>;
21
- }
22
-
23
- /**
24
- * Create a canvas element with optimal settings
25
- */
26
- export function createCanvas(options: CanvasOptions = {}): HTMLCanvasElement {
27
- const { width = 300, height = 150, highDPI = true, className, style } = options;
28
-
29
- const canvas = document.createElement('canvas');
30
-
31
- if (highDPI) {
32
- const dpr = window.devicePixelRatio || 1;
33
- canvas.width = width * dpr;
34
- canvas.height = height * dpr;
35
- canvas.style.width = `${width}px`;
36
- canvas.style.height = `${height}px`;
37
-
38
- const ctx = canvas.getContext('2d');
39
- if (ctx) {
40
- ctx.scale(dpr, dpr);
41
- }
42
- } else {
43
- canvas.width = width;
44
- canvas.height = height;
45
- }
46
-
47
- if (className) {
48
- canvas.className = className;
49
- }
50
-
51
- if (style) {
52
- Object.assign(canvas.style, style);
53
- }
54
-
55
- return canvas;
56
- }
57
-
58
- /**
59
- * Get the device pixel ratio
60
- */
61
- export function getDevicePixelRatio(): number {
62
- return typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;
63
- }
64
-
65
- /**
66
- * Resize canvas to match container dimensions
67
- */
68
- export function resizeCanvasToContainer(
69
- canvas: HTMLCanvasElement,
70
- container: HTMLElement,
71
- highDPI: boolean = true
72
- ): { width: number; height: number } {
73
- const rect = container.getBoundingClientRect();
74
- const dpr = highDPI ? getDevicePixelRatio() : 1;
75
-
76
- const width = rect.width;
77
- const height = rect.height;
78
-
79
- canvas.width = width * dpr;
80
- canvas.height = height * dpr;
81
- canvas.style.width = `${width}px`;
82
- canvas.style.height = `${height}px`;
83
-
84
- if (highDPI) {
85
- const ctx = canvas.getContext('2d');
86
- if (ctx) {
87
- ctx.scale(dpr, dpr);
88
- }
89
- }
90
-
91
- return { width, height };
92
- }
93
-
94
- /**
95
- * Create an offscreen canvas for buffer rendering
96
- */
97
- export function createOffscreenCanvas(width: number, height: number): HTMLCanvasElement | OffscreenCanvas {
98
- if (typeof OffscreenCanvas !== 'undefined') {
99
- return new OffscreenCanvas(width, height);
100
- }
101
-
102
- // Fallback for environments without OffscreenCanvas
103
- const canvas = document.createElement('canvas');
104
- canvas.width = width;
105
- canvas.height = height;
106
- return canvas;
107
- }
108
-
109
- /**
110
- * Clear a canvas
111
- */
112
- export function clearCanvas(
113
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
114
- width: number,
115
- height: number,
116
- backgroundColor?: string
117
- ): void {
118
- if (backgroundColor) {
119
- ctx.fillStyle = backgroundColor;
120
- ctx.fillRect(0, 0, width, height);
121
- } else {
122
- ctx.clearRect(0, 0, width, height);
123
- }
124
- }
125
-
126
- /**
127
- * Get image data from a canvas region
128
- */
129
- export function getImageData(
130
- ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
131
- x: number = 0,
132
- y: number = 0,
133
- width?: number,
134
- height?: number
135
- ): ImageData {
136
- const canvas = ctx.canvas;
137
- const w = width ?? canvas.width;
138
- const h = height ?? canvas.height;
139
-
140
- return ctx.getImageData(x, y, w, h);
141
- }
142
-
143
- /**
144
- * Set optimal rendering context settings
145
- */
146
- export function optimizeContext(ctx: CanvasRenderingContext2D): void {
147
- // Disable image smoothing for crisp ASCII rendering
148
- ctx.imageSmoothingEnabled = false;
149
-
150
- // Use source-over for standard compositing
151
- ctx.globalCompositeOperation = 'source-over';
152
- }
153
-
154
- /**
155
- * Set text rendering options for ASCII display
156
- */
157
- export function setupTextRendering(
158
- ctx: CanvasRenderingContext2D,
159
- fontSize: number,
160
- fontFamily: string = 'monospace',
161
- color: string = '#ffffff'
162
- ): void {
163
- ctx.font = `${fontSize}px ${fontFamily}`;
164
- ctx.textBaseline = 'top';
165
- ctx.textAlign = 'left';
166
- ctx.fillStyle = color;
167
- }
168
-
169
- /**
170
- * Measure text width for a given font configuration
171
- */
172
- export function measureTextWidth(
173
- ctx: CanvasRenderingContext2D,
174
- text: string,
175
- fontSize: number,
176
- fontFamily: string = 'monospace'
177
- ): number {
178
- const originalFont = ctx.font;
179
- ctx.font = `${fontSize}px ${fontFamily}`;
180
- const metrics = ctx.measureText(text);
181
- ctx.font = originalFont;
182
- return metrics.width;
183
- }
184
-
185
- /**
186
- * Calculate optimal cell size for a given canvas and desired columns
187
- */
188
- export function calculateCellSize(
189
- canvasWidth: number,
190
- canvasHeight: number,
191
- targetCols: number
192
- ): { cellWidth: number; cellHeight: number; cols: number; rows: number } {
193
- const cellWidth = Math.floor(canvasWidth / targetCols);
194
- // Use a typical monospace aspect ratio of ~0.6
195
- const cellHeight = Math.floor(cellWidth * 1.5);
196
- const cols = Math.floor(canvasWidth / cellWidth);
197
- const rows = Math.floor(canvasHeight / cellHeight);
198
-
199
- return { cellWidth, cellHeight, cols, rows };
200
- }
201
-
202
- /**
203
- * Apply a CSS blend mode to canvas compositing
204
- */
205
- export function setBlendMode(
206
- ctx: CanvasRenderingContext2D,
207
- mode: 'normal' | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten' | 'color-dodge' | 'color-burn' | 'soft-light' | 'hard-light' | 'difference' | 'exclusion'
208
- ): void {
209
- ctx.globalCompositeOperation = mode === 'normal' ? 'source-over' : mode;
210
- }
@@ -1,275 +0,0 @@
1
- /**
2
- * Image Utilities
3
- *
4
- * Image loading, processing, and pixel manipulation for ASCII conversion.
5
- */
6
-
7
- /**
8
- * Image loading options
9
- */
10
- export interface ImageLoadOptions {
11
- /** Cross-origin setting for external images */
12
- crossOrigin?: 'anonymous' | 'use-credentials' | '';
13
- /** Maximum width to scale image to */
14
- maxWidth?: number;
15
- /** Maximum height to scale image to */
16
- maxHeight?: number;
17
- /** Whether to preserve aspect ratio when scaling */
18
- preserveAspectRatio?: boolean;
19
- }
20
-
21
- /**
22
- * Load an image from a URL
23
- */
24
- export function loadImage(src: string, options: ImageLoadOptions = {}): Promise<HTMLImageElement> {
25
- return new Promise((resolve, reject) => {
26
- const img = new Image();
27
-
28
- if (options.crossOrigin !== undefined) {
29
- img.crossOrigin = options.crossOrigin;
30
- }
31
-
32
- img.onload = () => resolve(img);
33
- img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
34
-
35
- img.src = src;
36
- });
37
- }
38
-
39
- /**
40
- * Load and scale an image to fit within bounds
41
- */
42
- export async function loadAndScaleImage(
43
- src: string,
44
- maxWidth: number,
45
- maxHeight: number,
46
- options: ImageLoadOptions = {}
47
- ): Promise<{ image: HTMLImageElement; width: number; height: number }> {
48
- const img = await loadImage(src, options);
49
-
50
- let width = img.naturalWidth;
51
- let height = img.naturalHeight;
52
-
53
- // Scale down if needed
54
- if (width > maxWidth || height > maxHeight) {
55
- const widthRatio = maxWidth / width;
56
- const heightRatio = maxHeight / height;
57
- const scale = Math.min(widthRatio, heightRatio);
58
-
59
- width = Math.floor(width * scale);
60
- height = Math.floor(height * scale);
61
- }
62
-
63
- return { image: img, width, height };
64
- }
65
-
66
- /**
67
- * Draw an image to a canvas and get its pixel data
68
- */
69
- export function imageToPixelData(
70
- image: HTMLImageElement | HTMLCanvasElement | ImageBitmap,
71
- width?: number,
72
- height?: number
73
- ): ImageData {
74
- const w = width ?? (image instanceof HTMLImageElement ? image.naturalWidth : image.width);
75
- const h = height ?? (image instanceof HTMLImageElement ? image.naturalHeight : image.height);
76
-
77
- const canvas = document.createElement('canvas');
78
- canvas.width = w;
79
- canvas.height = h;
80
-
81
- const ctx = canvas.getContext('2d');
82
- if (!ctx) {
83
- throw new Error('Failed to get 2D context');
84
- }
85
-
86
- ctx.drawImage(image, 0, 0, w, h);
87
- return ctx.getImageData(0, 0, w, h);
88
- }
89
-
90
- /**
91
- * Extract brightness values from image data
92
- */
93
- export function extractBrightness(
94
- imageData: ImageData,
95
- brightnessFunction: (r: number, g: number, b: number) => number = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b
96
- ): number[] {
97
- const { data, width, height } = imageData;
98
- const brightness: number[] = new Array(width * height);
99
-
100
- for (let i = 0; i < data.length; i += 4) {
101
- brightness[i / 4] = brightnessFunction(data[i], data[i + 1], data[i + 2]);
102
- }
103
-
104
- return brightness;
105
- }
106
-
107
- /**
108
- * Sample image data at cell-based intervals
109
- */
110
- export function sampleImageCells(
111
- imageData: ImageData,
112
- cellWidth: number,
113
- cellHeight: number,
114
- brightnessFunction: (r: number, g: number, b: number) => number = (r, g, b) => 0.21 * r + 0.72 * g + 0.07 * b
115
- ): { brightness: number; color: string }[][] {
116
- const { data, width, height } = imageData;
117
- const cols = Math.ceil(width / cellWidth);
118
- const rows = Math.ceil(height / cellHeight);
119
-
120
- const result: { brightness: number; color: string }[][] = [];
121
-
122
- for (let row = 0; row < rows; row++) {
123
- result[row] = [];
124
-
125
- for (let col = 0; col < cols; col++) {
126
- const cellData = sampleCell(data, width, col * cellWidth, row * cellHeight, cellWidth, cellHeight);
127
- const brightness = brightnessFunction(cellData.r, cellData.g, cellData.b);
128
-
129
- result[row][col] = {
130
- brightness,
131
- color: `rgb(${Math.round(cellData.r)}, ${Math.round(cellData.g)}, ${Math.round(cellData.b)})`,
132
- };
133
- }
134
- }
135
-
136
- return result;
137
- }
138
-
139
- /**
140
- * Sample average color from a cell region
141
- */
142
- function sampleCell(
143
- data: Uint8ClampedArray,
144
- imageWidth: number,
145
- startX: number,
146
- startY: number,
147
- cellWidth: number,
148
- cellHeight: number
149
- ): { r: number; g: number; b: number; a: number } {
150
- let totalR = 0;
151
- let totalG = 0;
152
- let totalB = 0;
153
- let totalA = 0;
154
- let count = 0;
155
-
156
- for (let y = startY; y < startY + cellHeight; y++) {
157
- for (let x = startX; x < startX + cellWidth; x++) {
158
- const px = (y * imageWidth + x) * 4;
159
-
160
- if (px >= 0 && px + 3 < data.length) {
161
- totalR += data[px];
162
- totalG += data[px + 1];
163
- totalB += data[px + 2];
164
- totalA += data[px + 3];
165
- count++;
166
- }
167
- }
168
- }
169
-
170
- if (count === 0) {
171
- return { r: 0, g: 0, b: 0, a: 0 };
172
- }
173
-
174
- return {
175
- r: totalR / count,
176
- g: totalG / count,
177
- b: totalB / count,
178
- a: totalA / count,
179
- };
180
- }
181
-
182
- /**
183
- * Convert RGB to hex color string
184
- */
185
- export function rgbToHex(r: number, g: number, b: number): string {
186
- const toHex = (n: number) =>
187
- Math.max(0, Math.min(255, Math.round(n)))
188
- .toString(16)
189
- .padStart(2, '0');
190
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
191
- }
192
-
193
- /**
194
- * Convert hex color to RGB
195
- */
196
- export function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
197
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
198
- return result
199
- ? {
200
- r: parseInt(result[1], 16),
201
- g: parseInt(result[2], 16),
202
- b: parseInt(result[3], 16),
203
- }
204
- : null;
205
- }
206
-
207
- /**
208
- * Adjust image brightness
209
- */
210
- export function adjustBrightness(imageData: ImageData, amount: number): ImageData {
211
- const { data, width, height } = imageData;
212
- const adjusted = new Uint8ClampedArray(data.length);
213
-
214
- for (let i = 0; i < data.length; i += 4) {
215
- adjusted[i] = Math.min(255, Math.max(0, data[i] + amount));
216
- adjusted[i + 1] = Math.min(255, Math.max(0, data[i + 1] + amount));
217
- adjusted[i + 2] = Math.min(255, Math.max(0, data[i + 2] + amount));
218
- adjusted[i + 3] = data[i + 3];
219
- }
220
-
221
- return new ImageData(adjusted, width, height);
222
- }
223
-
224
- /**
225
- * Adjust image contrast
226
- */
227
- export function adjustContrast(imageData: ImageData, amount: number): ImageData {
228
- const { data, width, height } = imageData;
229
- const adjusted = new Uint8ClampedArray(data.length);
230
- const factor = (259 * (amount + 255)) / (255 * (259 - amount));
231
-
232
- for (let i = 0; i < data.length; i += 4) {
233
- adjusted[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
234
- adjusted[i + 1] = Math.min(255, Math.max(0, factor * (data[i + 1] - 128) + 128));
235
- adjusted[i + 2] = Math.min(255, Math.max(0, factor * (data[i + 2] - 128) + 128));
236
- adjusted[i + 3] = data[i + 3];
237
- }
238
-
239
- return new ImageData(adjusted, width, height);
240
- }
241
-
242
- /**
243
- * Invert image colors
244
- */
245
- export function invertColors(imageData: ImageData): ImageData {
246
- const { data, width, height } = imageData;
247
- const inverted = new Uint8ClampedArray(data.length);
248
-
249
- for (let i = 0; i < data.length; i += 4) {
250
- inverted[i] = 255 - data[i];
251
- inverted[i + 1] = 255 - data[i + 1];
252
- inverted[i + 2] = 255 - data[i + 2];
253
- inverted[i + 3] = data[i + 3];
254
- }
255
-
256
- return new ImageData(inverted, width, height);
257
- }
258
-
259
- /**
260
- * Convert image to grayscale
261
- */
262
- export function toGrayscale(imageData: ImageData): ImageData {
263
- const { data, width, height } = imageData;
264
- const grayscale = new Uint8ClampedArray(data.length);
265
-
266
- for (let i = 0; i < data.length; i += 4) {
267
- const gray = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2];
268
- grayscale[i] = gray;
269
- grayscale[i + 1] = gray;
270
- grayscale[i + 2] = gray;
271
- grayscale[i + 3] = data[i + 3];
272
- }
273
-
274
- return new ImageData(grayscale, width, height);
275
- }