asciify-engine 1.0.39 → 1.0.41
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 +168 -336
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,427 +4,259 @@
|
|
|
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
|
+
|
|
16
|
+
## Overview
|
|
15
17
|
|
|
16
|
-
-
|
|
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
|
|
18
|
+
asciify-engine works in two stages:
|
|
24
19
|
|
|
25
|
-
|
|
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
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
30
|
npm install asciify-engine
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Works with any modern bundler (Vite, webpack, esbuild, Rollup) and any framework — React, Vue, Svelte, Angular, Next.js, or vanilla JS.
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
---
|
|
34
36
|
|
|
35
|
-
|
|
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
|
-
```
|
|
37
|
+
## Converting Media to ASCII
|
|
58
38
|
|
|
59
|
-
|
|
39
|
+
### Images
|
|
60
40
|
|
|
61
|
-
|
|
41
|
+
`imageToAsciiFrame` accepts any `HTMLImageElement`, `HTMLVideoElement`, or `HTMLCanvasElement` and returns a single ASCII frame.
|
|
62
42
|
|
|
63
43
|
```ts
|
|
64
|
-
import {
|
|
44
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
65
45
|
|
|
66
|
-
const
|
|
46
|
+
const img = new Image();
|
|
47
|
+
img.crossOrigin = 'anonymous';
|
|
48
|
+
img.src = 'photo.jpg';
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
50
|
+
img.onload = () => {
|
|
51
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
52
|
+
const ctx = canvas.getContext('2d')!;
|
|
53
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
|
|
73
54
|
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
56
|
+
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height);
|
|
57
|
+
};
|
|
76
58
|
```
|
|
77
59
|
|
|
78
|
-
###
|
|
60
|
+
### Animated GIFs
|
|
61
|
+
|
|
62
|
+
`gifToAsciiFrames` parses a GIF `ArrayBuffer` and returns one `AsciiFrame` per GIF frame, preserving the original frame rate.
|
|
79
63
|
|
|
80
64
|
```ts
|
|
81
|
-
import {
|
|
65
|
+
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
const
|
|
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 };
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
|
|
71
|
+
const { frames, fps } = await gifToAsciiFrames(buffer, opts, canvas.width, canvas.height);
|
|
72
|
+
|
|
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);
|
|
88
78
|
```
|
|
89
79
|
|
|
90
80
|
### Video
|
|
91
81
|
|
|
92
|
-
|
|
93
|
-
import { asciifyVideo } from 'asciify-engine';
|
|
94
|
-
|
|
95
|
-
// Accepts a URL string or an existing HTMLVideoElement
|
|
96
|
-
const stop = await asciifyVideo('/my-video.mp4', canvas, { artStyle: 'terminal' });
|
|
82
|
+
`videoToAsciiFrames` extracts frames from an `HTMLVideoElement` at a given frame rate and returns the full frame sequence.
|
|
97
83
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### React
|
|
84
|
+
```ts
|
|
85
|
+
import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
103
86
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
const video = document.createElement('video');
|
|
88
|
+
video.src = '/clip.mp4';
|
|
89
|
+
video.muted = true;
|
|
90
|
+
await new Promise(r => (video.onloadeddata = r));
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
|
|
93
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}, [src]);
|
|
95
|
+
// videoToAsciiFrames(video, options, width, height, fps?, maxDurationSeconds?)
|
|
96
|
+
const { frames, fps } = await videoToAsciiFrames(video, opts, canvas.width, canvas.height, 12, 10);
|
|
114
97
|
|
|
115
|
-
|
|
116
|
-
|
|
98
|
+
let frameIndex = 0;
|
|
99
|
+
setInterval(() => {
|
|
100
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frames[frameIndex], opts, canvas.width, canvas.height);
|
|
101
|
+
frameIndex = (frameIndex + 1) % frames.length;
|
|
102
|
+
}, 1000 / fps);
|
|
117
103
|
```
|
|
118
104
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```tsx
|
|
122
|
-
import { useEffect, useRef } from 'react';
|
|
123
|
-
import { asciifyGif } from 'asciify-engine';
|
|
124
|
-
|
|
125
|
-
export function AsciiGif({ src }: { src: string }) {
|
|
126
|
-
const ref = useRef<HTMLCanvasElement>(null);
|
|
105
|
+
---
|
|
127
106
|
|
|
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]);
|
|
107
|
+
## Rendering Options
|
|
133
108
|
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
```
|
|
109
|
+
All conversion and render functions accept an `AsciiOptions` object. Spread `DEFAULT_OPTIONS` as a base and override what you need.
|
|
137
110
|
|
|
138
|
-
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `fontSize` | `number` | `10` | Character cell size in pixels. Smaller values increase density and detail. |
|
|
114
|
+
| `colorMode` | `'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent'` | `'grayscale'` | Determines how pixel color is mapped to character color. |
|
|
115
|
+
| `charset` | `string` | Standard ramp | Characters ordered from dense to sparse, representing brightness levels. |
|
|
116
|
+
| `brightness` | `number` | `0` | Brightness adjustment from `-1` (darker) to `1` (lighter). |
|
|
117
|
+
| `contrast` | `number` | `1` | Contrast multiplier applied before character mapping. |
|
|
118
|
+
| `invert` | `boolean` | `false` | Inverts the luminance mapping — light areas become dense, dark areas sparse. |
|
|
119
|
+
| `renderMode` | `'ascii' \| 'dots'` | `'ascii'` | Render as text characters or circular dot particles. |
|
|
120
|
+
| `hoverEffect` | `string` | `'none'` | Interactive effect driven by cursor position. See hover effects below. |
|
|
121
|
+
| `hoverStrength` | `number` | `0.8` | Effect intensity (0–1). |
|
|
122
|
+
| `hoverRadius` | `number` | `0.3` | Effect radius relative to canvas size (0–1). |
|
|
123
|
+
|
|
124
|
+
### Color Modes
|
|
125
|
+
|
|
126
|
+
| Mode | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `grayscale` | Classic monochrome ASCII. Character brightness maps to source luminance. |
|
|
129
|
+
| `fullcolor` | Each character inherits the original pixel color from the source. |
|
|
130
|
+
| `matrix` | Monochrome green — inspired by classic terminal aesthetics. |
|
|
131
|
+
| `accent` | Single accent color applied uniformly across all characters. |
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
<template>
|
|
142
|
-
<canvas ref="canvasRef" width="800" height="600" />
|
|
143
|
-
</template>
|
|
133
|
+
### Hover Effects
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
import { useTemplateRef, onMounted } from 'vue';
|
|
147
|
-
import { asciify } from 'asciify-engine';
|
|
135
|
+
Interactive effects that respond to cursor movement. Pass the effect name to `hoverEffect` and supply the cursor position to `renderFrameToCanvas` at render time.
|
|
148
136
|
|
|
149
|
-
|
|
150
|
-
const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');
|
|
137
|
+
Available effects: `spotlight` · `flashlight` · `magnifier` · `force-field` · `neon` · `fire` · `ice` · `gravity` · `shatter` · `ghost`
|
|
151
138
|
|
|
152
|
-
|
|
153
|
-
|
|
139
|
+
```ts
|
|
140
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
141
|
+
const rect = canvas.getBoundingClientRect();
|
|
142
|
+
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height, Date.now() / 1000, {
|
|
143
|
+
x: e.clientX - rect.left,
|
|
144
|
+
y: e.clientY - rect.top,
|
|
145
|
+
});
|
|
146
|
+
});
|
|
154
147
|
```
|
|
155
148
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import { Component, ElementRef, ViewChild, AfterViewInit, Input } from '@angular/core';
|
|
160
|
-
import { asciify } from 'asciify-engine';
|
|
161
|
-
|
|
162
|
-
@Component({
|
|
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';
|
|
170
|
-
|
|
171
|
-
ngAfterViewInit() {
|
|
172
|
-
asciify(this.src, this.canvasRef.nativeElement, { artStyle: this.artStyle as any });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
149
|
+
---
|
|
176
150
|
|
|
177
151
|
## Animated Backgrounds
|
|
178
152
|
|
|
179
|
-
|
|
153
|
+
`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.
|
|
180
154
|
|
|
181
155
|
```ts
|
|
182
156
|
import { asciiBackground } from 'asciify-engine';
|
|
183
157
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
asciiBackground('#hero', { type: 'wave', colorScheme: 'light' });
|
|
158
|
+
const stop = asciiBackground('#hero', {
|
|
159
|
+
type: 'rain',
|
|
160
|
+
colorScheme: 'auto', // follows OS dark/light mode
|
|
161
|
+
speed: 1.0,
|
|
162
|
+
density: 0.55,
|
|
163
|
+
accentColor: '#d4ff00',
|
|
164
|
+
});
|
|
192
165
|
|
|
193
|
-
// Stop
|
|
194
|
-
|
|
195
|
-
destroy();
|
|
166
|
+
// Stop and clean up when no longer needed
|
|
167
|
+
stop();
|
|
196
168
|
```
|
|
197
169
|
|
|
198
|
-
### Background Types
|
|
170
|
+
### Available Background Types
|
|
199
171
|
|
|
200
172
|
| Type | Description |
|
|
201
173
|
|---|---|
|
|
202
|
-
| `wave` | Flowing sine-wave field with noise turbulence |
|
|
203
|
-
| `rain` |
|
|
174
|
+
| `wave` | Flowing sine-wave field with layered noise turbulence |
|
|
175
|
+
| `rain` | Vertical column rain with a glowing leading character and fading trail |
|
|
204
176
|
| `stars` | Parallax star field that reacts to cursor position |
|
|
205
|
-
| `pulse` | Concentric ripple bursts
|
|
206
|
-
| `noise` | Smooth value-noise field with organic
|
|
207
|
-
| `grid` | Geometric grid that warps and
|
|
208
|
-
| `aurora` | Sweeping borealis-style
|
|
209
|
-
| `silk` |
|
|
210
|
-
| `void` | Gravitational singularity — characters spiral inward toward cursor |
|
|
211
|
-
| `morph` | Characters morph between shapes driven by noise |
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
| `terrain` | Procedural heightmap landscape with parallax depth layers |
|
|
215
|
-
| `circuit` | PCB trace network that pulses with traveling electric signals |
|
|
216
|
-
|
|
217
|
-
### `asciiBackground` Options
|
|
177
|
+
| `pulse` | Concentric ripple bursts emanating from the cursor |
|
|
178
|
+
| `noise` | Smooth value-noise field with organic, fluid motion |
|
|
179
|
+
| `grid` | Geometric grid that warps and brightens near the cursor |
|
|
180
|
+
| `aurora` | Sweeping borealis-style color bands drifting across the field |
|
|
181
|
+
| `silk` | Fluid swirl simulation following cursor movement |
|
|
182
|
+
| `void` | Gravitational singularity — characters spiral inward toward the cursor |
|
|
183
|
+
| `morph` | Characters morph between shapes over time, driven by noise |
|
|
184
|
+
|
|
185
|
+
### Background Options
|
|
218
186
|
|
|
219
187
|
| Option | Type | Default | Description |
|
|
220
188
|
|---|---|---|---|
|
|
221
|
-
| `type` | `string` | `'wave'` | Which background renderer to use |
|
|
222
|
-
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | `'auto'`
|
|
223
|
-
| `fontSize` | `number` | `13` | Character size in
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `
|
|
228
|
-
| `lightMode` | `boolean` | `false` | Dark characters on light background |
|
|
189
|
+
| `type` | `string` | `'wave'` | Which background renderer to use. |
|
|
190
|
+
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | `'auto'` reacts to OS theme changes in real time. |
|
|
191
|
+
| `fontSize` | `number` | `13` | Character size in pixels. |
|
|
192
|
+
| `speed` | `number` | `1` | Global animation speed multiplier. |
|
|
193
|
+
| `density` | `number` | `0.55` | Fraction of grid cells that are active (0–1). |
|
|
194
|
+
| `accentColor` | `string` | varies | Highlight or leading-character color (any CSS color string). |
|
|
195
|
+
| `color` | `string` | — | Override the body character color. |
|
|
229
196
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
### Low-level background renderers
|
|
233
|
-
|
|
234
|
-
All renderers are also exported individually for direct canvas use:
|
|
197
|
+
---
|
|
235
198
|
|
|
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
|
-
```
|
|
199
|
+
## React Integration
|
|
255
200
|
|
|
256
|
-
|
|
201
|
+
```tsx
|
|
202
|
+
import { useEffect, useRef } from 'react';
|
|
203
|
+
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
257
204
|
|
|
258
|
-
|
|
205
|
+
export function AsciiImage({ src }: { src: string }) {
|
|
206
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
259
207
|
|
|
260
|
-
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
const canvas = canvasRef.current;
|
|
210
|
+
if (!canvas) return;
|
|
211
|
+
|
|
212
|
+
const img = new Image();
|
|
213
|
+
img.crossOrigin = 'anonymous';
|
|
214
|
+
img.src = src;
|
|
215
|
+
img.onload = () => {
|
|
216
|
+
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
|
|
217
|
+
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
|
|
218
|
+
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
|
|
219
|
+
};
|
|
220
|
+
}, [src]);
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
222
|
+
return <canvas ref={canvasRef} width={800} height={600} />;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
264
225
|
|
|
265
|
-
|
|
266
|
-
const options = { ...DEFAULT_OPTIONS, fontSize: 8 };
|
|
267
|
-
const { frames, fps } = await gifToAsciiFrames(buffer, options, canvas.width, canvas.height);
|
|
268
|
-
|
|
269
|
-
const ctx = canvas.getContext('2d')!;
|
|
270
|
-
let i = 0;
|
|
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);
|
|
226
|
+
---
|
|
284
227
|
|
|
285
|
-
|
|
286
|
-
```
|
|
228
|
+
## Embed Generation
|
|
287
229
|
|
|
288
|
-
|
|
230
|
+
Generate self-contained HTML that can be hosted anywhere or dropped directly into a page — no runtime dependency required.
|
|
289
231
|
|
|
290
232
|
```ts
|
|
291
|
-
import {
|
|
233
|
+
import { generateEmbedCode, generateAnimatedEmbedCode } from 'asciify-engine';
|
|
292
234
|
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
}
|
|
235
|
+
// Static — produces a single-file HTML with the ASCII art baked in
|
|
236
|
+
const staticHtml = generateEmbedCode(frame, options);
|
|
300
237
|
|
|
301
|
-
|
|
302
|
-
const
|
|
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);
|
|
319
|
-
|
|
320
|
-
// Clean up → cancelAnimationFrame(animId);
|
|
238
|
+
// Animated — produces a self-running HTML animation
|
|
239
|
+
const animatedHtml = generateAnimatedEmbedCode(frames, options, fps);
|
|
321
240
|
```
|
|
322
241
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
```html
|
|
326
|
-
<canvas id="ascii" width="800" height="600"></canvas>
|
|
327
|
-
<script type="module">
|
|
328
|
-
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
|
|
329
|
-
|
|
330
|
-
const canvas = document.getElementById('ascii');
|
|
331
|
-
const ctx = canvas.getContext('2d');
|
|
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
|
-
```
|
|
242
|
+
---
|
|
343
243
|
|
|
344
244
|
## API Reference
|
|
345
245
|
|
|
346
|
-
|
|
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
|
-
| `charAspect` | `number` | `0.55` | Width ÷ height of a single output character. Set to `0.5` for terminal emulators, `0.52` for browser `line-height: 1.15`, `0.55` for `line-height: 1.09`. Ensuring this matches your rendering environment keeps the output proportional to the source. |
|
|
391
|
-
| `normalize` | `boolean` | `false` | Auto-stretch the luminance range of the frame before charset mapping. Maximises detail and contrast for low-contrast or muted images. |
|
|
392
|
-
|
|
393
|
-
### Art Styles (`artStyle`)
|
|
394
|
-
|
|
395
|
-
| Value | Color mode | Description |
|
|
246
|
+
| Function | Signature | Returns |
|
|
396
247
|
|---|---|---|
|
|
397
|
-
| `
|
|
398
|
-
| `
|
|
399
|
-
| `
|
|
400
|
-
| `
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
404
|
-
| `katakana` | Matrix | Half-width katakana — anime / cyberpunk aesthetic |
|
|
405
|
-
| `box` | Grayscale | Filled block elements `▪◾◼■█` |
|
|
406
|
-
| `lines` | Grayscale | Dash/em-dash ramp — minimalist typographic look |
|
|
407
|
-
| `circles` | Accent | Concentric circle chars with accent highlight |
|
|
408
|
-
| `musical` | Accent | Music notation ♩♪♫♬♭♮♯ — playful, low density |
|
|
409
|
-
| `emoji` | Full color | Block emoji mosaic — best at larger `fontSize` |
|
|
248
|
+
| `imageToAsciiFrame` | `(source, options, w?, h?)` | `{ frame, cols, rows }` |
|
|
249
|
+
| `renderFrameToCanvas` | `(ctx, frame, options, w, h, time?, hoverPos?)` | `void` |
|
|
250
|
+
| `gifToAsciiFrames` | `(buffer, options, w, h, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
251
|
+
| `videoToAsciiFrames` | `(video, options, w, h, fps?, maxSec?, onProgress?)` | `Promise<{ frames, cols, rows, fps }>` |
|
|
252
|
+
| `asciiBackground` | `(selector, options)` | `() => void` (cleanup) |
|
|
253
|
+
| `generateEmbedCode` | `(frame, options)` | `string` |
|
|
254
|
+
| `generateAnimatedEmbedCode` | `(frames, options, fps)` | `string` |
|
|
410
255
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
| Option | Type | Default | Description |
|
|
414
|
-
|---|---|---|---|
|
|
415
|
-
| `type` | `string` | `'wave'` | Background renderer |
|
|
416
|
-
| `colorScheme` | `'auto' \| 'light' \| 'dark'` | `'dark'` | Theme; `'auto'` follows OS preference |
|
|
417
|
-
| `fontSize` | `number` | `13` | Character size |
|
|
418
|
-
| `speed` | `number` | `1` | Animation speed multiplier |
|
|
419
|
-
| `density` | `number` | `0.55` | Fraction of cells active (0–1) |
|
|
420
|
-
| `accentColor` | `string` | varies | Highlight / head colour |
|
|
256
|
+
---
|
|
421
257
|
|
|
422
258
|
## License
|
|
423
259
|
|
|
424
260
|
MIT © [asciify.org](https://asciify.org)
|
|
425
261
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<p align="left">
|
|
429
|
-
<a href="https://www.buymeacoffee.com/asciify">☕ Buy me a coffee — if this saved you time, I'd appreciate it!</a>
|
|
430
|
-
</p>
|
|
262
|
+
> ☕ [Buy me a coffee](https://www.buymeacoffee.com/asciify) — if this saved you time, I'd appreciate it!
|
package/package.json
CHANGED