For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. The asciicn agent skill, for building any ASCII effect on the engine, is at /.well-known/agent-skills/asciicn/SKILL.md.

ASCII portrait

A face redrawn in fine Braille glyphs, graded for skin and shadow, with contour strokes, a develop reveal and a before/after split with the photo.

Controls
Photo
Charset
Colour
Cell8px
Detail0.5
Contour strokes
Split

Installation

pnpm dlx shadcn@latest add https://asciicn.dev/r/ascii-portrait.json

Usage

import { AsciiPortrait } from "@/components/asciicn/ascii-portrait";
 
export function TeamCard() {
  return (
    <AsciiPortrait
      src="/team/ada.jpg"
      alt="Ada, our head of research, smiling at the camera"
      className="aspect-[4/5] w-72"
    />
  );
}

AsciiPortrait is AsciiImage set up for faces. It samples the photo the same way, then shapes the tones before glyphs are chosen:

  1. A grade of contrast: 1.2 and gamma: 0.9 separates skin from shadow without crushing either.
  2. detail sharpens each sample against the cell around it, so eyes, lips and the line of the nose survive the cell grid.
  3. With edges on, a Sobel filter finds the strongest contours and thins them to one sample wide. In Braille and quadrants they light dots on top of the tones. In a ramp charset such as standard they become - / | \ strokes.

Braille is the default because it draws 2×4 dots per cell. That is eight times the detail of a ramp at the same cell size, which matters most on a face.

On mount the portrait develops: the contours come in first, like a sketch, and the tones rise through them over developDuration. Hover the portrait to see the photo left of the pointer. With compare="drag" the split stays where it is dropped, which suits touch screens.

Examples

Colour, with a split you drag

colorMode="truecolor" takes each cell's colour from the photo. compare="drag" starts the split at the middle.

Strokes and phosphor

On the left, the classic ramp with contour strokes drawn as - / | \ glyphs. On the right, Braille on the P3 amber phosphor.

Props

PropTypeDefault
src

Photo URL. Files on another origin need CORS headers, or the canvas can't read their pixels.

Type: stringDefault –
alt

Who the portrait shows. Read out by screen readers. Pass "" for a decorative portrait.

Type: stringDefault –
charset

Which glyphs draw the tones.

Type: "standard" | "dense" | "blocks" | "quadrants" | "braille" | "cp437" | "petscii" | "katakana" | "dots" | "edge"Default "braille"
chars

A custom ramp from empty to dense, such as " .oO@". Wins over charset.

Type: stringDefault –
colorMode

mono draws in one ink. truecolor takes each cell's colour from the photo. duotone reads tones through ramp.

Type: "mono" | "truecolor" | "ansi16" | "duotone" | "palette"Default "mono"
phosphor

With mono: a CRT ink and its dark glass.

Type: "p1" | "p3" | "ibm" | "blueprint"Default –
ramp

With duotone: a named ramp or your own stops, dark to light.

Type: RampName | string[]Default "mainframe"
ansi

With ansi16: which 16-colour table.

Type: "vga" | "xterm"Default "vga"
grade

Tone and colour grading before glyphs are chosen. Passing your own replaces the portrait grade.

Type: { brightness?, contrast?, gamma?, saturation? }Default { contrast: 1.2, gamma: 0.9 }
detail

How much to sharpen features before glyphs are chosen, 0 to 1.

Type: numberDefault 0.5
edges

Draw contour strokes over the tones: lit dots in Braille and quadrants, - / | \ glyphs in a ramp.

Type: booleanDefault true
edgeThreshold

How strong a contour must be to draw a stroke, 0 to 1. Raise it for fewer, bolder lines.

Type: numberDefault 0.16; 0.22 for edge; 0.3 for ramps
develop

Develop from outline to full tone when the photo loads.

Type: booleanDefault true
developDuration

Length of the develop reveal in milliseconds.

Type: numberDefault 2600
compare

hover shows the photo left of the pointer. drag keeps the split where it is dropped. none turns it off.

Type: "hover" | "drag" | "none"Default "hover"
invert

true: dark pixels draw dense glyphs. false: bright pixels do. Unset, it follows the page.

Type: booleanDefault follows the theme
threshold

Braille and quadrants: the tone that lights a dot.

Type: numberDefault 0.5
dither

Ordered dithering between glyph levels and dots.

Type: "none" | "bayer2" | "bayer4" | "bayer8" | "blue-noise"Default "blue-noise" for Braille and quadrants, else "none"
background

transparent draws glyphs only, over whatever is behind.

Type: "paper" | "transparent"Default "paper"
fit

How the photo fills its box, as in object-fit.

Type: "cover" | "contain" | "fill"Default "cover"
focus

Where the photo sits when it overflows, 0 to 1 per axis. The default keeps the eyes in frame.

Type: [x, y]Default [0.5, 0.3]
autoLevels

Stretch the photo's tones to use the whole ramp.

Type: booleanDefault true
cell

Glyph cell height in CSS pixels.

Type: numberDefault 8
animate

false skips the develop reveal.

Type: booleanDefault true
crossOrigin

Passed to the image request.

Type: "anonymous" | "use-credentials"Default "anonymous"
handle

Filled with a handle for export: the current frame as text, ANSI, SVG or PNG.

Type: RefObject<AsciiHandle | null>Default –

Notes

  • The portrait is announced as an image labelled with alt. The split handle is a slider: focus it with Tab, then move it with the arrow keys, or Home and End. Shift moves it in bigger steps.
  • With reduced motion, or animate={false}, the portrait appears fully developed. The split still works.
  • The develop reveal is the only motion. Once it ends the portrait draws nothing until a prop or the box size changes.
  • Sharpening and contour finding run on the CPU once per frame, over the sample grid. A 420 px portrait in Braille at cell={8} is about 100,000 samples, which takes a few milliseconds.
  • Give the box a size, such as aspect-[4/5] w-72. The photo fills it and never sets its own height.
  • The sample photos are public domain: Abraham Lincoln by Alexander Gardner (1863) and Sally Ride's 1984 NASA portrait, both from Wikimedia Commons.