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

> For the complete documentation index, see [llms.txt](/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](/.well-known/agent-skills/asciicn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the asciicn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `AsciiShader` ([Write your own](/docs/engine/write-your-own.md)), or draw a CPU grid with `AsciiGrid`, and read this example's source for technique. Its full source is in [https://asciicn.dev/r/ascii-portrait.json](https://asciicn.dev/r/ascii-portrait.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://asciicn.dev/r/ascii-portrait.json
```

## Usage [#usage]

```tsx
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 [#examples]

### Colour, with a split you drag [#colour-with-a-split-you-drag]

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

```tsx title="examples/ascii-portrait-color.tsx"
import { AsciiPortrait } from "@/components/asciicn/ascii-portrait";

export default function AsciiPortraitColor() {
  return (
    <AsciiPortrait
      src="/examples/ride.jpg"
      alt="Astronaut Sally Ride in 1984, smiling, in a navy NASA shirt"
      colorMode="truecolor"
      grade={{ brightness: 0.1, contrast: 1.1, saturation: 1.4 }}
      edges={false}
      compare="drag"
      cell={6}
      className="aspect-[4/5] w-full max-w-sm"
    />
  );
}
```

### Strokes and phosphor [#strokes-and-phosphor]

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

```tsx title="examples/ascii-portrait-sketch.tsx"
import { AsciiPortrait } from "@/components/asciicn/ascii-portrait";

export default function AsciiPortraitSketch() {
  return (
    <div className="grid w-full max-w-3xl gap-4 sm:grid-cols-2">
      <AsciiPortrait
        src="/examples/lincoln.jpg"
        alt="Abraham Lincoln drawn in classic glyphs with contour strokes"
        charset="standard"
        invert
        detail={0.7}
        cell={9}
        compare="none"
        className="aspect-[4/5] w-full"
      />
      <AsciiPortrait
        src="/examples/lincoln.jpg"
        alt="Abraham Lincoln in Braille on amber phosphor glass"
        phosphor="p3"
        edgeThreshold={0.22}
        cell={7}
        compare="none"
        className="aspect-[4/5] w-full"
      />
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` |  | Photo URL. Files on another origin need CORS headers, or the canvas can't read their pixels. |
| `alt` | `string` |  | Who the portrait shows. Read out by screen readers. Pass "" for a decorative portrait. |
| `charset` | `"standard" \| "dense" \| "blocks" \| "quadrants" \| "braille" \| "cp437" \| "petscii" \| "katakana" \| "dots" \| "edge"` | `"braille"` | Which glyphs draw the tones. |
| `chars` | `string` |  | A custom ramp from empty to dense, such as " .oO@". Wins over charset. |
| `colorMode` | `"mono" \| "truecolor" \| "ansi16" \| "duotone" \| "palette"` | `"mono"` | mono draws in one ink. truecolor takes each cell's colour from the photo. duotone reads tones through ramp. |
| `phosphor` | `"p1" \| "p3" \| "ibm" \| "blueprint"` |  | With mono: a CRT ink and its dark glass. |
| `ramp` | `RampName \| string[]` | `"mainframe"` | With duotone: a named ramp or your own stops, dark to light. |
| `ansi` | `"vga" \| "xterm"` | `"vga"` | With ansi16: which 16-colour table. |
| `grade` | `{ brightness?, contrast?, gamma?, saturation? }` | `{ contrast: 1.2, gamma: 0.9 }` | Tone and colour grading before glyphs are chosen. Passing your own replaces the portrait grade. |
| `detail` | `number` | `0.5` | How much to sharpen features before glyphs are chosen, 0 to 1. |
| `edges` | `boolean` | `true` | Draw contour strokes over the tones: lit dots in Braille and quadrants, - / \| \ glyphs in a ramp. |
| `edgeThreshold` | `number` | `0.16; 0.22 for edge; 0.3 for ramps` | How strong a contour must be to draw a stroke, 0 to 1. Raise it for fewer, bolder lines. |
| `develop` | `boolean` | `true` | Develop from outline to full tone when the photo loads. |
| `developDuration` | `number` | `2600` | Length of the develop reveal in milliseconds. |
| `compare` | `"hover" \| "drag" \| "none"` | `"hover"` | hover shows the photo left of the pointer. drag keeps the split where it is dropped. none turns it off. |
| `invert` | `boolean` | `follows the theme` | true: dark pixels draw dense glyphs. false: bright pixels do. Unset, it follows the page. |
| `threshold` | `number` | `0.5` | Braille and quadrants: the tone that lights a dot. |
| `dither` | `"none" \| "bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"blue-noise" for Braille and quadrants, else "none"` | Ordered dithering between glyph levels and dots. |
| `background` | `"paper" \| "transparent"` | `"paper"` | transparent draws glyphs only, over whatever is behind. |
| `fit` | `"cover" \| "contain" \| "fill"` | `"cover"` | How the photo fills its box, as in object-fit. |
| `focus` | `[x, y]` | `[0.5, 0.3]` | Where the photo sits when it overflows, 0 to 1 per axis. The default keeps the eyes in frame. |
| `autoLevels` | `boolean` | `true` | Stretch the photo's tones to use the whole ramp. |
| `cell` | `number` | `8` | Glyph cell height in CSS pixels. |
| `animate` | `boolean` | `true` | false skips the develop reveal. |
| `crossOrigin` | `"anonymous" \| "use-credentials"` | `"anonymous"` | Passed to the image request. |
| `handle` | `RefObject<AsciiHandle \| null>` |  | Filled with a handle for export: the current frame as text, ANSI, SVG or PNG. |

## Notes [#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.