asciify-engine
v1.0.80
Published
Framework-agnostic ASCII art engine. Convert images, videos, and GIFs into ASCII art rendered on canvas.
Maintainers
Keywords
Readme
asciify-engine
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.
Overview
asciify-engine works in two stages:
- 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. - 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.
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.
Installation
npm install asciify-engineWorks with any modern bundler (Vite, webpack, esbuild, Rollup) and any framework — React, Vue, Svelte, Angular, Next.js, or vanilla JS.
Converting Media to ASCII
Images
imageToAsciiFrame accepts any HTMLImageElement, HTMLVideoElement, or HTMLCanvasElement and returns a single ASCII frame.
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'photo.jpg';
img.onload = () => {
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height);
};Animated GIFs
gifToAsciiFrames parses a GIF ArrayBuffer and returns one AsciiFrame per GIF frame, preserving the original frame rate.
import { gifToAsciiFrames, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
const buffer = await fetch('animation.gif').then(r => r.arrayBuffer());
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
const opts = { ...DEFAULT_OPTIONS, fontSize: 8 };
const { frames, fps } = await gifToAsciiFrames(buffer, opts, canvas.width, canvas.height);
let frameIndex = 0;
setInterval(() => {
renderFrameToCanvas(canvas.getContext('2d')!, frames[frameIndex], opts, canvas.width, canvas.height);
frameIndex = (frameIndex + 1) % frames.length;
}, 1000 / fps);Video
asciifyVideo streams video as live ASCII art in real time. Instant start, constant memory, unlimited duration.
⚠️ Never set the backing
<video>element todisplay: none— browsers skip GPU frame decoding. When given a URL string,asciifyVideohandles this automatically.
import { asciifyVideo } from 'asciify-engine';
const canvas = document.getElementById('ascii') as HTMLCanvasElement;
// Minimal
const stop = await asciifyVideo('/clip.mp4', canvas);
// Fit canvas to a container and re-size automatically on resize:
const stop = await asciifyVideo('/clip.mp4', canvas, {
fitTo: '#hero', // or an HTMLElement
});
// Lifecycle hooks — ready state, timers, etc.:
const stop = await asciifyVideo('/clip.mp4', canvas, {
fitTo: '#hero',
fontSize: 6,
onReady: () => setLoading(false),
onFrame: () => setElapsed(t => t + 1),
});
// Pre-extract all frames before playback (frame-perfect loops, short clips):
const stop = await asciifyVideo('/clip.mp4', canvas, { preExtract: true });
// Clean up:
stop();Rendering Options
All conversion and render functions accept an AsciiOptions object. Spread DEFAULT_OPTIONS as a base and override what you need.
| Option | Type | Default | Description |
|---|---|---|---|
| fontSize | number | 10 | Character cell size in pixels. Smaller values increase density and detail. |
| colorMode | 'grayscale' \| 'fullcolor' \| 'matrix' \| 'accent' | 'grayscale' | Determines how pixel color is mapped to character color. |
| charset | string | Standard ramp | Characters ordered from dense to sparse, representing brightness levels. |
| brightness | number | 0 | Brightness adjustment from -1 (darker) to 1 (lighter). |
| contrast | number | 1 | Contrast multiplier applied before character mapping. |
| invert | boolean \| 'auto' | false | Inverts the luminance mapping — light areas become dense, dark areas sparse. Set to 'auto' to auto-detect from OS color scheme (light mode → invert, dark mode → normal). |
| renderMode | 'ascii' \| 'dots' | 'ascii' | Render as text characters or circular dot particles. |
| hoverEffect | string | 'none' | Interactive effect driven by cursor position. See hover effects below. |
| hoverStrength | number | 0 | Effect intensity (0–1). 0 = hover disabled. |
| hoverRadius | number | 0.2 | Effect radius relative to canvas size (0–1). |
| chromaKey | true \| 'blue-screen' \| {r,g,b} \| string \| null | null | Remove a background colour. true = heuristic green screen (any shade). 'blue-screen' = heuristic blue screen. Custom: {r,g,b} or any CSS hex string keyed by Euclidean distance. null to disable. |
| chromaKeyTolerance | number | 60 | Euclidean RGB distance threshold for chroma-key detection. 0 = exact match, higher = more pixels removed (max useful ~100). |
Chroma Key (Green/Blue Screen)
Remove a solid background colour from any source — images, GIFs, or video — so the canvas background shows through keyed pixels.
import { asciify, DEFAULT_OPTIONS } from 'asciify-engine';
// Green screen — zero config, just set true:
asciify(img, canvas, {
options: { ...DEFAULT_OPTIONS, chromaKey: true, colorMode: 'fullcolor' },
});
// Blue screen
asciify(img, canvas, {
options: { ...DEFAULT_OPTIONS, chromaKey: 'blue-screen', chromaKeyTolerance: 70 },
});
// Custom RGB key
asciify(img, canvas, {
options: { ...DEFAULT_OPTIONS, chromaKey: { r: 0, g: 180, b: 90 }, chromaKeyTolerance: 50 },
});
// Live video with green screen
asciifyVideo('/footage.mp4', canvas, {
fitTo: '#container',
options: { ...DEFAULT_OPTIONS, chromaKey: true, colorMode: 'fullcolor' },
});Tolerance guide:
40–60— tight key, natural green screen under good lighting60–80— broader key, wrinkled fabric or uneven lighting80–120— aggressive; expect some spill into the subject
Color Modes
| Mode | Description |
|---|---|
| grayscale | Classic monochrome ASCII. Character brightness maps to source luminance. |
| fullcolor | Each character inherits the original pixel color from the source. |
| matrix | Monochrome green — inspired by classic terminal aesthetics. |
| accent | Single accent color applied uniformly across all characters. |
Hover Effects
Interactive effects that respond to cursor movement. Pass the effect name to hoverEffect and supply the cursor position to renderFrameToCanvas at render time.
Available effects: spotlight · flashlight · magnifier · force-field · neon · fire · ice · gravity · shatter · ghost
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
renderFrameToCanvas(ctx, frame, opts, canvas.width, canvas.height, Date.now() / 1000, {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
});Animated Backgrounds
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.
import { asciiBackground } from 'asciify-engine';
const stop = asciiBackground('#hero', {
type: 'rain',
colorScheme: 'auto', // follows OS dark/light mode
speed: 1.0,
density: 0.55,
accentColor: '#d4ff00',
});
// Stop and clean up when no longer needed
stop();Available Background Types
| Type | Description |
|---|---|
| wave | Flowing sine-wave field with layered noise turbulence |
| rain | Vertical column rain with a glowing leading character and fading trail |
| stars | Parallax star field that reacts to cursor position |
| pulse | Concentric ripple bursts emanating from the cursor |
| noise | Smooth value-noise field with organic, fluid motion |
| grid | Geometric grid that warps and brightens near the cursor |
| aurora | Sweeping borealis-style color bands drifting across the field |
| silk | Fluid swirl simulation following cursor movement |
| void | Gravitational singularity — characters spiral inward toward the cursor |
| morph | Characters morph between shapes over time, driven by noise |
Background Options
| Option | Type | Default | Description |
|---|---|---|---|
| type | string | 'wave' | Which background renderer to use. |
| colorScheme | 'auto' \| 'light' \| 'dark' | 'dark' | 'auto' reacts to OS theme changes in real time. |
| fontSize | number | 13 | Character size in pixels. |
| speed | number | 1 | Global animation speed multiplier. |
| density | number | 0.55 | Fraction of grid cells that are active (0–1). |
| accentColor | string \| 'auto' | varies | Highlight or leading-character color (any CSS hex string). Set to 'auto' to auto-detect: probes --accent-color, --color-accent, --accent, --primary CSS variables on :root, then falls back to OS color scheme (dark ink in light mode, light ink in dark mode). |
| color | string | — | Override the body character color. |
React Integration
import { useEffect, useRef } from 'react';
import { imageToAsciiFrame, renderFrameToCanvas, DEFAULT_OPTIONS } from 'asciify-engine';
export function AsciiImage({ src }: { src: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = src;
img.onload = () => {
const opts = { ...DEFAULT_OPTIONS, fontSize: 10, colorMode: 'fullcolor' as const };
const { frame } = imageToAsciiFrame(img, opts, canvas.width, canvas.height);
renderFrameToCanvas(canvas.getContext('2d')!, frame, opts, canvas.width, canvas.height);
};
}, [src]);
return <canvas ref={canvasRef} width={800} height={600} />;
}Embed Generation
Generate self-contained HTML that can be hosted anywhere or dropped directly into a page — no runtime dependency required.
import { generateEmbedCode, generateAnimatedEmbedCode } from 'asciify-engine';
// Static — produces a single-file HTML with the ASCII art baked in
const staticHtml = generateEmbedCode(frame, options);
// Animated — produces a self-running HTML animation
const animatedHtml = generateAnimatedEmbedCode(frames, options, fps);API Reference
| Function | Signature | Returns |
|---|---|---|
| asciify | (source, canvas, options?) | Promise<void> |
| asciifyVideo | (source, canvas, options?) | Promise<() => void> |
| asciifyGif | (source, canvas, options?) | Promise<() => void> |
| asciifyWebcam | (canvas, options?) | Promise<() => void> |
| asciiBackground | (selector, options) | () => void |
| imageToAsciiFrame | (source, options, w?, h?) | { frame, cols, rows } |
| renderFrameToCanvas | (ctx, frame, options, w, h, time?, hoverPos?) | void |
| gifToAsciiFrames | (buffer, options, w, h, onProgress?) | Promise<{ frames, cols, rows, fps }> |
| videoToAsciiFrames | (video, options, w, h, fps?, maxSec?, onProgress?) | Promise<{ frames, cols, rows, fps }> |
| generateEmbedCode | (frame, options) | string |
| generateAnimatedEmbedCode | (frames, options, fps) | string |
asciifyVideo options: fitTo (HTMLElement/selector — fits canvas to container + ResizeObserver), preExtract (pre-decode all frames, default false), trim: { start?: number; end?: number } (loop a time slice in seconds, accepts floats), onReady(video), onFrame()
License
MIT © asciify.org
☕ Buy me a coffee — if this saved you time, I'd appreciate it!
