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.
"use client"
import { AsciiImage } from "mischief-ui/ascii-image"
export function AsciiImageDemo() {
return (
<div className="w-full max-w-sm">
<AsciiImage
src="/demo/gallery/impossible-checkbox.png"
alt="The impossible checkbox experiment, drawn as characters"
className="aspect-square"
cell={7}
/>
</div>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/ascii-imageimport { 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.
"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} />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.