asciify-engine 1.0.38 → 1.0.40
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 +117 -336
- package/dist/index.cjs +17 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,23 +4,14 @@
|
|
|
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-☕-d4ff00?labelColor=0a0a0a&style=flat-square" alt="Buy Me A Coffee" /></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Turn any image, video, or GIF into ASCII art on an HTML canvas — with live animated backgrounds, hover effects, and zero dependencies.
|
|
11
11
|
|
|
12
|
-
**[▶
|
|
12
|
+
**[▶ Try the live playground](https://asciify.org) · [npm](https://www.npmjs.com/package/asciify-engine)**
|
|
13
13
|
|
|
14
|
-
|
|
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
|
-
- **14 animated backgrounds** — Wave, Rain, Stars, Pulse, Noise, Grid, Aurora, Silk, Void, Morph, Fire, DNA, Terrain, Circuit
|
|
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
|
|
14
|
+
---
|
|
24
15
|
|
|
25
16
|
## Install
|
|
26
17
|
|
|
@@ -28,401 +19,191 @@
|
|
|
28
19
|
npm install asciify-engine
|
|
29
20
|
```
|
|
30
21
|
|
|
31
|
-
|
|
22
|
+
---
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
## The 30-second version
|
|
34
25
|
|
|
35
26
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
});
|
|
27
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
28
|
+
|
|
29
|
+
const img = new Image();
|
|
30
|
+
img.src = 'your-image.jpg';
|
|
31
|
+
img.onload = () => {
|
|
32
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
33
|
+
const { frame } = imageToAsciiFrame(img, DEFAULT_OPTIONS, canvas.width, canvas.height);
|
|
34
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frame, DEFAULT_OPTIONS, canvas.width, canvas.height);
|
|
35
|
+
};
|
|
57
36
|
```
|
|
58
37
|
|
|
59
|
-
|
|
38
|
+
That's it — one function to convert, one to draw. Everything else is optional.
|
|
60
39
|
|
|
61
|
-
|
|
40
|
+
---
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
import { asciifyWebcam } from 'asciify-engine';
|
|
42
|
+
## Animated backgrounds
|
|
65
43
|
|
|
66
|
-
|
|
44
|
+
Add a living ASCII animation to any element with one line:
|
|
67
45
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
artStyle: 'terminal',
|
|
71
|
-
mirror: true, // horizontal flip (selfie mode)
|
|
72
|
-
});
|
|
46
|
+
```ts
|
|
47
|
+
import { asciiBackground } from 'asciify-engine';
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
stop();
|
|
49
|
+
asciiBackground('#hero', { type: 'rain' });
|
|
76
50
|
```
|
|
77
51
|
|
|
78
|
-
|
|
52
|
+
Returns a cleanup function when you're done:
|
|
79
53
|
|
|
80
54
|
```ts
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Fetches, converts, and starts the animation loop — returns a stop() function
|
|
84
|
-
const stop = await asciifyGif('animation.gif', canvas);
|
|
85
|
-
|
|
86
|
-
// Clean up the animation loop when done
|
|
55
|
+
const stop = asciiBackground('#hero', { type: 'aurora', colorScheme: 'auto' });
|
|
56
|
+
// later…
|
|
87
57
|
stop();
|
|
88
58
|
```
|
|
89
59
|
|
|
90
|
-
|
|
60
|
+
**Available types:** `wave` · `rain` · `stars` · `pulse` · `noise` · `grid` · `aurora` · `silk` · `void` · `morph`
|
|
61
|
+
|
|
62
|
+
**Common options:**
|
|
91
63
|
|
|
92
64
|
```ts
|
|
93
|
-
|
|
65
|
+
asciiBackground('#el', {
|
|
66
|
+
type: 'stars',
|
|
67
|
+
colorScheme: 'auto', // 'auto' | 'light' | 'dark' — 'auto' follows OS theme
|
|
68
|
+
fontSize: 13, // character size in px
|
|
69
|
+
speed: 1.2, // animation speed multiplier
|
|
70
|
+
density: 0.6, // how many cells are active (0–1)
|
|
71
|
+
accentColor: '#d4ff00' // highlight colour
|
|
72
|
+
});
|
|
73
|
+
```
|
|
94
74
|
|
|
95
|
-
|
|
96
|
-
const stop = await asciifyVideo('/my-video.mp4', canvas, { artStyle: 'terminal' });
|
|
75
|
+
---
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
stop();
|
|
100
|
-
```
|
|
77
|
+
## More recipes
|
|
101
78
|
|
|
102
79
|
### React
|
|
103
80
|
|
|
104
81
|
```tsx
|
|
105
82
|
import { useEffect, useRef } from 'react';
|
|
106
|
-
import {
|
|
83
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
107
84
|
|
|
108
85
|
export function AsciiImage({ src }: { src: string }) {
|
|
109
86
|
const ref = useRef<HTMLCanvasElement>(null);
|
|
110
87
|
|
|
111
88
|
useEffect(() => {
|
|
112
|
-
|
|
89
|
+
const img = new Image();
|
|
90
|
+
img.crossOrigin = 'anonymous';
|
|
91
|
+
img.src = src;
|
|
92
|
+
img.onload = () => {
|
|
93
|
+
const canvas = ref.current!;
|
|
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
|
+
};
|
|
113
98
|
}, [src]);
|
|
114
99
|
|
|
115
100
|
return <canvas ref={ref} width={800} height={600} />;
|
|
116
101
|
}
|
|
117
102
|
```
|
|
118
103
|
|
|
119
|
-
### Animated GIF
|
|
104
|
+
### Animated GIF
|
|
120
105
|
|
|
121
|
-
```
|
|
122
|
-
import {
|
|
123
|
-
import { asciifyGif } from 'asciify-engine';
|
|
106
|
+
```ts
|
|
107
|
+
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
const buf = await fetch('animation.gif').then(r => r.arrayBuffer());
|
|
110
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
111
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
127
112
|
|
|
128
|
-
|
|
129
|
-
let stop: (() => void) | undefined;
|
|
130
|
-
asciifyGif(src, ref.current!).then(fn => { stop = fn; });
|
|
131
|
-
return () => stop?.(); // cancels the rAF loop on unmount
|
|
132
|
-
}, [src]);
|
|
113
|
+
const { frames, fps } = await gifToAsciiFrames(buf, opts, canvas.width, canvas.height);
|
|
133
114
|
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
let i = 0;
|
|
116
|
+
setInterval(() => {
|
|
117
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frames[i], opts, canvas.width, canvas.height);
|
|
118
|
+
i = (i + 1) % frames.length;
|
|
119
|
+
}, 1000 / fps);
|
|
136
120
|
```
|
|
137
121
|
|
|
138
|
-
###
|
|
139
|
-
|
|
140
|
-
```vue
|
|
141
|
-
<template>
|
|
142
|
-
<canvas ref="canvasRef" width="800" height="600" />
|
|
143
|
-
</template>
|
|
144
|
-
|
|
145
|
-
<script setup lang="ts">
|
|
146
|
-
import { useTemplateRef, onMounted } from 'vue';
|
|
147
|
-
import { asciify } from 'asciify-engine';
|
|
148
|
-
|
|
149
|
-
const props = defineProps<{ src: string; artStyle?: string }>();
|
|
150
|
-
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');
|
|
122
|
+
### Video
|
|
151
123
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```
|
|
124
|
+
```ts
|
|
125
|
+
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
155
126
|
|
|
156
|
-
|
|
127
|
+
const video = document.createElement('video');
|
|
128
|
+
video.src = '/clip.mp4';
|
|
129
|
+
await new Promise(r => (video.onloadeddata = r));
|
|
157
130
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
import { asciify } from 'asciify-engine';
|
|
131
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
132
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
161
133
|
|
|
162
|
-
|
|
163
|
-
selector: 'app-ascii',
|
|
164
|
-
template: `<canvas #canvas width="800" height="600"></canvas>`,
|
|
165
|
-
})
|
|
166
|
-
export class AsciiComponent implements AfterViewInit {
|
|
167
|
-
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
168
|
-
@Input() src = '';
|
|
169
|
-
@Input() artStyle = 'classic';
|
|
134
|
+
const { frames, fps } = await videoToAsciiFrames(video, opts, canvas.width, canvas.height);
|
|
170
135
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
136
|
+
let i = 0;
|
|
137
|
+
setInterval(() => {
|
|
138
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frames[i], opts, canvas.width, canvas.height);
|
|
139
|
+
i = (i + 1) % frames.length;
|
|
140
|
+
}, 1000 / fps);
|
|
175
141
|
```
|
|
176
142
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
Drop a live ASCII animation into any element with one call.
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { asciiBackground } from 'asciify-engine';
|
|
183
|
-
|
|
184
|
-
// Attach to any selector — animates the element's background
|
|
185
|
-
asciiBackground('#hero', { type: 'rain' });
|
|
143
|
+
---
|
|
186
144
|
|
|
187
|
-
|
|
188
|
-
asciiBackground('#hero', { type: 'aurora', colorScheme: 'auto' });
|
|
145
|
+
## Tweaking the output
|
|
189
146
|
|
|
190
|
-
|
|
191
|
-
asciiBackground('#hero', { type: 'wave', colorScheme: 'light' });
|
|
147
|
+
Pass options to any function to change the look:
|
|
192
148
|
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
149
|
+
```ts
|
|
150
|
+
const opts = {
|
|
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
|
+
};
|
|
196
160
|
```
|
|
197
161
|
|
|
198
|
-
|
|
162
|
+
**Color modes at a glance:**
|
|
199
163
|
|
|
200
|
-
|
|
|
164
|
+
| Mode | What it does |
|
|
201
165
|
|---|---|
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
204
|
-
| `
|
|
205
|
-
| `
|
|
206
|
-
| `noise` | Smooth value-noise field with organic flow |
|
|
207
|
-
| `grid` | Geometric grid that warps and glows at cursor proximity |
|
|
208
|
-
| `aurora` | Sweeping borealis-style colour bands |
|
|
209
|
-
| `silk` | Silky fluid swirls following the cursor |
|
|
210
|
-
| `void` | Gravitational singularity — characters spiral inward toward cursor |
|
|
211
|
-
| `morph` | Characters morph between shapes driven by noise |
|
|
212
|
-
| `fire` | Upward-drifting flame columns with heat-gradient character mapping |
|
|
213
|
-
| `dna` | Rotating double-helix strands with base-pair characters |
|
|
214
|
-
| `terrain` | Procedural heightmap landscape with parallax depth layers |
|
|
215
|
-
| `circuit` | PCB trace network that pulses with traveling electric signals |
|
|
216
|
-
|
|
217
|
-
### `asciiBackground` Options
|
|
218
|
-
|
|
219
|
-
| Option | Type | Default | Description |
|
|
220
|
-
|---|---|---|---|
|
|
221
|
-
| `type` | `string` | `'wave'` | Which background renderer to use |
|
|
222
|
-
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | `'auto'` follows OS theme live; `'light'` = dark chars on light bg |
|
|
223
|
-
| `fontSize` | `number` | `13` | Character size in px |
|
|
224
|
-
| `accentColor` | `string` | varies | Head/highlight colour (CSS colour string) |
|
|
225
|
-
| `color` | `string` | — | Override body character colour |
|
|
226
|
-
| `speed` | `number` | `1` | Global animation speed multiplier |
|
|
227
|
-
| `density` | `number` | `0.55` | Fraction of cells active (0–1) |
|
|
228
|
-
| `lightMode` | `boolean` | `false` | Dark characters on light background |
|
|
229
|
-
|
|
230
|
-
Each background type also accepts its own specific options — see the individual type exports (e.g. `RainBackgroundOptions`, `WaveBackgroundOptions`, etc.) for the full list.
|
|
231
|
-
|
|
232
|
-
### Low-level background renderers
|
|
233
|
-
|
|
234
|
-
All renderers are also exported individually for direct canvas use:
|
|
166
|
+
| `grayscale` | Classic monochrome ASCII |
|
|
167
|
+
| `fullcolor` | Preserves original pixel colours |
|
|
168
|
+
| `matrix` | Everything in green, like the film |
|
|
169
|
+
| `accent` | Single highlight colour |
|
|
235
170
|
|
|
236
|
-
|
|
237
|
-
import { renderRainBackground } from 'asciify-engine';
|
|
238
|
-
|
|
239
|
-
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
|
|
240
|
-
const ctx = canvas.getContext('2d')!;
|
|
241
|
-
let t = 0;
|
|
242
|
-
|
|
243
|
-
function tick() {
|
|
244
|
-
renderRainBackground(ctx, canvas.width, canvas.height, t, {
|
|
245
|
-
speed: 1.2,
|
|
246
|
-
density: 0.6,
|
|
247
|
-
accentColor: '#00ffcc',
|
|
248
|
-
lightMode: false,
|
|
249
|
-
});
|
|
250
|
-
t += 0.016;
|
|
251
|
-
requestAnimationFrame(tick);
|
|
252
|
-
}
|
|
253
|
-
tick();
|
|
254
|
-
```
|
|
171
|
+
**Hover effects:** `spotlight` · `flashlight` · `magnifier` · `force-field` · `neon` · `fire` · `ice` · `gravity` · `shatter` · `ghost`
|
|
255
172
|
|
|
256
|
-
|
|
173
|
+
---
|
|
257
174
|
|
|
258
|
-
|
|
175
|
+
## Embed generation
|
|
259
176
|
|
|
260
|
-
|
|
177
|
+
Export your ASCII art as a self-contained HTML file:
|
|
261
178
|
|
|
262
179
|
```ts
|
|
263
|
-
import {
|
|
264
|
-
|
|
265
|
-
const buffer = await fetch('animation.gif').then(r => r.arrayBuffer());
|
|
266
|
-
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
267
|
-
const { frames, fps } = await gifToAsciiFrames(buffer, options, canvas.width, canvas.height);
|
|
180
|
+
import { generateEmbedCode, generateAnimatedEmbedCode } from 'asciify-engine';
|
|
268
181
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
let last = performance.now();
|
|
272
|
-
const interval = 1000 / fps;
|
|
273
|
-
|
|
274
|
-
let animId: number;
|
|
275
|
-
const tick = (now: number) => {
|
|
276
|
-
if (now - last >= interval) {
|
|
277
|
-
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
278
|
-
i = (i + 1) % frames.length;
|
|
279
|
-
last = now;
|
|
280
|
-
}
|
|
281
|
-
animId = requestAnimationFrame(tick);
|
|
282
|
-
};
|
|
283
|
-
animId = requestAnimationFrame(tick);
|
|
182
|
+
// Static image
|
|
183
|
+
const html = generateEmbedCode(frame, options);
|
|
284
184
|
|
|
285
|
-
//
|
|
185
|
+
// Animated
|
|
186
|
+
const html = generateAnimatedEmbedCode(frames, options, fps);
|
|
286
187
|
```
|
|
287
188
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
```ts
|
|
291
|
-
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
292
|
-
|
|
293
|
-
const video = document.createElement('video');
|
|
294
|
-
video.crossOrigin = 'anonymous';
|
|
295
|
-
video.src = '/my-video.mp4';
|
|
296
|
-
// Guard against cached video where onloadeddata already fired
|
|
297
|
-
if (video.readyState < 2) {
|
|
298
|
-
await new Promise<void>(r => { video.onloadeddata = () => r(); });
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
302
|
-
const { frames, fps } = await videoToAsciiFrames(video, options, canvas.width, canvas.height, 12, 10);
|
|
303
|
-
|
|
304
|
-
const ctx = canvas.getContext('2d')!;
|
|
305
|
-
let i = 0;
|
|
306
|
-
let last = performance.now();
|
|
307
|
-
const interval = 1000 / fps;
|
|
308
|
-
|
|
309
|
-
let animId: number;
|
|
310
|
-
const tick = (now: number) => {
|
|
311
|
-
if (now - last >= interval) {
|
|
312
|
-
renderFrameToCanvas(ctx, frames[i], options, canvas.width, canvas.height);
|
|
313
|
-
i = (i + 1) % frames.length;
|
|
314
|
-
last = now;
|
|
315
|
-
}
|
|
316
|
-
animId = requestAnimationFrame(tick);
|
|
317
|
-
};
|
|
318
|
-
animId = requestAnimationFrame(tick);
|
|
189
|
+
---
|
|
319
190
|
|
|
320
|
-
|
|
321
|
-
```
|
|
191
|
+
## API summary
|
|
322
192
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const options = { ...DEFAULT_OPTIONS, fontSize: 10 };
|
|
333
|
-
|
|
334
|
-
const img = new Image();
|
|
335
|
-
img.crossOrigin = 'anonymous';
|
|
336
|
-
img.src = 'https://picsum.photos/600/400';
|
|
337
|
-
img.onload = () => {
|
|
338
|
-
const { frame } = imageToAsciiFrame(img, options, canvas.width, canvas.height);
|
|
339
|
-
renderFrameToCanvas(ctx, frame, options, canvas.width, canvas.height);
|
|
340
|
-
};
|
|
341
|
-
</script>
|
|
342
|
-
```
|
|
193
|
+
| Function | Description |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `imageToAsciiFrame(img, opts, w, h)` | Convert an image/video/canvas element to an ASCII frame |
|
|
196
|
+
| `renderFrameToCanvas(ctx, frame, opts, w, h, time?, pos?)` | Draw an ASCII frame onto a canvas 2D context |
|
|
197
|
+
| `gifToAsciiFrames(buffer, opts, w, h)` | Parse an animated GIF into ASCII frames |
|
|
198
|
+
| `videoToAsciiFrames(video, opts, w, h, fps?, maxSec?)` | Extract video frames and convert them to ASCII |
|
|
199
|
+
| `asciiBackground(selector, opts)` | Mount a live animated ASCII background |
|
|
200
|
+
| `generateEmbedCode(frame, opts)` | Self-contained static HTML snippet |
|
|
201
|
+
| `generateAnimatedEmbedCode(frames, opts, fps)` | Self-contained animated HTML snippet |
|
|
343
202
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
### Core Functions
|
|
347
|
-
|
|
348
|
-
| Function | Returns | Description |
|
|
349
|
-
|---|---|---|
|
|
350
|
-
| `asciify(source, canvas, opts?)` | `Promise<void>` | Render a **single** ASCII frame from an image (or URL). For animated loops use `asciifyGif` / `asciifyVideo` |
|
|
351
|
-
| `asciifyGif(source, canvas, opts?)` | `Promise<() => void>` | Fetch a GIF, convert, and start an animation loop — returns `stop()` |
|
|
352
|
-
| `asciifyVideo(source, canvas, opts?)` | `Promise<() => void>` | Convert a video and start an animation loop — returns `stop()` |
|
|
353
|
-
| `imageToAsciiFrame(source, options, w?, h?)` | `{ frame, cols, rows }` | Convert an image, video frame, or canvas to a raw ASCII frame |
|
|
354
|
-
| `renderFrameToCanvas(ctx, frame, options, w, h, time?, hoverPos?)` | `void` | Render a raw ASCII frame to a 2D canvas context |
|
|
355
|
-
| `gifToAsciiFrames(buffer, options, w, h, onProgress?)` | `{ frames, cols, rows, fps }` | Parse an animated GIF `ArrayBuffer` into ASCII frames |
|
|
356
|
-
| `videoToAsciiFrames(video, options, w, h, fps?, maxDuration?, onProgress?)` | `{ frames, cols, rows, fps }` | Extract and convert video frames to ASCII |
|
|
357
|
-
| `asciifyWebcam(canvas, opts?)` | `Promise<() => void>` | Start a live webcam → ASCII loop; returns `stop()` to cancel and release the camera |
|
|
358
|
-
| `asciiBackground(target, options)` | `{ destroy: () => void }` | Mount a live animated ASCII background; call `destroy()` to stop and remove |
|
|
359
|
-
| `generateEmbedCode(frame, options)` | `string` | Self-contained static HTML embed |
|
|
360
|
-
| `generateAnimatedEmbedCode(frames, options, fps)` | `string` | Self-contained animated HTML embed |
|
|
361
|
-
|
|
362
|
-
### Simple API Options (`AsciifySimpleOptions`)
|
|
363
|
-
|
|
364
|
-
Used by `asciify()`, `asciifyGif()`, and `asciifyVideo()`:
|
|
365
|
-
|
|
366
|
-
| Option | Type | Default | Description |
|
|
367
|
-
|---|---|---|---|
|
|
368
|
-
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
369
|
-
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset — sets charset, render mode, and color mode together |
|
|
370
|
-
| `options` | `Partial<AsciiOptions>` | `{}` | Fine-grained overrides applied on top of the preset |
|
|
371
|
-
|
|
372
|
-
### Key Options (`AsciiOptions`)
|
|
373
|
-
|
|
374
|
-
| Option | Type | Default | Description |
|
|
375
|
-
|---|---|---|---|
|
|
376
|
-
| `fontSize` | `number` | `10` | Character cell size in px |
|
|
377
|
-
| `colorMode` | `'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent'` | `'grayscale'` | Color output mode |
|
|
378
|
-
| `renderMode` | `'ascii' \| 'dots'` | `'ascii'` | Render as characters or dot particles |
|
|
379
|
-
| `charset` | `string` | `' .:-=+*#%@'` | Custom character density ramp (light → dark) |
|
|
380
|
-
| `brightness` | `number` | `0` | Brightness adjustment (−1 → 0 → 1) |
|
|
381
|
-
| `contrast` | `number` | `0` | Contrast boost (0 = unchanged, positive = more contrast) |
|
|
382
|
-
| `invert` | `boolean` | `false` | Invert luminance mapping |
|
|
383
|
-
| `animationStyle` | `AnimationStyle` | `'none'` | Per-character animation driven over time |
|
|
384
|
-
| `hoverEffect` | `HoverEffect` | `'spotlight'` | Cursor interaction style |
|
|
385
|
-
| `hoverStrength` | `number` | `0` | Effect intensity (0 = disabled) |
|
|
386
|
-
| `hoverRadius` | `number` | `0.2` | Effect radius as a fraction of canvas size |
|
|
387
|
-
| `artStyle` | `ArtStyle` | `'classic'` | Art style preset (see `ART_STYLE_PRESETS`) |
|
|
388
|
-
| `ditherStrength` | `number` | `0` | Floyd-Steinberg dither intensity (0–1) |
|
|
389
|
-
| `dotSizeRatio` | `number` | `0.8` | Dot size when `renderMode === 'dots'` (fraction of cell) |
|
|
390
|
-
|
|
391
|
-
### Art Styles (`artStyle`)
|
|
392
|
-
|
|
393
|
-
| Value | Color mode | Description |
|
|
394
|
-
|---|---|---|
|
|
395
|
-
| `classic` | Grayscale | Standard density ramp — clean, universally readable |
|
|
396
|
-
| `art` | Full color | 70-char dense ramp for maximum tonal detail |
|
|
397
|
-
| `particles` | Full color | Dot circles (`renderMode: 'dots'`) — great for photos |
|
|
398
|
-
| `letters` | Full color | Alphabet characters with pixel-accurate color |
|
|
399
|
-
| `terminal` | Matrix | Classic charset with green phosphor / Matrix look |
|
|
400
|
-
| `claudeCode` | Accent | Box-drawing chars with accent color — technical/hacker aesthetic |
|
|
401
|
-
| `braille` | Full color | 256-char braille block — ultra-dense, printed feel |
|
|
402
|
-
| `katakana` | Matrix | Half-width katakana — anime / cyberpunk aesthetic |
|
|
403
|
-
| `box` | Grayscale | Filled block elements `▪◾◼■█` |
|
|
404
|
-
| `lines` | Grayscale | Dash/em-dash ramp — minimalist typographic look |
|
|
405
|
-
| `circles` | Accent | Concentric circle chars with accent highlight |
|
|
406
|
-
| `musical` | Accent | Music notation ♩♪♫♬♭♮♯ — playful, low density |
|
|
407
|
-
| `emoji` | Full color | Block emoji mosaic — best at larger `fontSize` |
|
|
408
|
-
|
|
409
|
-
### Background Options
|
|
410
|
-
|
|
411
|
-
| Option | Type | Default | Description |
|
|
412
|
-
|---|---|---|---|
|
|
413
|
-
| `type` | `string` | `'wave'` | Background renderer |
|
|
414
|
-
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | Theme; `'auto'` follows OS preference |
|
|
415
|
-
| `fontSize` | `number` | `13` | Character size |
|
|
416
|
-
| `speed` | `number` | `1` | Animation speed multiplier |
|
|
417
|
-
| `density` | `number` | `0.55` | Fraction of cells active (0–1) |
|
|
418
|
-
| `accentColor` | `string` | varies | Highlight / head colour |
|
|
203
|
+
---
|
|
419
204
|
|
|
420
205
|
## License
|
|
421
206
|
|
|
422
207
|
MIT © [asciify.org](https://asciify.org)
|
|
423
208
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
<p align="left">
|
|
427
|
-
<a href="https://www.buymeacoffee.com/asciify">☕ Buy me a coffee — if this saved you time, I'd appreciate it!</a>
|
|
428
|
-
</p>
|
|
209
|
+
> ☕ [Buy me a coffee](https://www.buymeacoffee.com/asciify) — if this saved you time, I'd appreciate it!
|
package/dist/index.cjs
CHANGED
|
@@ -112,6 +112,8 @@ var DEFAULT_OPTIONS = {
|
|
|
112
112
|
animationSpeed: 1,
|
|
113
113
|
dotSizeRatio: 0.8,
|
|
114
114
|
ditherStrength: 0,
|
|
115
|
+
charAspect: 0.55,
|
|
116
|
+
normalize: false,
|
|
115
117
|
hoverStrength: 0,
|
|
116
118
|
hoverRadius: 0.2,
|
|
117
119
|
hoverEffect: "spotlight",
|
|
@@ -606,7 +608,7 @@ function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
|
606
608
|
if (srcWidth === 0 || srcHeight === 0) {
|
|
607
609
|
return { frame: [], cols: 0, rows: 0 };
|
|
608
610
|
}
|
|
609
|
-
const charAspect =
|
|
611
|
+
const charAspect = options.charAspect;
|
|
610
612
|
const cellW = options.fontSize * options.charSpacing;
|
|
611
613
|
const cellH = options.fontSize / charAspect * options.charSpacing;
|
|
612
614
|
const renderW = targetWidth || srcWidth;
|
|
@@ -620,6 +622,18 @@ function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
|
620
622
|
ctx.drawImage(source, 0, 0, cols, rows);
|
|
621
623
|
const imageData = ctx.getImageData(0, 0, cols, rows);
|
|
622
624
|
const pixels = imageData.data;
|
|
625
|
+
let normMin = 0;
|
|
626
|
+
let normRange = 255;
|
|
627
|
+
if (options.normalize) {
|
|
628
|
+
let lo = 255, hi = 0;
|
|
629
|
+
for (let k = 0; k < pixels.length; k += 4) {
|
|
630
|
+
const l = 0.299 * pixels[k] + 0.587 * pixels[k + 1] + 0.114 * pixels[k + 2];
|
|
631
|
+
if (l < lo) lo = l;
|
|
632
|
+
if (l > hi) hi = l;
|
|
633
|
+
}
|
|
634
|
+
normMin = lo;
|
|
635
|
+
normRange = hi > lo ? hi - lo : 255;
|
|
636
|
+
}
|
|
623
637
|
const frame = [];
|
|
624
638
|
for (let y = 0; y < rows; y++) {
|
|
625
639
|
const row = [];
|
|
@@ -629,7 +643,8 @@ function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
|
629
643
|
const g = pixels[i + 1];
|
|
630
644
|
const b = pixels[i + 2];
|
|
631
645
|
const a = pixels[i + 3];
|
|
632
|
-
const
|
|
646
|
+
const rawLum = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
647
|
+
const lum = options.normalize ? (rawLum - normMin) / normRange * 255 : rawLum;
|
|
633
648
|
const adjustedLum = adjustLuminance(lum, options.brightness, options.contrast);
|
|
634
649
|
const ditheredLum = applyDither(adjustedLum, x, y, options.ditherStrength);
|
|
635
650
|
const char = options.customText ? customTextToChar(ditheredLum, options.customText, x, y, cols, options.invert) : luminanceToChar(ditheredLum, options.charset, options.invert);
|