mezzotint

Open source · zero dependencies

Images, made of living dust.

mezzotint turns any picture into thousands of particles. They hold its shape, drift, scatter under your cursor — and flow into the next picture as you scroll.

Move your cursor through the letters · scroll to begin

01 — Sampling

Every pixel casts a vote.

Bright pixels pull particles in, dark ones leave room. A few thousand points, placed by brightness, are enough to read a face, a gesture, a mood.

02 — Tone

Light becomes density.

Gradients survive. A face half in shadow stays half in shadow — the sampler keeps the tonal range of a photograph instead of flattening it into a cut‑out.

03 — Morphing

Shapes flow. They don’t jump.

Particles are ordered along a space‑filling curve, so neighbours in one image stay neighbours in the next. The transition reads as motion, not noise.

04 — Scroll

The scrollbar is the timeline.

Every transition on this page is scrubbed by your scroll position. Stop halfway and the dust waits with you; scroll back and it plays in reverse.

05 — Touch

It answers to your hand.

Move the pointer through the dust to push it aside. Press and hold to blast it apart — let go, and every particle finds its way home.

Press and hold on the hand

06 — Weight

Small enough to ship.

One ES module, no dependencies. WebGL point sprites with a Canvas 2D fallback; it pauses off‑screen and honours reduced‑motion settings.

07 — Yours

Bring your own silhouette.

High‑contrast black‑and‑white pictures work best: a figure against the light, a shape on a plain background. The configurator inverts, sharpens and cleans them up — and nothing leaves your browser.

Get started

Three lines to your first shape.

Install from npm or import the module straight from a CDN. Give it a canvas and a picture; every later picture morphs out of the previous one.

// npm i mezzotint
import { Mezzotint } from 'mezzotint';

const dust = new Mezzotint('#canvas');
await dust.show('silhouette.jpg');

// later: flows into the next picture
dust.show('another.jpg', { sweep: 'left' });
dust.text('hello');
// scroll-driven story, like this page
import { Mezzotint, story } from 'mezzotint';

const dust = new Mezzotint('#canvas');
story(dust, [...document.querySelectorAll('[data-src]')]
  .map((el) => ({ el, src: el.dataset.src })));

What’s inside

Brightness‑weighted sampling

Stratified sampling places a fixed number of particles by brightness — even dust, no clumps, no holes.

Flow and swarm morphs

Hilbert‑curve ordering keeps neighbours together; swarm mode lets every particle go anywhere. Sweep from any side.

Scroll scrubbing

story() ties transitions to the scroll position — forwards, backwards, paused halfway.

Pointer physics

Repel, attract or vortex; press and hold to blast the shape apart. Touch-friendly.

Image clean‑up

Auto‑invert for dark ink on light paper, levels, blur, edge detection, posterize and a flat silhouette mode.

Polite by default

Pauses off‑screen, stops drawing when nothing moves and respects prefers-reduced-motion.

The name

Light, pulled out of the dark.

Mezzotint is a printmaking technique from the 1640s. A copper plate is roughened with thousands of tiny dots, and the image is worked out of that darkness: la manière noire, the dark manner. Image editors still list a Mezzotint filter under “Pixelate”. This library does the same with light, one particle at a time.

Sample images

Fourteen pictures to play with.

Open any of them in the configurator. The sample images are AI-generated: they were made with Google Gemini for this demo.