Image / media · 5.7
Error-diffusion dithering
A Game Boy screenshot, or a 1-bit Mac.
Reducing an image to a tiny palette while preserving apparent detail.
4 knobs
How it actually works
The only feature in this catalog that is not a shader and could not be one. Everything else here is embarrassingly parallel: every pixel decides independently. This one requires the pixel to its left to have already made up its mind. That single dependency is why it lives on the CPU in 2026.
Notably, this runs on the CPU, not the GPU. Error diffusion is inherently sequential: each pixel's error propagates to neighbours that have not been processed yet, which fights the entire GPU model. JavaScript walks the pixels in order, rounds each to the nearest palette colour, takes the error (desired minus actual), and spreads it to neighbours by weights. Floyd-Steinberg's are 7/16, 3/16, 5/16, 1/16, asymmetric to avoid diagonal artifacts. Palette index comes from luminance: 0.299R + 0.587G + 0.114B.
The knobs, named
Algorithm, palette size, luminance weights, serpentine. An eight-way algorithm dropdown over the same image is the most educational control here: you can see the difference between Atkinson and Floyd-Steinberg instantly, and nobody can explain it in words.
| Knob | Source | What it teaches |
|---|---|---|
| Algorithm | sourced | The eight are the source's own list. Atkinson throws away 25% of the error, which is why old Macs look blown-out and clean. Bayer is here as the contrast: no error at all, just a threshold lookup. |
| Palette size | sourced | 2 is 1-bit. The technique is at its most honest at 2, where every pixel is a lie and the image still works. |
| Pixel scale | ours | Chunkiness. Also the perf knob: this is CPU work and the cost is per pixel. |
| Serpentine | sourced | Alternate the scan direction each row. Off, the errors all drift the same way and you get diagonal streaks. |
sourced means the source names this parameter. ours means the source names none and the knob is our design against the mechanism. No knob here is invented and passed off as sourced.
Evidence
VERIFIED (author)
Codrops "Efecto" (2026); "WebGL Backgrounds with Bayer Dithering"; "Animating 160,000 Cubes in Three.js to Visualize Dithering"; "Dithering Process Visualization". The algorithms and the 7/16, 3/16, 5/16, 1/16 weights are verbatim. The ordered-vs-error-diffusion real-time contrast is marked INFERRED in the index.
- Seen on
- Codrops "Efecto" and the dithering series.
- Dependencies
- vanilla JS + Canvas 2D
- Difficulty
- moderate (Floyd-Steinberg is about 20 lines); hard to make interactive at video rates on CPU
- Performance
- CPU-bound. It blocks the main thread on large images, so the real answer is a worker. Ours keeps the source small and recomputes only on knob change, which is the sourced advice.
- Accessibility and the floor
- Static output, no motion, no reduced-motion branch needed. It is the same picture whether or not you allow animation.
Notes
Composability. Pairs with halftone (5.6). Same source image, two philosophies of destruction.