asciify-engine 1.0.34 → 1.0.35
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 +379 -0
- 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
|
@@ -9,6 +9,385 @@
|
|
|
9
9
|
|
|
10
10
|
> Convert images, videos, and GIFs into ASCII art on HTML canvas. 13 art styles, 4 color modes, 10 animated backgrounds, interactive hover effects — zero dependencies.
|
|
11
11
|
|
|
12
|
+
**[▶ Live Playground](https://asciify.org) · [npm](https://www.npmjs.com/package/asciify-engine) · [Support the project ☕](https://www.buymeacoffee.com/asciify)**
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
- **Media → ASCII** — images, videos, and animated GIFs
|
|
17
|
+
- **13 art styles** — Standard, Blocks, Circles, Braille, Katakana, Dense, and more
|
|
18
|
+
- **4 color modes** — Grayscale, Full Color, Matrix, Accent
|
|
19
|
+
- **10 animated backgrounds** — Wave, Rain, Stars, Pulse, Noise, Grid, Aurora, Silk, Void, Morph
|
|
20
|
+
- **Interactive hover effects** — Spotlight, Flashlight, Magnifier, Force Field, Neon, Fire, Ice, Gravity, Shatter, Ghost
|
|
21
|
+
- **Light & dark mode** — via `colorScheme: 'auto'` or explicit `light` / `dark`
|
|
22
|
+
- **Embed generation** — self-contained HTML output (static or animated)
|
|
23
|
+
- **Zero dependencies** — works with React, Vue, Angular, Svelte, Next.js, or vanilla JS
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install asciify-engine
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **90% of use cases need only two functions:** `asciify()` to convert images/video/GIFs to ASCII art, and `asciiBackground()` for animated backgrounds. Start there.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Image → ASCII
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { asciify } from 'asciify-engine';
|
|
39
|
+
|
|
40
|
+
const canvas = document.querySelector('canvas')!;
|
|
41
|
+
|
|
42
|
+
// Minimal — just a URL and a canvas, no img.onload boilerplate needed
|
|
43
|
+
await asciify('https://example.com/photo.jpg', canvas);
|
|
44
|
+
|
|
45
|
+
// With an art style preset
|
|
46
|
+
await asciify('photo.jpg', canvas, { artStyle: 'letters' });
|
|
47
|
+
|
|
48
|
+
// Full control
|
|
49
|
+
await asciify('photo.jpg', canvas, {
|
|
50
|
+
fontSize: 8,
|
|
51
|
+
artStyle: 'art',
|
|
52
|
+
options: {
|
|
53
|
+
colorMode: 'fullcolor',
|
|
54
|
+
invert: true,
|
|
55
|
+
hoverEffect: 'spotlight',
|
|
56
|
+
hoverStrength: 0.5,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### GIF Animation
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { asciifyGif } from 'asciify-engine';
|
|
65
|
+
|
|
66
|
+
// Fetches, converts, and starts the animation loop — returns a stop() function
|
|
67
|
+
const stop = await asciifyGif('animation.gif', canvas);
|
|
68
|
+
|
|
69
|
+
// Clean up the animation loop when done
|
|
70
|
+
stop();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Video
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { asciifyVideo } from 'asciify-engine';
|
|
77
|
+
|
|
78
|
+
// Accepts a URL string or an existing HTMLVideoElement
|
|
79
|
+
const stop = await asciifyVideo('/my-video.mp4', canvas, { artStyle: 'terminal' });
|
|
80
|
+
|
|
81
|
+
// Cancel when unmounting / navigating away
|
|
82
|
+
stop();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### React
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { useEffect, useRef } from 'react';
|
|
89
|
+
import { asciify } from 'asciify-engine';
|
|
90
|
+
|
|
91
|
+
export function AsciiImage({ src }: { src: string }) {
|
|
92
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (ref.current) asciify(src, ref.current, { artStyle: 'art' });
|
|
96
|
+
}, [src]);
|
|
97
|
+
|
|
98
|
+
return <canvas ref={ref} width={800} height={600} />;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Animated GIF or video in React
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useEffect, useRef } from 'react';
|
|
106
|
+
import { asciifyGif } from 'asciify-engine';
|
|
107
|
+
|
|
108
|
+
export function AsciiGif({ src }: { src: string }) {
|
|
109
|
+
const ref = useRef<HTMLCanvasElement>(null);
|
|
110
|
+
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
let stop: (() => void) | undefined;
|
|
113
|
+
asciifyGif(src, ref.current!).then(fn => { stop = fn; });
|
|
114
|
+
return () => stop?.(); // cancels the rAF loop on unmount
|
|
115
|
+
}, [src]);
|
|
116
|
+
|
|
117
|
+
return <canvas ref={ref} width={800} height={600} />;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Vue
|
|
122
|
+
|
|
123
|
+
```vue
|
|
124
|
+
<template>
|
|
125
|
+
<canvas ref="canvasRef" width="800" height="600" />
|
|
126
|
+
</template>
|
|
127
|
+
|
|
128
|
+
<script setup lang="ts">
|
|
129
|
+
import { useTemplateRef, onMounted } from 'vue';
|
|
130
|
+
import { asciify } from 'asciify-engine';
|
|
131
|
+
|
|
132
|
+
const props = defineProps<{ src: string; artStyle?: string }>();
|
|
133
|
+
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');
|
|
134
|
+
|
|
135
|
+
onMounted(() => asciify(props.src, canvasRef.value!, { artStyle: props.artStyle as any }));
|
|
136
|
+
</script>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Angular
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Component, ElementRef, ViewChild, AfterViewInit, Input } from '@angular/core';
|
|
143
|
+
import { asciify } from 'asciify-engine';
|
|
144
|
+
|
|
145
|
+
@Component({
|
|
146
|
+
selector: 'app-ascii',
|
|
147
|
+
template: `<canvas #canvas width="800" height="600"></canvas>`,
|
|
148
|
+
})
|
|
149
|
+
export class AsciiComponent implements AfterViewInit {
|
|
150
|
+
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
151
|
+
@Input() src = '';
|
|
152
|
+
@Input() artStyle = 'classic';
|
|
153
|
+
|
|
154
|
+
ngAfterViewInit() {
|
|
155
|
+
asciify(this.src, this.canvasRef.nativeElement, { artStyle: this.artStyle as any });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Animated Backgrounds
|
|
161
|
+
|
|
162
|
+
Drop a live ASCII animation into any element with one call.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { asciiBackground } from 'asciify-engine';
|
|
166
|
+
|
|
167
|
+
// Attach to any selector — animates the element's background
|
|
168
|
+
asciiBackground('#hero', { type: 'rain' });
|
|
169
|
+
|
|
170
|
+
// Follows OS dark/light mode automatically
|
|
171
|
+
asciiBackground('#hero', { type: 'aurora', colorScheme: 'auto' });
|
|
172
|
+
|
|
173
|
+
// Force light mode (dark characters on light background)
|
|
174
|
+
asciiBackground('#hero', { type: 'wave', colorScheme: 'light' });
|
|
175
|
+
|
|
176
|
+
// Stop / clean up
|
|
177
|
+
const stop = asciiBackground('#hero', { type: 'stars' });
|
|
178
|
+
stop();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Background Types
|
|
182
|
+
|
|
183
|
+
| Type | Description |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `wave` | Flowing sine-wave field with noise turbulence |
|
|
186
|
+
| `rain` | Matrix-style vertical column rain with glowing head and fading tail |
|
|
187
|
+
| `stars` | Parallax star field that reacts to cursor position |
|
|
188
|
+
| `pulse` | Concentric ripple bursts that emanate from the cursor |
|
|
189
|
+
| `noise` | Smooth value-noise field with organic flow |
|
|
190
|
+
| `grid` | Geometric grid that warps and glows at cursor proximity |
|
|
191
|
+
| `aurora` | Sweeping borealis-style colour bands |
|
|
192
|
+
| `silk` | Silky fluid swirls following the cursor |
|
|
193
|
+
| `void` | Gravitational singularity — characters spiral inward toward cursor |
|
|
194
|
+
| `morph` | Characters morph between shapes driven by noise |
|
|
195
|
+
|
|
196
|
+
### `asciiBackground` Options
|
|
197
|
+
|
|
198
|
+
| Option | Type | Default | Description |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| `type` | `string` | `'wave'` | Which background renderer to use |
|
|
201
|
+
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | `'auto'` follows OS theme live; `'light'` = dark chars on light bg |
|
|
202
|
+
| `fontSize` | `number` | `13` | Character size in px |
|
|
203
|
+
| `accentColor` | `string` | varies | Head/highlight colour (CSS colour string) |
|
|
204
|
+
| `color` | `string` | — | Override body character colour |
|
|
205
|
+
| `speed` | `number` | `1` | Global animation speed multiplier |
|
|
206
|
+
| `density` | `number` | `0.55` | Fraction of cells active (0–1) |
|
|
207
|
+
| `lightMode` | `boolean` | `false` | Dark characters on light background |
|
|
208
|
+
|
|
209
|
+
Each background type also accepts its own specific options — see the individual type exports (e.g. `RainBackgroundOptions`, `WaveBackgroundOptions`, etc.) for the full list.
|
|
210
|
+
|
|
211
|
+
### Low-level background renderers
|
|
212
|
+
|
|
213
|
+
All renderers are also exported individually for direct canvas use:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { renderRainBackground } from 'asciify-engine';
|
|
217
|
+
|
|
218
|
+
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
|
|
219
|
+
const ctx = canvas.getContext('2d')!;
|
|
220
|
+
let t = 0;
|
|
221
|
+
|
|
222
|
+
function tick() {
|
|
223
|
+
renderRainBackground(ctx, canvas.width, canvas.height, t, {
|
|
224
|
+
speed: 1.2,
|
|
225
|
+
density: 0.6,
|
|
226
|
+
accentColor: '#00ffcc',
|
|
227
|
+
lightMode: false,
|
|
228
|
+
});
|
|
229
|
+
t += 0.016;
|
|
230
|
+
requestAnimationFrame(tick);
|
|
231
|
+
}
|
|
232
|
+
tick();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Low-level API
|
|
236
|
+
|
|
237
|
+
For cases where the one-call API is too limiting — direct frame manipulation, custom render loops, progress callbacks, etc.
|
|
238
|
+
|
|
239
|
+
### GIF (low-level)
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
243
|
+
|
|
244
|
+
const buffer = await fetch('animation.gif').then(r => r.arrayBuffer());
|
|
245
|
+
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
246
|
+
const { frames, fps } = await gifToAsciiFrames(buffer, options, canvas.width, canvas.height);
|
|
247
|
+
|
|
248
|
+
const ctx = canvas.getContext('2d')!;
|
|
249
|
+
let i = 0;
|
|
250
|
+
let last = performance.now();
|
|
251
|
+
const interval = 1000 / fps;
|
|
252
|
+
|
|
253
|
+
let animId: number;
|
|
254
|
+
const tick = (now: number) => {
|
|
255
|
+
if (now - last >= interval) {
|
|
256
|
+
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
257
|
+
i = (i + 1) % frames.length;
|
|
258
|
+
last = now;
|
|
259
|
+
}
|
|
260
|
+
animId = requestAnimationFrame(tick);
|
|
261
|
+
};
|
|
262
|
+
animId = requestAnimationFrame(tick);
|
|
263
|
+
|
|
264
|
+
// Clean up → cancelAnimationFrame(animId);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Video (low-level)
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
271
|
+
|
|
272
|
+
const video = document.createElement('video');
|
|
273
|
+
video.crossOrigin = 'anonymous';
|
|
274
|
+
video.src = '/my-video.mp4';
|
|
275
|
+
// Guard against cached video where onloadeddata already fired
|
|
276
|
+
if (video.readyState < 2) {
|
|
277
|
+
await new Promise<void>(r => { video.onloadeddata = () => r(); });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
281
|
+
const { frames, fps } = await videoToAsciiFrames(video, options, canvas.width, canvas.height, 12, 10);
|
|
282
|
+
|
|
283
|
+
const ctx = canvas.getContext('2d')!;
|
|
284
|
+
let i = 0;
|
|
285
|
+
let last = performance.now();
|
|
286
|
+
const interval = 1000 / fps;
|
|
287
|
+
|
|
288
|
+
let animId: number;
|
|
289
|
+
const tick = (now: number) => {
|
|
290
|
+
if (now - last >= interval) {
|
|
291
|
+
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
292
|
+
i = (i + 1) % frames.length;
|
|
293
|
+
last = now;
|
|
294
|
+
}
|
|
295
|
+
animId = requestAnimationFrame(tick);
|
|
296
|
+
};
|
|
297
|
+
animId = requestAnimationFrame(tick);
|
|
298
|
+
|
|
299
|
+
// Clean up → cancelAnimationFrame(animId);
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Vanilla JS (low-level image)
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<canvas id="ascii" width="800" height="600"></canvas>
|
|
306
|
+
<script type="module">
|
|
307
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
308
|
+
|
|
309
|
+
const canvas = document.getElementById('ascii');
|
|
310
|
+
const ctx = canvas.getContext('2d');
|
|
311
|
+
const options = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
312
|
+
|
|
313
|
+
const img = new Image();
|
|
314
|
+
img.crossOrigin = 'anonymous';
|
|
315
|
+
img.src = 'https://picsum.photos/600/400';
|
|
316
|
+
img.onload = () => {
|
|
317
|
+
const { frame } = imageToAsciiFrame(img, options, canvas.width, canvas.height);
|
|
318
|
+
renderFrameToCanvas(ctx, frame, options, canvas.width, canvas.height);
|
|
319
|
+
};
|
|
320
|
+
</script>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## API Reference
|
|
324
|
+
|
|
325
|
+
### Core Functions
|
|
326
|
+
|
|
327
|
+
| Function | Returns | Description |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| `asciify(source, canvas, opts?)` | `Promise<void>` | Convert an image/video/canvas (or URL) to ASCII and render — handles loading automatically |
|
|
330
|
+
| `asciifyGif(source, canvas, opts?)` | `Promise<() => void>` | Fetch a GIF, convert, and start an animation loop — returns `stop()` |
|
|
331
|
+
| `asciifyVideo(source, canvas, opts?)` | `Promise<() => void>` | Convert a video and start an animation loop — returns `stop()` |
|
|
332
|
+
| `imageToAsciiFrame(source, options, w?, h?)` | `{ frame, cols, rows }` | Convert an image, video frame, or canvas to a raw ASCII frame |
|
|
333
|
+
| `renderFrameToCanvas(ctx, frame, options, w, h, time?, hoverPos?)` | `void` | Render a raw ASCII frame to a 2D canvas context |
|
|
334
|
+
| `gifToAsciiFrames(buffer, options, w, h, onProgress?)` | `{ frames, cols, rows, fps }` | Parse an animated GIF `ArrayBuffer` into ASCII frames |
|
|
335
|
+
| `videoToAsciiFrames(video, options, w, h, fps?, maxDuration?, onProgress?)` | `{ frames, cols, rows, fps }` | Extract and convert video frames to ASCII |
|
|
336
|
+
| `asciiBackground(selector, options)` | `() => void` | Mount a live animated ASCII background; returns a cleanup function |
|
|
337
|
+
| `generateEmbedCode(frame, options)` | `string` | Self-contained static HTML embed |
|
|
338
|
+
| `generateAnimatedEmbedCode(frames, options, fps)` | `string` | Self-contained animated HTML embed |
|
|
339
|
+
|
|
340
|
+
### Simple API Options (`AsciifySimpleOptions`)
|
|
341
|
+
|
|
342
|
+
Used by `asciify()`, `asciifyGif()`, and `asciifyVideo()`:
|
|
343
|
+
|
|
344
|
+
| Option | Type | Default | Description |
|
|
345
|
+
|---|---|---|---|
|
|
346
|
+
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
347
|
+
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset — sets charset, render mode, and color mode together |
|
|
348
|
+
| `options` | `Partial<AsciiOptions>` | `{}` | Fine-grained overrides applied on top of the preset |
|
|
349
|
+
|
|
350
|
+
### Key Options (`AsciiOptions`)
|
|
351
|
+
|
|
352
|
+
| Option | Type | Default | Description |
|
|
353
|
+
|---|---|---|---|
|
|
354
|
+
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
355
|
+
| `colorMode` | `'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent'` | `'grayscale'` | Color output mode |
|
|
356
|
+
| `renderMode` | `'ascii' \| 'dots'` | `'ascii'` | Render as characters or dot particles |
|
|
357
|
+
| `charset` | `string` | `' .:-=+*#%@'` | Custom character density ramp (light → dark) |
|
|
358
|
+
| `brightness` | `number` | `0` | Brightness adjustment (−1 → 0 → 1) |
|
|
359
|
+
| `contrast` | `number` | `0` | Contrast boost (0 = unchanged, positive = more contrast) |
|
|
360
|
+
| `invert` | `boolean` | `false` | Invert luminance mapping |
|
|
361
|
+
| `animationStyle` | `AnimationStyle` | `'none'` | Per-character animation driven over time |
|
|
362
|
+
| `hoverEffect` | `HoverEffect` | `'spotlight'` | Cursor interaction style |
|
|
363
|
+
| `hoverStrength` | `number` | `0` | Effect intensity (0 = disabled) |
|
|
364
|
+
| `hoverRadius` | `number` | `0.2` | Effect radius as a fraction of canvas size |
|
|
365
|
+
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset (see `ART_STYLE_PRESETS`) |
|
|
366
|
+
| `ditherStrength` | `number` | `0` | Floyd-Steinberg dither intensity (0–1) |
|
|
367
|
+
| `dotSizeRatio` | `number` | `0.8` | Dot size when `renderMode === 'dots'` (fraction of cell) |
|
|
368
|
+
|
|
369
|
+
### Background Options
|
|
370
|
+
|
|
371
|
+
| Option | Type | Default | Description |
|
|
372
|
+
|---|---|---|---|
|
|
373
|
+
| `type` | `string` | `'wave'` | Background renderer |
|
|
374
|
+
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | Theme; `'auto'` follows OS preference |
|
|
375
|
+
| `fontSize` | `number` | `13` | Character size |
|
|
376
|
+
| `speed` | `number` | `1` | Animation speed multiplier |
|
|
377
|
+
| `density` | `number` | `0.55` | Fraction of cells active (0–1) |
|
|
378
|
+
| `accentColor` | `string` | varies | Highlight / head colour |
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT © [asciify.org](https://asciify.org)
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
<p align="left">
|
|
387
|
+
<a href="https://www.buymeacoffee.com/asciify">☕ Buy me a coffee — if this saved you time, I'd appreciate it!</a>
|
|
388
|
+
</p>
|
|
389
|
+
|
|
390
|
+
|
|
12
391
|
**[▶ Live Playground](https://asciify.org) · [npm](https://www.npmjs.com/package/asciify-engine) · [Support the project ☕](https://www.buymeacoffee.com/asciify)**
|
|
13
392
|
|
|
14
393
|
## Features
|
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);
|