Skip to content

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.

Terminal window
npm install ascii-things
import { imageToAscii, renderAsciiImage } from "ascii-things/image";
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.

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.

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 }.

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 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.

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:

5173/examples/image/
npm run dev

Source: examples/image.