Image to ASCII
ascii-things/image is a separate, tree-shakeable entry for turning images into
ASCII. It is not a living scene: there is no loop, no world, and no canvas
engine. You give it pixels; it gives you text or a drawn canvas.
It has no runtime dependencies and runs entirely in the browser. The docs also
include a full studio at /image/ with crop, tone, dither, edge,
colour, batch, TXT, PNG, SVG, and ZIP export.
Install
Section titled “Install”npm install ascii-thingsimport { imageToAscii, renderAsciiImage } from "ascii-things/image";Convert to text
Section titled “Convert to text”const text = await imageToAscii(file, { columns: 80, charset: "classic", dither: "floyd-steinberg",});
console.log(text);imageToAscii() accepts a File, Blob, HTMLImageElement, ImageBitmap,
HTMLCanvasElement, or a URL string. It resolves with the plain-text art.
Draw to a canvas
Section titled “Draw to a canvas”await renderAsciiImage(canvas, file, { columns: 80, color: true, background: "#070809", brightness: 0.04, gamma: 1.1,});renderAsciiImage() resolves with { text, columns, rows, colors } and paints
the characters into the supplied canvas. The canvas is sized from its rendered
width, with the backing store capped at 2× DPR.
A canvas source is useful when you want to crop or pre-process an image before conversion. The site studio passes a cropped canvas into the renderer.
import { AsciiImage } from "ascii-things/image/react";
export function Portrait({ file }: { file: File }) { return ( <AsciiImage src={file} columns={80} dither="bayer" brightness={0.05} gamma={1.1} alt="A portrait rendered in ASCII" /> );}AsciiImage renders a canvas with role="img" and the supplied alt. Use
onRender(result) to read the generated text and colours, or onError(error)
to handle a failed load.
Custom element
Section titled “Custom element”After npm install ascii-things, import the element entry in your bundler:
<script type="module"> import "ascii-things/image/element";</script>
<ascii-image src="/portrait.png" columns="80" charset="classic" dither="bayer" brightness="0.05" gamma="1.1" label="A portrait rendered in ASCII"></ascii-image>The element dispatches bubbling, composed ascii-ready and ascii-error
events. The ready event carries { result }; the error event carries { error }.
Options
Section titled “Options”| Option | Type | Default | |
|---|---|---|---|
columns |
number |
80 |
Characters across; clamped to 8–400 |
charset |
"classic" | "blocks" | "dots" | string |
"classic" |
A named ramp or any custom string |
invert |
boolean |
false |
Flip the luminance ramp |
dither |
"none" | "floyd-steinberg" | "bayer" |
"none" |
Error diffusion or ordered dithering |
contrast |
number |
1 |
Multiplier around mid-grey; clamped to 0.1–4 |
brightness |
number |
0 |
Luminance offset; clamped to -1–1 |
gamma |
number |
1 |
Gamma correction; clamped to 0.2–4 |
color |
boolean |
false |
Draw glyphs from the source pixels |
foreground |
string |
"#ece8df" |
Monochrome ink |
background |
string |
"#070809" |
Canvas background; "transparent" keeps alpha |
fontFamily |
string |
Geist Mono stack | Font used when drawing |
The named ramps are:
classic; // "@%#*+=-:. "blocks; // "█▓▒░ "dots; // "●◉◎○· "The first character in a ramp is used for black pixels and the last for white
pixels. For cream glyphs on a dark canvas, start the ramp with a space, as the
studio’s presets do. Set invert when your ramp runs the other way.
The browser studio
Section titled “The browser studio”The live page at /image/ adds controls around the library:
- Drag and drop, file picker, clipboard paste, and a built-in demo
- Crop and zoom before conversion
- Columns, ramps, contrast, brightness, gamma, invert, and dither
- Sobel edge mode, which forces a monochrome contour field
- Mono or source-coloured glyphs, ink/paper colours, and transparent output
- TXT, 2× PNG, and vector SVG exports
- Batch mode with a TXT, PNG, and SVG per image inside a ZIP
- Keyboard crop nudging, reduced-motion support, and light/dark UI
Uploaded files are read locally. They are never uploaded to the site or to the image proxy.
Loading images
Section titled “Loading images”Remote URLs are subject to CORS. If the server does not send an
Access-Control-Allow-Origin header, the browser cannot read the pixels. The
studio routes remote URLs through /api/image-proxy, a Cloudflare Pages
Function that fetches only raster images from public hosts. The proxy:
- accepts PNG, JPEG, WebP, GIF, and AVIF
- caps responses at 10 MB and uses a short timeout
- rejects localhost, private networks, credentials, non-standard ports, and redirects into private networks
- does not log image URLs at the application level
Remote SVG is intentionally rejected by the proxy. Local SVG files still work because they never leave the browser.
The studio is at /image/ and is the best place to try every option.
The original Vite-only example remains in the repository:
npm run devSource: examples/image.