Mischief

83 / Scenes

ASCII Image

A photograph redrawn as characters. The grid is worked out once and kept, so each frame is a single copy rather than thousands of letters measured again.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/ascii-image
import { AsciiImage } from "mischief-ui/ascii-image"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/ascii-image/ascii-image.tsx
"use client" import * as React from "react"import {  RenderSurface,  useThemeColors,  type SurfaceColor,} from "@/registry/default/render-surface/render-surface"import { cn } from "@/lib/utils" export type AsciiImageProps = React.HTMLAttributes<HTMLDivElement> & {  src: string  alt: string  /** Width of one character cell in pixels. Smaller is more detailed. */

Usage

export function Portrait() {
  return (
    <AsciiImage
      src="/team/ada.jpg"
      alt="Ada at her desk"
      cell={7}
      className="aspect-square"
    />
  )
}

Worked out once

A grid of this size is several thousand characters, each of which has to be measured and drawn. Doing that every frame to produce an identical result would be the most expensive component here by a wide margin.

So the grid is drawn once into a canvas kept aside, and every frame after that is a single copy of it. It is worked out again only when something it is made of changes: the picture arrived, the box was resized, the theme was switched, a prop moved. Everything else costs one copy.

The surface underneath still sleeps whenever it is off screen or its tab is hidden, so a page holding one of these is doing nothing at all while it is out of sight.

Choosing the characters

The ramp runs from the character used for the darkest part of the picture to the one used for the lightest, and the last entry is usually a space. Shorter ramps are more graphic and longer ones hold more detail.

<AsciiImage src="/team/ada.jpg" alt="Ada" ramp="#+-. " cell={10} />
Cells are drawn a little taller than they are wide, because that is the shape of a character.

API

srcstringThe picture.
altstringDescribes it. Required.
cellnumberWidth of one character cell in pixels. Defaults to 8.
rampstringThe characters, darkest first. Defaults to "@%#*+=-:. ".
colorstringThe characters. Defaults to "--foreground".
backgroundstringBehind them. Defaults to "--background".
contrastnumberApplied before the ramp. Defaults to 1.2.

Accessibility

The canvas is announced as an image with your alt text, so the picture is described exactly as an ordinary one would be. Nothing animates at any setting.