Mischief

82 / Scenes

Dither Image

A photograph reduced to two theme colours through an ordered dither, the way a newspaper reduced one to ink and paper.

The Mischief mark and wordmark, reduced to two tones

Two theme colours and an ordered dither, the way a newspaper did it.

Installation

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

npx shadcn@latest add Tinkerers-Labs/mischief-ui/dither-image
import { DitherImage } from "mischief-ui/dither-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/dither-image/dither-image.tsx
"use client" /* eslint-disable @next/next/no-img-element -- This must work outside Next.js. */ import * as React from "react"import {  RenderSurface,  createQuadProgram,  useThemeColors,  type QuadProgram,  type SurfaceColor,} from "@/registry/default/render-surface/render-surface"import { cn } from "@/lib/utils" 

Usage

export function Portrait() {
  return (
    <DitherImage
      src="/team/ada.jpg"
      alt="Ada at her desk"
      cell={4}
      className="aspect-[3/2] rounded-xl"
    />
  )
}

Ordered, not random

The threshold each pixel is measured against comes from a repeating four by four matrix rather than from a random number. That is what gives the result its woven, printed look instead of the sandy look of noise, and it is also why the picture is stable: the same pixel gets the same threshold on every frame, so nothing crawls.

Because both colours come from the theme, the same photograph arrives as dark ink on pale paper in a light application and the other way round in a dark one.

There is always a picture

The original is rendered as an ordinary image underneath. If WebGL is missing, if the shader will not compile, or if the picture comes from a host that refuses to let it be read, the reader gets the photograph rather than an empty box.

API

srcstringThe picture.
altstringDescribes it. Required, and used by both the fallback and the canvas.
cellnumberSize of one dot in pixels. Larger is coarser. Defaults to 4.
levelsnumberHow many tones survive. Two is a pure halftone. Defaults to 2.
basestringThe paper. Defaults to "--background".
tintstringThe ink. Defaults to "--foreground".
contrastnumberApplied before the dither. Defaults to 1.15.

Accessibility

The alt text is carried by both the fallback image and the canvas, so the picture is described once whichever is showing. Nothing moves, so there is nothing for reduced motion to remove.