Seven dithering algorithms race on a moving scene in 1-bit, handheld green, CGA and EGA.
A procedurally painted lake at sunset, with drifting clouds, sun glitter and a passing sailboat, is quantized every frame to a tiny retro palette. Error diffusion snaps each pixel to the nearest palette color and pushes the leftover error onto unvisited neighbors (Floyd-Steinberg, Jarvis-Judice-Ninke, and Atkinson, which passes on only 6/8 of it), Riemersma diffuses along a Hilbert curve with a fading 16-pixel memory, and ordered dithering adds a fixed threshold pattern: a recursive 8x8 Bayer matrix or a blue noise mask grown with the void-and-cluster method. Tonal palettes dither by luminance (pulled halfway toward the frame's equalized histogram, so every shade gets used), color palettes by nearest color, and Adaptive 8 picks its colors from the frame with k-means. The gamma toggle runs everything in linear light (raw sRGB dithering brightens every midtone), and the grid view scores each algorithm by how far its blurred result strays from the blurred original.
Try it. Drag anywhere to move the wipe, and click the chips beside the handle (or press 1 to 7 and B) to change either side, which can also show the original. The buttons switch palette, scene (the lake or a test card with ramps and a spinning sphere), wipe or grid view, sRGB or linear light, pixel size and motion; hover for a magnifier. Keys: P palette, G grid, L linear light, C scene, space to freeze, plus and minus for pixel size.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build a dithering playground with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Make a canvas that fills the window. Do all the pixel work on a small offscreen canvas, about 320x200, and draw it scaled up with imageSmoothingEnabled = false so the pixels stay crisp.
- Paint a test image into the offscreen canvas yourself: a sky gradient, a glowing sun, a few rolling hills and a horizontal gray ramp. Smooth gradients are what make dithering interesting.
- Quantize it to two colors, black and white, three ways: plain thresholding at 50%, Floyd-Steinberg error diffusion (push 7/16 of each pixel's error right and 3/16, 5/16 and 1/16 to the row below), and ordered dithering with a recursive 8x8 Bayer matrix.
- Show one algorithm on the left and another on the right of a vertical line you can drag with the mouse, with a label on each side and number keys to pick the algorithms.
Once that works, make it beautiful:
- Add retro palettes: a 4-shade green handheld ramp, the 4-color CGA palette and the 16 EGA colors. For tonal ramps dither on luminance; for color palettes pick the nearest color in RGB and diffuse the error per channel.
- Animate the scene (drifting sun, rippling water) and re-dither every frame, so you can see error diffusion shimmer while the Bayer pattern stays locked in place.
- Run Floyd-Steinberg in serpentine order (alternate direction on each row) to break up the diagonal worms.
- Add a toggle that converts to linear light before dithering, and notice how much darker and more correct the midtones become.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as Atkinson's or Jarvis-Judice-Ninke's kernels, a blue noise threshold mask made with the void-and-cluster method, or Riemersma dithering along a Hilbert curve.