asciify-engine 1.0.34 → 1.0.36
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/README.md +217 -124
- package/dist/index.cjs +12 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -6
- package/dist/index.d.ts +85 -6
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,133 @@
|
|
|
28
28
|
npm install asciify-engine
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Image → ASCII
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { asciify } from 'asciify-engine';
|
|
37
|
+
|
|
38
|
+
const canvas = document.querySelector('canvas')!;
|
|
39
|
+
|
|
40
|
+
// Minimal — just a URL and a canvas, no img.onload boilerplate needed
|
|
41
|
+
await asciify('https://example.com/photo.jpg', canvas);
|
|
42
|
+
|
|
43
|
+
// With an art style preset
|
|
44
|
+
await asciify('photo.jpg', canvas, { artStyle: 'letters' });
|
|
45
|
+
|
|
46
|
+
// Full control
|
|
47
|
+
await asciify('photo.jpg', canvas, {
|
|
48
|
+
fontSize: 8,
|
|
49
|
+
artStyle: 'art',
|
|
50
|
+
options: {
|
|
51
|
+
colorMode: 'fullcolor',
|
|
52
|
+
invert: true,
|
|
53
|
+
hoverEffect: 'spotlight',
|
|
54
|
+
hoverStrength: 0.5,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### GIF Animation
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { asciifyGif } from 'asciify-engine';
|
|
63
|
+
|
|
64
|
+
// Fetches, converts, and starts the animation loop — returns a stop() function
|
|
65
|
+
const stop = await asciifyGif('animation.gif', canvas);
|
|
66
|
+
|
|
67
|
+
// Clean up the animation loop when done
|
|
68
|
+
stop();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Video
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { asciifyVideo } from 'asciify-engine';
|
|
75
|
+
|
|
76
|
+
// Accepts a URL string or an existing HTMLVideoElement
|
|
77
|
+
const stop = await asciifyVideo('/my-video.mp4', canvas, { artStyle: 'terminal' });
|
|
78
|
+
|
|
79
|
+
// Cancel when unmounting / navigating away
|
|
80
|
+
stop();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### React
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { useEffect, useRef } from 'react';
|
|
87
|
+
import { asciify } from 'asciify-engine';
|
|
88
|
+
|
|
89
|
+
export function AsciiImage({ src }: { src: string }) {
|
|
90
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (ref.current) asciify(src, ref.current, { artStyle: 'art' });
|
|
94
|
+
}, [src]);
|
|
95
|
+
|
|
96
|
+
return <canvas ref={ref} width={800} height={600} />;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Animated GIF or video in React
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { useEffect, useRef } from 'react';
|
|
104
|
+
import { asciifyGif } from 'asciify-engine';
|
|
105
|
+
|
|
106
|
+
export function AsciiGif({ src }: { src: string }) {
|
|
107
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
let stop: (() => void) | undefined;
|
|
111
|
+
asciifyGif(src, ref.current!).then(fn => { stop = fn; });
|
|
112
|
+
return () => stop?.(); // cancels the rAF loop on unmount
|
|
113
|
+
}, [src]);
|
|
114
|
+
|
|
115
|
+
return <canvas ref={ref} width={800} height={600} />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Vue
|
|
120
|
+
|
|
121
|
+
```vue
|
|
122
|
+
<template>
|
|
123
|
+
<canvas ref="canvasRef" width="800" height="600" />
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<script setup lang="ts">
|
|
127
|
+
import { useTemplateRef, onMounted } from 'vue';
|
|
128
|
+
import { asciify } from 'asciify-engine';
|
|
129
|
+
|
|
130
|
+
const props = defineProps<{ src: string; artStyle?: string }>();
|
|
131
|
+
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');
|
|
132
|
+
|
|
133
|
+
onMounted(() => asciify(props.src, canvasRef.value!, { artStyle: props.artStyle as any }));
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Angular
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { Component, ElementRef, ViewChild, AfterViewInit, Input } from '@angular/core';
|
|
141
|
+
import { asciify } from 'asciify-engine';
|
|
142
|
+
|
|
143
|
+
@Component({
|
|
144
|
+
selector: 'app-ascii',
|
|
145
|
+
template: `<canvas #canvas width="800" height="600"></canvas>`,
|
|
146
|
+
})
|
|
147
|
+
export class AsciiComponent implements AfterViewInit {
|
|
148
|
+
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
149
|
+
@Input() src = '';
|
|
150
|
+
@Input() artStyle = 'classic';
|
|
151
|
+
|
|
152
|
+
ngAfterViewInit() {
|
|
153
|
+
asciify(this.src, this.canvasRef.nativeElement, { artStyle: this.artStyle as any });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
31
158
|
## Animated Backgrounds
|
|
32
159
|
|
|
33
160
|
Drop a live ASCII animation into any element with one call.
|
|
@@ -84,23 +211,12 @@ Each background type also accepts its own specific options — see the individua
|
|
|
84
211
|
All renderers are also exported individually for direct canvas use:
|
|
85
212
|
|
|
86
213
|
```ts
|
|
87
|
-
import {
|
|
88
|
-
|
|
89
|
-
renderWaveBackground,
|
|
90
|
-
renderStarsBackground,
|
|
91
|
-
renderPulseBackground,
|
|
92
|
-
renderNoiseBackground,
|
|
93
|
-
renderGridBackground,
|
|
94
|
-
renderAuroraBackground,
|
|
95
|
-
renderSilkBackground,
|
|
96
|
-
renderVoidBackground,
|
|
97
|
-
renderMorphBackground,
|
|
98
|
-
} from 'asciify-engine';
|
|
99
|
-
|
|
100
|
-
// Example: drive the rain renderer yourself
|
|
214
|
+
import { renderRainBackground } from 'asciify-engine';
|
|
215
|
+
|
|
101
216
|
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
|
|
102
217
|
const ctx = canvas.getContext('2d')!;
|
|
103
218
|
let t = 0;
|
|
219
|
+
|
|
104
220
|
function tick() {
|
|
105
221
|
renderRainBackground(ctx, canvas.width, canvas.height, t, {
|
|
106
222
|
speed: 1.2,
|
|
@@ -114,113 +230,39 @@ function tick() {
|
|
|
114
230
|
tick();
|
|
115
231
|
```
|
|
116
232
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
### Vanilla JS
|
|
120
|
-
|
|
121
|
-
```html
|
|
122
|
-
<canvas id="ascii" width="800" height="600"></canvas>
|
|
123
|
-
<script type="module">
|
|
124
|
-
import {
|
|
125
|
-
imageToAsciiFrame,
|
|
126
|
-
renderFrameToCanvas,
|
|
127
|
-
DEFAULT_OPTIONS,
|
|
128
|
-
} from 'asciify-engine';
|
|
129
|
-
|
|
130
|
-
const img = new Image();
|
|
131
|
-
img.crossOrigin = 'anonymous';
|
|
132
|
-
img.src = 'https://picsum.photos/600/400';
|
|
133
|
-
img.onload = () => {
|
|
134
|
-
const canvas = document.getElementById('ascii');
|
|
135
|
-
const options = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
136
|
-
const { frame } = imageToAsciiFrame(img, options, canvas.width, canvas.height);
|
|
137
|
-
renderFrameToCanvas(canvas.getContext('2d'), frame, options, canvas.width, canvas.height);
|
|
138
|
-
};
|
|
139
|
-
</script>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### React
|
|
143
|
-
|
|
144
|
-
```tsx
|
|
145
|
-
import { useEffect, useRef } from 'react';
|
|
146
|
-
import {
|
|
147
|
-
imageToAsciiFrame,
|
|
148
|
-
renderFrameToCanvas,
|
|
149
|
-
DEFAULT_OPTIONS,
|
|
150
|
-
} from 'asciify-engine';
|
|
151
|
-
|
|
152
|
-
export function AsciiImage({ src }: { src: string }) {
|
|
153
|
-
const ref = useRef<HTMLCanvasElement>(null);
|
|
154
|
-
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
const img = new Image();
|
|
157
|
-
img.crossOrigin = 'anonymous';
|
|
158
|
-
img.src = src;
|
|
159
|
-
img.onload = () => {
|
|
160
|
-
const canvas = ref.current!;
|
|
161
|
-
const opts = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
162
|
-
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
163
|
-
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
|
|
164
|
-
};
|
|
165
|
-
}, [src]);
|
|
166
|
-
|
|
167
|
-
return <canvas ref={ref} width={800} height={600} />;
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Angular
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
175
|
-
import {
|
|
176
|
-
imageToAsciiFrame,
|
|
177
|
-
renderFrameToCanvas,
|
|
178
|
-
DEFAULT_OPTIONS,
|
|
179
|
-
} from 'asciify-engine';
|
|
180
|
-
|
|
181
|
-
@Component({
|
|
182
|
-
selector: 'app-ascii',
|
|
183
|
-
template: `<canvas #canvas [width]="800" [height]="600"></canvas>`,
|
|
184
|
-
})
|
|
185
|
-
export class AsciiComponent implements AfterViewInit {
|
|
186
|
-
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
233
|
+
## Low-level API
|
|
187
234
|
|
|
188
|
-
|
|
189
|
-
const img = new Image();
|
|
190
|
-
img.crossOrigin = 'anonymous';
|
|
191
|
-
img.src = 'https://picsum.photos/600/400';
|
|
192
|
-
img.onload = () => {
|
|
193
|
-
const canvas = this.canvasRef.nativeElement;
|
|
194
|
-
const opts = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
195
|
-
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
196
|
-
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
```
|
|
235
|
+
For cases where the one-call API is too limiting — direct frame manipulation, custom render loops, progress callbacks, etc.
|
|
201
236
|
|
|
202
|
-
### GIF
|
|
237
|
+
### GIF (low-level)
|
|
203
238
|
|
|
204
239
|
```ts
|
|
205
240
|
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
206
241
|
|
|
207
|
-
const
|
|
208
|
-
const buffer = await response.arrayBuffer();
|
|
209
|
-
|
|
210
|
-
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
211
|
-
const ctx = canvas.getContext('2d')!;
|
|
242
|
+
const buffer = await fetch('animation.gif').then(r => r.arrayBuffer());
|
|
212
243
|
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
213
|
-
|
|
214
244
|
const { frames, fps } = await gifToAsciiFrames(buffer, options, canvas.width, canvas.height);
|
|
215
245
|
|
|
246
|
+
const ctx = canvas.getContext('2d')!;
|
|
216
247
|
let i = 0;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
let last = performance.now();
|
|
249
|
+
const interval = 1000 / fps;
|
|
250
|
+
|
|
251
|
+
let animId: number;
|
|
252
|
+
const tick = (now: number) => {
|
|
253
|
+
if (now - last >= interval) {
|
|
254
|
+
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
255
|
+
i = (i + 1) % frames.length;
|
|
256
|
+
last = now;
|
|
257
|
+
}
|
|
258
|
+
animId = requestAnimationFrame(tick);
|
|
259
|
+
};
|
|
260
|
+
animId = requestAnimationFrame(tick);
|
|
261
|
+
|
|
262
|
+
// Clean up → cancelAnimationFrame(animId);
|
|
221
263
|
```
|
|
222
264
|
|
|
223
|
-
### Video
|
|
265
|
+
### Video (low-level)
|
|
224
266
|
|
|
225
267
|
```ts
|
|
226
268
|
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
@@ -228,18 +270,52 @@ import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciif
|
|
|
228
270
|
const video = document.createElement('video');
|
|
229
271
|
video.crossOrigin = 'anonymous';
|
|
230
272
|
video.src = '/my-video.mp4';
|
|
231
|
-
|
|
273
|
+
// Guard against cached video where onloadeddata already fired
|
|
274
|
+
if (video.readyState < 2) {
|
|
275
|
+
await new Promise<void>(r => { video.onloadeddata = () => r(); });
|
|
276
|
+
}
|
|
232
277
|
|
|
233
|
-
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
234
278
|
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
235
|
-
|
|
236
279
|
const { frames, fps } = await videoToAsciiFrames(video, options, canvas.width, canvas.height, 12, 10);
|
|
237
280
|
|
|
281
|
+
const ctx = canvas.getContext('2d')!;
|
|
238
282
|
let i = 0;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
283
|
+
let last = performance.now();
|
|
284
|
+
const interval = 1000 / fps;
|
|
285
|
+
|
|
286
|
+
let animId: number;
|
|
287
|
+
const tick = (now: number) => {
|
|
288
|
+
if (now - last >= interval) {
|
|
289
|
+
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
290
|
+
i = (i + 1) % frames.length;
|
|
291
|
+
last = now;
|
|
292
|
+
}
|
|
293
|
+
animId = requestAnimationFrame(tick);
|
|
294
|
+
};
|
|
295
|
+
animId = requestAnimationFrame(tick);
|
|
296
|
+
|
|
297
|
+
// Clean up → cancelAnimationFrame(animId);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Vanilla JS (low-level image)
|
|
301
|
+
|
|
302
|
+
```html
|
|
303
|
+
<canvas id="ascii" width="800" height="600"></canvas>
|
|
304
|
+
<script type="module">
|
|
305
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
306
|
+
|
|
307
|
+
const canvas = document.getElementById('ascii');
|
|
308
|
+
const ctx = canvas.getContext('2d');
|
|
309
|
+
const options = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
310
|
+
|
|
311
|
+
const img = new Image();
|
|
312
|
+
img.crossOrigin = 'anonymous';
|
|
313
|
+
img.src = 'https://picsum.photos/600/400';
|
|
314
|
+
img.onload = () => {
|
|
315
|
+
const { frame } = imageToAsciiFrame(img, options, canvas.width, canvas.height);
|
|
316
|
+
renderFrameToCanvas(ctx, frame, options, canvas.width, canvas.height);
|
|
317
|
+
};
|
|
318
|
+
</script>
|
|
243
319
|
```
|
|
244
320
|
|
|
245
321
|
## API Reference
|
|
@@ -248,14 +324,27 @@ setInterval(() => {
|
|
|
248
324
|
|
|
249
325
|
| Function | Returns | Description |
|
|
250
326
|
|---|---|---|
|
|
251
|
-
| `
|
|
252
|
-
| `
|
|
327
|
+
| `asciify(source, canvas, opts?)` | `Promise<void>` | Convert an image/video/canvas (or URL) to ASCII and render — handles loading automatically |
|
|
328
|
+
| `asciifyGif(source, canvas, opts?)` | `Promise<() => void>` | Fetch a GIF, convert, and start an animation loop — returns `stop()` |
|
|
329
|
+
| `asciifyVideo(source, canvas, opts?)` | `Promise<() => void>` | Convert a video and start an animation loop — returns `stop()` |
|
|
330
|
+
| `imageToAsciiFrame(source, options, w?, h?)` | `{ frame, cols, rows }` | Convert an image, video frame, or canvas to a raw ASCII frame |
|
|
331
|
+
| `renderFrameToCanvas(ctx, frame, options, w, h, time?, hoverPos?)` | `void` | Render a raw ASCII frame to a 2D canvas context |
|
|
253
332
|
| `gifToAsciiFrames(buffer, options, w, h, onProgress?)` | `{ frames, cols, rows, fps }` | Parse an animated GIF `ArrayBuffer` into ASCII frames |
|
|
254
333
|
| `videoToAsciiFrames(video, options, w, h, fps?, maxDuration?, onProgress?)` | `{ frames, cols, rows, fps }` | Extract and convert video frames to ASCII |
|
|
255
334
|
| `asciiBackground(selector, options)` | `() => void` | Mount a live animated ASCII background; returns a cleanup function |
|
|
256
335
|
| `generateEmbedCode(frame, options)` | `string` | Self-contained static HTML embed |
|
|
257
336
|
| `generateAnimatedEmbedCode(frames, options, fps)` | `string` | Self-contained animated HTML embed |
|
|
258
337
|
|
|
338
|
+
### Simple API Options (`AsciifySimpleOptions`)
|
|
339
|
+
|
|
340
|
+
Used by `asciify()`, `asciifyGif()`, and `asciifyVideo()`:
|
|
341
|
+
|
|
342
|
+
| Option | Type | Default | Description |
|
|
343
|
+
|---|---|---|---|
|
|
344
|
+
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
345
|
+
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset — sets charset, render mode, and color mode together |
|
|
346
|
+
| `options` | `Partial<AsciiOptions>` | `{}` | Fine-grained overrides applied on top of the preset |
|
|
347
|
+
|
|
259
348
|
### Key Options (`AsciiOptions`)
|
|
260
349
|
|
|
261
350
|
| Option | Type | Default | Description |
|
|
@@ -263,13 +352,17 @@ setInterval(() => {
|
|
|
263
352
|
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
264
353
|
| `colorMode` | `'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent'` | `'grayscale'` | Color output mode |
|
|
265
354
|
| `renderMode` | `'ascii' \| 'dots'` | `'ascii'` | Render as characters or dot particles |
|
|
266
|
-
| `charset` | `string` |
|
|
267
|
-
| `brightness` | `number` | `0` | Brightness adjustment (
|
|
268
|
-
| `contrast` | `number` | `
|
|
355
|
+
| `charset` | `string` | `' .:-=+*#%@'` | Custom character density ramp (light → dark) |
|
|
356
|
+
| `brightness` | `number` | `0` | Brightness adjustment (−1 → 0 → 1) |
|
|
357
|
+
| `contrast` | `number` | `0` | Contrast boost (0 = unchanged, positive = more contrast) |
|
|
269
358
|
| `invert` | `boolean` | `false` | Invert luminance mapping |
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
359
|
+
| `animationStyle` | `AnimationStyle` | `'none'` | Per-character animation driven over time |
|
|
360
|
+
| `hoverEffect` | `HoverEffect` | `'spotlight'` | Cursor interaction style |
|
|
361
|
+
| `hoverStrength` | `number` | `0` | Effect intensity (0 = disabled) |
|
|
362
|
+
| `hoverRadius` | `number` | `0.2` | Effect radius as a fraction of canvas size |
|
|
363
|
+
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset (see `ART_STYLE_PRESETS`) |
|
|
364
|
+
| `ditherStrength` | `number` | `0` | Floyd-Steinberg dither intensity (0–1) |
|
|
365
|
+
| `dotSizeRatio` | `number` | `0.8` | Dot size when `renderMode === 'dots'` (fraction of cell) |
|
|
273
366
|
|
|
274
367
|
### Background Options
|
|
275
368
|
|
package/dist/index.cjs
CHANGED
|
@@ -970,7 +970,7 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
|
|
|
970
970
|
}
|
|
971
971
|
|
|
972
972
|
// src/core/simple-api.ts
|
|
973
|
-
async function asciify(source, canvas, { fontSize = 10,
|
|
973
|
+
async function asciify(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
|
|
974
974
|
let el;
|
|
975
975
|
if (typeof source === "string") {
|
|
976
976
|
const img = new Image();
|
|
@@ -990,16 +990,16 @@ async function asciify(source, canvas, { fontSize = 10, style = "classic", optio
|
|
|
990
990
|
} else {
|
|
991
991
|
el = source;
|
|
992
992
|
}
|
|
993
|
-
const preset = ART_STYLE_PRESETS[
|
|
993
|
+
const preset = ART_STYLE_PRESETS[artStyle];
|
|
994
994
|
const merged = { ...DEFAULT_OPTIONS, ...preset, ...options, fontSize };
|
|
995
995
|
const ctx = canvas.getContext("2d");
|
|
996
996
|
if (!ctx) throw new Error("Could not get 2d context from canvas");
|
|
997
997
|
const { frame } = imageToAsciiFrame(el, merged, canvas.width, canvas.height);
|
|
998
998
|
renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height);
|
|
999
999
|
}
|
|
1000
|
-
async function asciifyGif(source, canvas, { fontSize = 10,
|
|
1000
|
+
async function asciifyGif(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
|
|
1001
1001
|
const buffer = typeof source === "string" ? await fetch(source).then((r) => r.arrayBuffer()) : source;
|
|
1002
|
-
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[
|
|
1002
|
+
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1003
1003
|
const ctx = canvas.getContext("2d");
|
|
1004
1004
|
if (!ctx) throw new Error("Could not get 2d context from canvas");
|
|
1005
1005
|
const { frames, fps } = await gifToAsciiFrames(buffer, merged, canvas.width, canvas.height);
|
|
@@ -1023,20 +1023,22 @@ async function asciifyGif(source, canvas, { fontSize = 10, style = "classic", op
|
|
|
1023
1023
|
cancelAnimationFrame(animId);
|
|
1024
1024
|
};
|
|
1025
1025
|
}
|
|
1026
|
-
async function asciifyVideo(source, canvas, { fontSize = 10,
|
|
1026
|
+
async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
|
|
1027
1027
|
let video;
|
|
1028
1028
|
if (typeof source === "string") {
|
|
1029
1029
|
video = document.createElement("video");
|
|
1030
1030
|
video.crossOrigin = "anonymous";
|
|
1031
1031
|
video.src = source;
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1032
|
+
if (video.readyState < 2) {
|
|
1033
|
+
await new Promise((resolve, reject) => {
|
|
1034
|
+
video.onloadeddata = () => resolve();
|
|
1035
|
+
video.onerror = () => reject(new Error(`Failed to load video: ${source}`));
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1036
1038
|
} else {
|
|
1037
1039
|
video = source;
|
|
1038
1040
|
}
|
|
1039
|
-
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[
|
|
1041
|
+
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1040
1042
|
const ctx = canvas.getContext("2d");
|
|
1041
1043
|
if (!ctx) throw new Error("Could not get 2d context from canvas");
|
|
1042
1044
|
const { frames, fps } = await videoToAsciiFrames(video, merged, canvas.width, canvas.height);
|