# ASCII image

Any photo redrawn in live glyphs, with every charset and colour mode, auto levels and a pointer spotlight.

> 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-image.json](https://asciicn.dev/r/ascii-image.json), under `files[].content`.

## Installation [#installation]

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

## Usage [#usage]

```tsx
import { AsciiImage } from "@/components/asciicn/ascii-image";

export function Cover() {
  return (
    <AsciiImage
      src="/photos/launch.jpg"
      alt="The launch pad at dawn"
      charset="dense"
      reveal="spotlight"
      className="aspect-video w-full"
    />
  );
}
```

The image is sampled on the CPU. Each glyph takes the area average of the pixels under its cell, so fine detail averages out instead of flickering into noise. Braille samples 2×4 dots per cell and quadrants 2×2, which gives those charsets four to eight times the detail of a ramp at the same cell size.

Switching `charset` or `colorMode` redraws from the samples already taken. Resizing, a new `fit` or a new `src` samples the image again, once.

On a dark page, bright pixels draw dense glyphs. On a light page it flips, so dark pixels draw dense glyphs and the photo still reads the right way round. Set `invert` to pick one yourself. Phosphor and duotone looks bring their own dark glass and never flip.

`autoLevels` stretches the image so its darkest and brightest 1% of pixels reach the ends of the ramp. Flat, foggy photos gain the most. Turn it off to keep a photo's own exposure, then use `grade` to adjust it.

## Examples [#examples]

### Amber phosphor in Braille [#amber-phosphor-in-braille]

Two by four dots per cell in one ink, with the P3 amber glass behind it.

```tsx title="examples/ascii-image-phosphor.tsx"
import { AsciiImage } from "@/components/asciicn/ascii-image";

export default function AsciiImagePhosphor() {
  return (
    <AsciiImage
      src="/examples/earthrise.jpg"
      alt="Earth rising over the grey horizon of the Moon, seen from Apollo 8"
      charset="braille"
      colorMode="mono"
      phosphor="p3"
      cell={9}
      className="aspect-[4/3] w-full max-w-xl"
    />
  );
}
```

### See the photo under the pointer [#see-the-photo-under-the-pointer]

`reveal="photo"` opens a soft window onto the original file around the pointer.

```tsx title="examples/ascii-image-reveal.tsx"
import { AsciiImage } from "@/components/asciicn/ascii-image";

export default function AsciiImageReveal() {
  return (
    <AsciiImage
      src="/examples/pillars.jpg"
      alt="The Pillars of Creation, towers of gas and dust in the Eagle Nebula"
      charset="dense"
      reveal="photo"
      revealRadius={140}
      cell={8}
      className="aspect-[4/5] w-full max-w-sm"
    />
  );
}
```

### Edges and blocks [#edges-and-blocks]

`charset="edge"` draws strong contours as `- / | \` strokes across the gradient and fills the rest from a ramp. Next to it, ANSI blocks through the sodium ramp.

```tsx title="examples/ascii-image-edge.tsx"
import { AsciiImage } from "@/components/asciicn/ascii-image";

export default function AsciiImageEdge() {
  return (
    <div className="grid w-full max-w-3xl gap-4 sm:grid-cols-2">
      <AsciiImage
        src="/examples/tetons.jpg"
        alt="The Teton Range above the Snake River, drawn as Sobel edge strokes"
        charset="edge"
        colorMode="mono"
        edgeThreshold={0.18}
        cell={9}
        className="aspect-[5/4] w-full"
      />
      <AsciiImage
        src="/examples/tetons.jpg"
        alt="The same view in ANSI blocks with a sodium duotone"
        charset="blocks"
        colorMode="duotone"
        ramp="sodium"
        cell={9}
        className="aspect-[5/4] w-full"
      />
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `src` | `string` |  | Image URL. Files on another origin need CORS headers, or the canvas can't read their pixels. |
| `alt` | `string` |  | What the image shows. Read out by screen readers. Pass "" for a decorative image. |
| `charset` | `"standard" \| "dense" \| "blocks" \| "quadrants" \| "braille" \| "cp437" \| "petscii" \| "katakana" \| "dots" \| "edge"` | `"standard"` | 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"` | `"truecolor"` | truecolor takes each cell's own colour. ansi16 snaps it to 16 terminal colours. mono draws in one ink. duotone and palette colour glyphs by tone. |
| `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? }` |  | Tone and colour grading before glyphs are chosen. |
| `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. |
| `edgeThreshold` | `number` | `0.22` | Edge charset: how strong a contour must be to draw a stroke. |
| `dither` | `"none" \| "bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"none"` | Ordered dithering between glyph levels, which smooths gradients in short ramps. |
| `background` | `"paper" \| "transparent"` | `"paper"` | transparent draws glyphs only, over whatever is behind. |
| `fit` | `"cover" \| "contain" \| "fill"` | `"cover"` | How the image fills its box, as in object-fit. |
| `focus` | `[x, y]` | `[0.5, 0.5]` | Where the image sits when it overflows or underfills, 0 to 1 per axis. |
| `autoLevels` | `boolean` | `true` | Stretch the image's tones to use the whole ramp. |
| `reveal` | `"none" \| "spotlight" \| "photo"` | `"none"` | spotlight thins glyphs away from the pointer. photo shows the original file around it. |
| `revealRadius` | `number` | `120` | Radius of the reveal in CSS pixels. |
| `intro` | `boolean` | `true` | Glyphs resolve in from noise once the image has loaded. |
| `cell` | `number` | `10` | Glyph cell height in CSS pixels. |
| `animate` | `boolean` | `true` | false skips the intro and the loading sweep. |
| `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 wrapper is announced as an image labelled with `alt`. The canvas inside is hidden from assistive technology. With `alt=""` the whole thing is decorative.
* While the file loads, a faint sweep of glyphs and a spinner hold its place. If it fails to load, the box says so in glyphs.
* With reduced motion, the image appears without the intro. The spotlight still follows the pointer.
* A still image draws once and then stops. Only the intro, the loading sweep and `reveal="spotlight"` keep a frame loop running, and that loop pauses offscreen and in hidden tabs.
* Give the box a size, such as `aspect-video w-full`. The image fills it and never sets its own height.