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.
Files changed (2) hide show
  1. package/README.md +168 -336
  2. 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-support-d4ff00?labelColor=0a0a0a&style=flat-square" alt="Buy Me A Coffee" /></a>
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
- > 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.
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
- **[ Live Playground](https://asciify.org) · [npm](https://www.npmjs.com/package/asciify-engine) · [Support the project ☕](https://www.buymeacoffee.com/asciify)**
12
+ **[&#9654; Live Playground](https://asciify.org) &middot; [npm](https://www.npmjs.com/package/asciify-engine)**
13
13
 
14
- ## Features
14
+ ---
15
+
16
+ ## Overview
15
17
 
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
18
+ asciify-engine works in two stages:
24
19
 
25
- ## Install
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
- ## Quick Start
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
- ### Image → ASCII
35
+ ---
34
36
 
35
- ```ts
36
- import { asciify } from 'asciify-engine';
37
-
38
- const canvas = document.querySelector('canvas')!;
39
-
40
- // Minimal — just a URL and a canvas, no img.onload boilerplate needed
41
- await asciify('https://example.com/photo.jpg', canvas);
42
-
43
- // With an art style preset
44
- await asciify('photo.jpg', canvas, { artStyle: 'letters' });
45
-
46
- // Full control
47
- await asciify('photo.jpg', canvas, {
48
- fontSize: 8,
49
- artStyle: 'art',
50
- options: {
51
- colorMode: 'fullcolor',
52
- invert: true,
53
- hoverEffect: 'spotlight',
54
- hoverStrength: 0.5,
55
- },
56
- });
57
- ```
37
+ ## Converting Media to ASCII
58
38
 
59
- > **Canvas sizing:** Set `width` and `height` as HTML attributes on the `<canvas>` element — these control the pixel grid. CSS sizing alone (`style="width:100%"`) won't work (the canvas will be 0×0). Use `canvas.width = el.clientWidth` to fill a container.
39
+ ### Images
60
40
 
61
- ### Webcam ASCII
41
+ `imageToAsciiFrame` accepts any `HTMLImageElement`, `HTMLVideoElement`, or `HTMLCanvasElement` and returns a single ASCII frame.
62
42
 
63
43
  ```ts
64
- import { asciifyWebcam } from 'asciify-engine';
44
+ import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
65
45
 
66
- const canvas = document.querySelector('canvas')!;
46
+ const img = new Image();
47
+ img.crossOrigin = 'anonymous';
48
+ img.src = 'photo.jpg';
67
49
 
68
- // Requests camera access, starts a live rAF loop, returns stop()
69
- const stop = await asciifyWebcam(canvas, {
70
- artStyle: 'terminal',
71
- mirror: true, // horizontal flip (selfie mode)
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
- // Release camera + cancel loop
75
- stop();
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
- ### GIF Animation
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 { asciifyGif } from 'asciify-engine';
65
+ import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
82
66
 
83
- // Fetches, converts, and starts the animation loop — returns a stop() function
84
- const stop = await asciifyGif('animation.gif', canvas);
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
- // Clean up the animation loop when done
87
- stop();
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
- ```ts
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
- // Cancel when unmounting / navigating away
99
- stop();
100
- ```
101
-
102
- ### React
84
+ ```ts
85
+ import { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
103
86
 
104
- ```tsx
105
- import { useEffect, useRef } from 'react';
106
- import { asciify } from 'asciify-engine';
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
- export function AsciiImage({ src }: { src: string }) {
109
- const ref = useRef<HTMLCanvasElement>(null);
92
+ const canvas = document.getElementById('ascii') as HTMLCanvasElement;
93
+ const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
110
94
 
111
- useEffect(() => {
112
- if (ref.current) asciify(src, ref.current, { artStyle: 'art' });
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
- return <canvas ref={ref} width={800} height={600} />;
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
- ### Animated GIF or video in React
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
- useEffect(() => {
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
- return <canvas ref={ref} width={800} height={600} />;
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
- ### Vue
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
- ```vue
141
- <template>
142
- <canvas ref="canvasRef" width="800" height="600" />
143
- </template>
133
+ ### Hover Effects
144
134
 
145
- <script setup lang="ts">
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
- const props = defineProps<{ src: string; artStyle?: string }>();
150
- const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef');
137
+ Available effects: `spotlight` · `flashlight` · `magnifier` · `force-field` · `neon` · `fire` · `ice` · `gravity` · `shatter` · `ghost`
151
138
 
152
- onMounted(() => asciify(props.src, canvasRef.value!, { artStyle: props.artStyle as any }));
153
- </script>
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
- ### Angular
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
- Drop a live ASCII animation into any element with one call.
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
- // Attach to any selector — animates the element's background
185
- asciiBackground('#hero', { type: 'rain' });
186
-
187
- // Follows OS dark/light mode automatically
188
- asciiBackground('#hero', { type: 'aurora', colorScheme: 'auto' });
189
-
190
- // Force light mode (dark characters on light background)
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 / clean up
194
- const { destroy } = asciiBackground('#hero', { type: 'stars' });
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` | Matrix-style vertical column rain with glowing head and fading tail |
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 that emanate from the cursor |
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
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'` 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 |
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
- 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:
197
+ ---
235
198
 
236
- ```ts
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
- ## Low-level API
201
+ ```tsx
202
+ import { useEffect, useRef } from 'react';
203
+ import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
257
204
 
258
- For cases where the one-call API is too limiting — direct frame manipulation, custom render loops, progress callbacks, etc.
205
+ export function AsciiImage({ src }: { src: string }) {
206
+ const canvasRef = useRef<HTMLCanvasElement>(null);
259
207
 
260
- ### GIF (low-level)
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
- ```ts
263
- import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
222
+ return <canvas ref={canvasRef} width={800} height={600} />;
223
+ }
224
+ ```
264
225
 
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);
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
- // Clean up → cancelAnimationFrame(animId);
286
- ```
228
+ ## Embed Generation
287
229
 
288
- ### Video (low-level)
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 { videoToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
233
+ import { generateEmbedCode, generateAnimatedEmbedCode } from 'asciify-engine';
292
234
 
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
- }
235
+ // Static produces a single-file HTML with the ASCII art baked in
236
+ const staticHtml = generateEmbedCode(frame, options);
300
237
 
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);
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
- ### Vanilla JS (low-level image)
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
- ### 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
- | `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
- | `classic` | Grayscale | Standard density ramp clean, universally readable |
398
- | `art` | Full color | 70-char dense ramp for maximum tonal detail |
399
- | `particles` | Full color | Dot circles (`renderMode: 'dots'`) great for photos |
400
- | `letters` | Full color | Alphabet characters with pixel-accurate color |
401
- | `terminal` | Matrix | Classic charset with green phosphor / Matrix look |
402
- | `claudeCode` | Accent | Box-drawing chars with accent color — technical/hacker aesthetic |
403
- | `braille` | Full color | 256-char braille block — ultra-dense, printed feel |
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
- ### Background Options
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asciify-engine",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "Framework-agnostic ASCII art engine. Convert images, videos, and GIFs into ASCII art rendered on canvas.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",