asciify-engine 1.0.40 → 1.0.42
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 +182 -115
- package/dist/index.cjs +57 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +57 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,201 +4,268 @@
|
|
|
4
4
|
<a href="https://www.npmjs.com/package/asciify-engine"><img src="https://img.shields.io/npm/v/asciify-engine?color=d4ff00&labelColor=0a0a0a&style=flat-square" alt="npm version" /></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/asciify-engine"><img src="https://img.shields.io/npm/dm/asciify-engine?color=d4ff00&labelColor=0a0a0a&style=flat-square" alt="downloads" /></a>
|
|
6
6
|
<a href="https://github.com/ayangabryl/asciify-engine/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-d4ff00?labelColor=0a0a0a&style=flat-square" alt="MIT license" /></a>
|
|
7
|
-
<a href="https://www.buymeacoffee.com/asciify"><img src="https://img.shields.io/badge/buy_me_a_coffee
|
|
7
|
+
<a href="https://www.buymeacoffee.com/asciify"><img src="https://img.shields.io/badge/buy_me_a_coffee-%E2%98%95-d4ff00?labelColor=0a0a0a&style=flat-square" alt="Buy Me A Coffee" /></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A framework-agnostic ASCII art rendering engine for the browser. Convert images, animated GIFs, and video into character-based art rendered onto an HTML canvas — with full color support, animated backgrounds, interactive hover effects, and embed generation. Zero runtime dependencies.
|
|
11
11
|
|
|
12
|
-
**[
|
|
12
|
+
**[▶ Live Playground](https://asciify.org) · [npm](https://www.npmjs.com/package/asciify-engine)**
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
asciify-engine works in two stages:
|
|
19
|
+
|
|
20
|
+
1. **Convert** — a source (image, GIF buffer, video element) is sampled and converted into an `AsciiFrame`: a 2D array of character cells, each carrying a character and RGBA color data.
|
|
21
|
+
2. **Render** — the frame is drawn onto a `<canvas>` element via a 2D context, with full support for color modes, font sizes, hover effects, and time-based animations.
|
|
22
|
+
|
|
23
|
+
This separation means you can pre-compute frames once and render them at any frame rate, making it efficient for both static images and smooth animations.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
17
28
|
|
|
18
29
|
```bash
|
|
19
30
|
npm install asciify-engine
|
|
20
31
|
```
|
|
21
32
|
|
|
33
|
+
Works with any modern bundler (Vite, webpack, esbuild, Rollup) and any framework — React, Vue, Svelte, Angular, Next.js, or vanilla JS.
|
|
34
|
+
|
|
22
35
|
---
|
|
23
36
|
|
|
24
|
-
##
|
|
37
|
+
## Converting Media to ASCII
|
|
38
|
+
|
|
39
|
+
### Images
|
|
40
|
+
|
|
41
|
+
`imageToAsciiFrame` accepts any `HTMLImageElement`, `HTMLVideoElement`, or `HTMLCanvasElement` and returns a single ASCII frame.
|
|
25
42
|
|
|
26
43
|
```ts
|
|
27
44
|
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
28
45
|
|
|
29
46
|
const img = new Image();
|
|
30
|
-
img.
|
|
47
|
+
img.crossOrigin = 'anonymous';
|
|
48
|
+
img.src = 'photo.jpg';
|
|
49
|
+
|
|
31
50
|
img.onload = () => {
|
|
32
51
|
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
33
|
-
const
|
|
34
|
-
|
|
52
|
+
const ctx = canvas.getContext('2d')!;
|
|
53
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
|
|
54
|
+
|
|
55
|
+
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
56
|
+
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height);
|
|
35
57
|
};
|
|
36
58
|
```
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
### Animated GIFs
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
`gifToAsciiFrames` parses a GIF `ArrayBuffer` and returns one `AsciiFrame` per GIF frame, preserving the original frame rate.
|
|
41
63
|
|
|
42
|
-
|
|
64
|
+
```ts
|
|
65
|
+
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
43
66
|
|
|
44
|
-
|
|
67
|
+
const buffer = await fetch('animation.gif').then(r => r.arrayBuffer());
|
|
68
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
69
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
45
70
|
|
|
46
|
-
|
|
47
|
-
import { asciiBackground } from 'asciify-engine';
|
|
71
|
+
const { frames, fps } = await gifToAsciiFrames(buffer, opts, canvas.width, canvas.height);
|
|
48
72
|
|
|
49
|
-
|
|
73
|
+
let frameIndex = 0;
|
|
74
|
+
setInterval(() => {
|
|
75
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frames[frameIndex], opts, canvas.width, canvas.height);
|
|
76
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
77
|
+
}, 1000 / fps);
|
|
50
78
|
```
|
|
51
79
|
|
|
52
|
-
|
|
80
|
+
### Video — live (recommended)
|
|
81
|
+
|
|
82
|
+
`asciifyLiveVideo` streams a video as ASCII art in real time. Pass a URL and a canvas — it handles everything else.
|
|
83
|
+
|
|
84
|
+
> ⚠️ Never set the `<video>` element to `display: none`. Browsers skip GPU frame decoding for hidden elements — you get a blank canvas. `asciifyLiveVideo` handles this automatically.
|
|
53
85
|
|
|
54
86
|
```ts
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
import { asciifyLiveVideo } from 'asciify-engine';
|
|
88
|
+
|
|
89
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
90
|
+
const stop = await asciifyLiveVideo('/clip.mp4', canvas);
|
|
91
|
+
|
|
92
|
+
// With options:
|
|
93
|
+
const stop = await asciifyLiveVideo('/clip.mp4', canvas, {
|
|
94
|
+
fontSize: 6,
|
|
95
|
+
artStyle: 'matrix',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Clean up:
|
|
57
99
|
stop();
|
|
58
100
|
```
|
|
59
101
|
|
|
60
|
-
|
|
102
|
+
### Video — pre-extracted frames
|
|
61
103
|
|
|
62
|
-
|
|
104
|
+
`asciifyVideo` seeks through the clip frame by frame and returns a frame sequence. Good for short clips where you want frame-perfect control, but requires up-front processing time.
|
|
63
105
|
|
|
64
106
|
```ts
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
density: 0.6, // how many cells are active (0–1)
|
|
71
|
-
accentColor: '#d4ff00' // highlight colour
|
|
72
|
-
});
|
|
107
|
+
import { asciifyVideo } from 'asciify-engine';
|
|
108
|
+
|
|
109
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
110
|
+
const stop = await asciifyVideo('/clip.mp4', canvas, { fontSize: 8 });
|
|
111
|
+
stop();
|
|
73
112
|
```
|
|
74
113
|
|
|
75
114
|
---
|
|
76
115
|
|
|
77
|
-
##
|
|
116
|
+
## Rendering Options
|
|
78
117
|
|
|
79
|
-
|
|
118
|
+
All conversion and render functions accept an `AsciiOptions` object. Spread `DEFAULT_OPTIONS` as a base and override what you need.
|
|
80
119
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
120
|
+
| Option | Type | Default | Description |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| `fontSize` | `number` | `10` | Character cell size in pixels. Smaller values increase density and detail. |
|
|
123
|
+
| `colorMode` | `'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent'` | `'grayscale'` | Determines how pixel color is mapped to character color. |
|
|
124
|
+
| `charset` | `string` | Standard ramp | Characters ordered from dense to sparse, representing brightness levels. |
|
|
125
|
+
| `brightness` | `number` | `0` | Brightness adjustment from `-1` (darker) to `1` (lighter). |
|
|
126
|
+
| `contrast` | `number` | `1` | Contrast multiplier applied before character mapping. |
|
|
127
|
+
| `invert` | `boolean` | `false` | Inverts the luminance mapping — light areas become dense, dark areas sparse. |
|
|
128
|
+
| `renderMode` | `'ascii' \| 'dots'` | `'ascii'` | Render as text characters or circular dot particles. |
|
|
129
|
+
| `hoverEffect` | `string` | `'none'` | Interactive effect driven by cursor position. See hover effects below. |
|
|
130
|
+
| `hoverStrength` | `number` | `0.8` | Effect intensity (0–1). |
|
|
131
|
+
| `hoverRadius` | `number` | `0.3` | Effect radius relative to canvas size (0–1). |
|
|
84
132
|
|
|
85
|
-
|
|
86
|
-
const ref = useRef<HTMLCanvasElement>(null);
|
|
133
|
+
### Color Modes
|
|
87
134
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
|
|
95
|
-
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
96
|
-
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
|
|
97
|
-
};
|
|
98
|
-
}, [src]);
|
|
135
|
+
| Mode | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `grayscale` | Classic monochrome ASCII. Character brightness maps to source luminance. |
|
|
138
|
+
| `fullcolor` | Each character inherits the original pixel color from the source. |
|
|
139
|
+
| `matrix` | Monochrome green — inspired by classic terminal aesthetics. |
|
|
140
|
+
| `accent` | Single accent color applied uniformly across all characters. |
|
|
99
141
|
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
```
|
|
142
|
+
### Hover Effects
|
|
103
143
|
|
|
104
|
-
|
|
144
|
+
Interactive effects that respond to cursor movement. Pass the effect name to `hoverEffect` and supply the cursor position to `renderFrameToCanvas` at render time.
|
|
105
145
|
|
|
106
|
-
|
|
107
|
-
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
146
|
+
Available effects: `spotlight` · `flashlight` · `magnifier` · `force-field` · `neon` · `fire` · `ice` · `gravity` · `shatter` · `ghost`
|
|
108
147
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
148
|
+
```ts
|
|
149
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
150
|
+
const rect = canvas.getBoundingClientRect();
|
|
151
|
+
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height, Date.now() / 1000, {
|
|
152
|
+
x: e.clientX - rect.left,
|
|
153
|
+
y: e.clientY - rect.top,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
```
|
|
112
157
|
|
|
113
|
-
|
|
158
|
+
---
|
|
114
159
|
|
|
115
|
-
|
|
116
|
-
setInterval(() => {
|
|
117
|
-
renderFrameToCanvas(canvas.getContext('2d')!, frames[i], opts, canvas.width, canvas.height);
|
|
118
|
-
i = (i + 1) % frames.length;
|
|
119
|
-
}, 1000 / fps);
|
|
120
|
-
```
|
|
160
|
+
## Animated Backgrounds
|
|
121
161
|
|
|
122
|
-
|
|
162
|
+
`asciiBackground` mounts a self-animating ASCII renderer onto any DOM element — ideal for hero sections, banners, or full-page backgrounds. It manages its own canvas, animation loop, and resize handling internally.
|
|
123
163
|
|
|
124
164
|
```ts
|
|
125
|
-
import {
|
|
165
|
+
import { asciiBackground } from 'asciify-engine';
|
|
126
166
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
167
|
+
const stop = asciiBackground('#hero', {
|
|
168
|
+
type: 'rain',
|
|
169
|
+
colorScheme: 'auto', // follows OS dark/light mode
|
|
170
|
+
speed: 1.0,
|
|
171
|
+
density: 0.55,
|
|
172
|
+
accentColor: '#d4ff00',
|
|
173
|
+
});
|
|
130
174
|
|
|
131
|
-
|
|
132
|
-
|
|
175
|
+
// Stop and clean up when no longer needed
|
|
176
|
+
stop();
|
|
177
|
+
```
|
|
133
178
|
|
|
134
|
-
|
|
179
|
+
### Available Background Types
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
181
|
+
| Type | Description |
|
|
182
|
+
|---|---|
|
|
183
|
+
| `wave` | Flowing sine-wave field with layered noise turbulence |
|
|
184
|
+
| `rain` | Vertical column rain with a glowing leading character and fading trail |
|
|
185
|
+
| `stars` | Parallax star field that reacts to cursor position |
|
|
186
|
+
| `pulse` | Concentric ripple bursts emanating from the cursor |
|
|
187
|
+
| `noise` | Smooth value-noise field with organic, fluid motion |
|
|
188
|
+
| `grid` | Geometric grid that warps and brightens near the cursor |
|
|
189
|
+
| `aurora` | Sweeping borealis-style color bands drifting across the field |
|
|
190
|
+
| `silk` | Fluid swirl simulation following cursor movement |
|
|
191
|
+
| `void` | Gravitational singularity — characters spiral inward toward the cursor |
|
|
192
|
+
| `morph` | Characters morph between shapes over time, driven by noise |
|
|
193
|
+
|
|
194
|
+
### Background Options
|
|
195
|
+
|
|
196
|
+
| Option | Type | Default | Description |
|
|
197
|
+
|---|---|---|---|
|
|
198
|
+
| `type` | `string` | `'wave'` | Which background renderer to use. |
|
|
199
|
+
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | `'auto'` reacts to OS theme changes in real time. |
|
|
200
|
+
| `fontSize` | `number` | `13` | Character size in pixels. |
|
|
201
|
+
| `speed` | `number` | `1` | Global animation speed multiplier. |
|
|
202
|
+
| `density` | `number` | `0.55` | Fraction of grid cells that are active (0–1). |
|
|
203
|
+
| `accentColor` | `string` | varies | Highlight or leading-character color (any CSS color string). |
|
|
204
|
+
| `color` | `string` | — | Override the body character color. |
|
|
142
205
|
|
|
143
206
|
---
|
|
144
207
|
|
|
145
|
-
##
|
|
208
|
+
## React Integration
|
|
146
209
|
|
|
147
|
-
|
|
210
|
+
```tsx
|
|
211
|
+
import { useEffect, useRef } from 'react';
|
|
212
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
148
213
|
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
...DEFAULT_OPTIONS,
|
|
152
|
-
fontSize: 8, // smaller = more detail
|
|
153
|
-
colorMode: 'matrix', // 'grayscale' | 'fullcolor' | 'matrix' | 'accent'
|
|
154
|
-
charset: '@#S%?*+;:,. ', // custom brightness ramp (dense → light)
|
|
155
|
-
brightness: 0.1, // -1 to 1
|
|
156
|
-
contrast: 1.2,
|
|
157
|
-
invert: false,
|
|
158
|
-
hoverEffect: 'spotlight', // interactive effect on cursor move
|
|
159
|
-
};
|
|
160
|
-
```
|
|
214
|
+
export function AsciiImage({ src }: { src: string }) {
|
|
215
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
161
216
|
|
|
162
|
-
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const canvas = canvasRef.current;
|
|
219
|
+
if (!canvas) return;
|
|
163
220
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
const img = new Image();
|
|
222
|
+
img.crossOrigin = 'anonymous';
|
|
223
|
+
img.src = src;
|
|
224
|
+
img.onload = () => {
|
|
225
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
|
|
226
|
+
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
227
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
|
|
228
|
+
};
|
|
229
|
+
}, [src]);
|
|
170
230
|
|
|
171
|
-
|
|
231
|
+
return <canvas ref={canvasRef} width={800} height={600} />;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
172
234
|
|
|
173
235
|
---
|
|
174
236
|
|
|
175
|
-
## Embed
|
|
237
|
+
## Embed Generation
|
|
176
238
|
|
|
177
|
-
|
|
239
|
+
Generate self-contained HTML that can be hosted anywhere or dropped directly into a page — no runtime dependency required.
|
|
178
240
|
|
|
179
241
|
```ts
|
|
180
242
|
import { generateEmbedCode, generateAnimatedEmbedCode } from 'asciify-engine';
|
|
181
243
|
|
|
182
|
-
// Static
|
|
183
|
-
const
|
|
244
|
+
// Static — produces a single-file HTML with the ASCII art baked in
|
|
245
|
+
const staticHtml = generateEmbedCode(frame, options);
|
|
184
246
|
|
|
185
|
-
// Animated
|
|
186
|
-
const
|
|
247
|
+
// Animated — produces a self-running HTML animation
|
|
248
|
+
const animatedHtml = generateAnimatedEmbedCode(frames, options, fps);
|
|
187
249
|
```
|
|
188
250
|
|
|
189
251
|
---
|
|
190
252
|
|
|
191
|
-
## API
|
|
192
|
-
|
|
193
|
-
| Function |
|
|
194
|
-
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
253
|
+
## API Reference
|
|
254
|
+
|
|
255
|
+
| Function | Signature | Returns |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `asciify` | `(source, canvas, options?)` | `Promise<void>` |
|
|
258
|
+
| `asciifyLiveVideo` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
259
|
+
| `asciifyVideo` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
260
|
+
| `asciifyGif` | `(source, canvas, options?)` | `Promise<() => void>` |
|
|
261
|
+
| `asciifyWebcam` | `(canvas, options?)` | `Promise<() => void>` |
|
|
262
|
+
| `asciiBackground` | `(selector, options)` | `() => void` |
|
|
263
|
+
| `imageToAsciiFrame` | `(source, options, w?, h?)` | `{ frame, cols, rows }` |
|
|
264
|
+
| `renderFrameToCanvas` | `(ctx, frame, options, w, h, time?, hoverPos?)` | `void` |
|
|
265
|
+
| `gifToAsciiFrames` | `(buffer, options, w, h, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
266
|
+
| `videoToAsciiFrames` | `(video, options, w, h, fps?, maxSec?, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
267
|
+
| `generateEmbedCode` | `(frame, options)` | `string` |
|
|
268
|
+
| `generateAnimatedEmbedCode` | `(frames, options, fps)` | `string` |
|
|
202
269
|
|
|
203
270
|
---
|
|
204
271
|
|
package/dist/index.cjs
CHANGED
|
@@ -1077,6 +1077,62 @@ async function asciifyVideo(source, canvas, { fontSize = 10, artStyle = "classic
|
|
|
1077
1077
|
cancelAnimationFrame(animId);
|
|
1078
1078
|
};
|
|
1079
1079
|
}
|
|
1080
|
+
async function asciifyLiveVideo(source, canvas, { fontSize = 10, artStyle = "classic", options = {} } = {}) {
|
|
1081
|
+
let video;
|
|
1082
|
+
let ownedVideo = false;
|
|
1083
|
+
if (typeof source === "string") {
|
|
1084
|
+
video = document.createElement("video");
|
|
1085
|
+
video.src = source;
|
|
1086
|
+
video.muted = true;
|
|
1087
|
+
video.loop = true;
|
|
1088
|
+
video.playsInline = true;
|
|
1089
|
+
video.setAttribute("playsinline", "");
|
|
1090
|
+
Object.assign(video.style, {
|
|
1091
|
+
position: "fixed",
|
|
1092
|
+
top: "0",
|
|
1093
|
+
left: "0",
|
|
1094
|
+
width: "1px",
|
|
1095
|
+
height: "1px",
|
|
1096
|
+
opacity: "0",
|
|
1097
|
+
pointerEvents: "none",
|
|
1098
|
+
zIndex: "-1"
|
|
1099
|
+
});
|
|
1100
|
+
document.body.appendChild(video);
|
|
1101
|
+
ownedVideo = true;
|
|
1102
|
+
await new Promise((resolve, reject) => {
|
|
1103
|
+
video.onloadedmetadata = () => resolve();
|
|
1104
|
+
video.onerror = () => reject(new Error(`asciifyLiveVideo: failed to load "${source}"`));
|
|
1105
|
+
});
|
|
1106
|
+
video.play().catch(() => {
|
|
1107
|
+
});
|
|
1108
|
+
} else {
|
|
1109
|
+
video = source;
|
|
1110
|
+
if (video.paused) video.play().catch(() => {
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize };
|
|
1114
|
+
const ctx = canvas.getContext("2d");
|
|
1115
|
+
if (!ctx) throw new Error("asciifyLiveVideo: could not get 2d context from canvas.");
|
|
1116
|
+
let cancelled = false;
|
|
1117
|
+
let animId;
|
|
1118
|
+
const tick = () => {
|
|
1119
|
+
if (cancelled) return;
|
|
1120
|
+
animId = requestAnimationFrame(tick);
|
|
1121
|
+
if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
|
|
1122
|
+
const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
|
|
1123
|
+
if (frame.length > 0) renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
|
|
1124
|
+
};
|
|
1125
|
+
animId = requestAnimationFrame(tick);
|
|
1126
|
+
return () => {
|
|
1127
|
+
cancelled = true;
|
|
1128
|
+
cancelAnimationFrame(animId);
|
|
1129
|
+
if (ownedVideo) {
|
|
1130
|
+
video.pause();
|
|
1131
|
+
video.src = "";
|
|
1132
|
+
document.body.removeChild(video);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1080
1136
|
|
|
1081
1137
|
// src/backgrounds/rain.ts
|
|
1082
1138
|
function renderRainBackground(ctx, width, height, time, options = {}) {
|
|
@@ -2468,6 +2524,7 @@ exports.asciiText = asciiText;
|
|
|
2468
2524
|
exports.asciiTextAnsi = asciiTextAnsi;
|
|
2469
2525
|
exports.asciify = asciify;
|
|
2470
2526
|
exports.asciifyGif = asciifyGif;
|
|
2527
|
+
exports.asciifyLiveVideo = asciifyLiveVideo;
|
|
2471
2528
|
exports.asciifyVideo = asciifyVideo;
|
|
2472
2529
|
exports.asciifyWebcam = asciifyWebcam;
|
|
2473
2530
|
exports.buildTextFrame = buildTextFrame;
|