Engine
lib/ascii is four small files. <AsciiCanvas> is the React primitive; everything in the catalogue is a wrapper around it.
Pipeline
input ──▶ sample ──▶ shape ──▶ glyphs ──▶ paint
source drawImage contrast ramp or glyph atlas
field f(x,y,t) gamma braille, drawImage
edges quadrants
frame you write dither per cell
paletteWrite your own field
A field returns ink from 0 to 1 for every cell. x and y run 0 to 1, t is seconds, and s carries the grid size, aspect ratio and pointer.
<AsciiCanvas
aria-hidden
opacity
className="text-primary"
field={(x, y, t, s) => {
const dx = (x - 0.5 - 0.25 * Math.cos(t)) * s.aspect
const dy = y - 0.5 - 0.25 * Math.sin(t)
return Math.max(0, 1 - Math.hypot(dx, dy) * 4)
}}
/>Frames
When cells depend on each other (3D with a z-buffer, particles, cellular automata) pass frame instead. It gets the whole Frame: write f.tone, and f.glyph char codes to force specific characters. The donut and matrix rain are built this way.
<AsciiCanvas
frame={(f, t) => {
for (let i = 0; i < f.tone.length; i++) f.tone[i] = Math.random() < 0.02 ? 1 : 0
}}
/>Export
const ascii = useRef<AsciiHandle>(null)
<AsciiCanvas ref={ascii} source={img} />
ascii.current.text() // plain text, one line per row
ascii.current.svg() // scalable <svg> with your theme colour
await ascii.current.png() // Blob at device resolutionCharsets
| standard | .:-=+*#%@ |
| detailed | .'`^",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczX… |
| mixed | .·:-=+*oxO%#&░▒▓█ |
| blocks | ░▒▓█ |
| dots | ·•● |
| minimal | .oO@ |
| hatch | ·-+=# |
| cross | ·+x×X╳ |
| diagonal | ·/╱X╳ |
| diamond | ·⋄◇◈◆ |
| lines | .╷│┃▐█ |
| donut | .,-~:;=!*#$@ |
| katakana | ヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ |
| binary | 01 |
| braille | ⠀⠁⠃⠇⡇⡏⡟⡿⣿ (2×4 dots per cell) |
| quadrants | ▘▀▛█▖▌▞▟▄ (2×2 blocks per cell) |
AsciiCanvas props
Plus exactly one input: source, field or frame. Also fit, mirror, ink, lineHeight and onRender.
| Prop | Type | Default | Description |
|---|---|---|---|
| charset | CharsetName | string | "standard" | Named ramp or your own string, empty to dense. "braille" packs 2×4 dots per cell, "quadrants" 2×2 blocks. |
| fontSize | number | 12 | Cell size in CSS pixels. Smaller means more detail. |
| dither | "none" | "bayer" | "floyd" | "none" | Quantisation. Floyd–Steinberg for photos, Bayer for a retro look. |
| color | "ink" | "source" | string[] | "ink" | Ink uses the CSS colour (theme with text-*). Source keeps pixel colours. An array maps tone onto gradient stops, e.g. palettes.thermal. |
| edges | number | 0 | Outline contours with | / - \ where the tone gradient beats this strength. Try 0.3. Ramps only. |
| opacity | boolean | false | Fade each glyph by its tone. |
| brightness | number | 0 | Added after contrast, -1 to 1. |
| contrast | number | 1 | Multiplier around mid grey. |
| gamma | number | 1 | Tone curve exponent. |
| invert | boolean | false | Swap ink and blank. |
| animate | boolean | auto | On for fields, frames and video. Off under reduced motion, except video. |
| fps | number | 30 | Frame cap. |
| ref | Ref<AsciiHandle> | — | Exposes text(), svg(), png() and the canvas. |
Performance
- Glyphs are drawn from a pre-rendered atlas; source-colour mode falls back to fillText.
- Animation runs at 30 fps by default and stops while offscreen or in a hidden tab.
prefers-reduced-motionfreezes fields and frames on their current frame. Video keeps playing.