01 //The Trick in Our Eyes

Apple Macintosh 128K
A 9-inch monochrome screen with 512 × 342 pixels. In video memory, each pixel was represented by just one bit: 0 for white, 1 for black. There was no hardware palette and no support for grayscale.
In 1984, Apple shipped the original Macintosh with a screen that was, by today's standards, incredibly limited. It had just 512 × 342 pixels, driven by an 8 MHz Motorola 68000 processor. More importantly, each pixel was only one bit: it could be either black or white. There was no color palette, no DAC, and no hardware for displaying different shades of gray.
And yet, open up something like MacPaint and you could see images that looked surprisingly smooth. They didn't look like a collection of black-and-white dots. They looked, at least from a normal viewing distance, much closer to photographs.
The trick wasn't really in the Macintosh's hardware. It was in our eyes.
Put black and white dots close enough together and the eye eventually stops seeing them as separate dots. At a certain distance, they begin to blend into what looks like a continuous tone. The display hasn't actually produced gray; your visual system has essentially done the mixing for it.
This wasn't just a convenient observation made by computer designers. Researchers had been studying the effect for years. In the late 1960s, Larry Roberts was working on image transmission at MIT and showed how carefully arranged high-frequency patterns could be used to make coarse image information appear smoother. By adding small variations around otherwise abrupt changes, it was possible to fool the visual system into perceiving tones that weren't actually present on the screen.
A few years earlier, researchers Fergus Campbell and John Robson had been measuring another important part of the same problem. In 1968, they published measurements of how sensitive human vision is to different spatial frequencies, using sinusoidal luminance gratings. Their results showed that our eyes are particularly sensitive to patterns in roughly the 2–5 cycles-per-degree range. As the frequency gets higher, that sensitivity falls away.

• Horizontal axis: The frequency of the stripes increases from left to right.
• Vertical axis: The contrast fades out toward the top.
Notice how the visible boundary forms a curve that peaks in the middle. Our eyes act a bit like a low-pass filter: fine, high-frequency patterns blend together, while medium frequencies stand out. Dithering takes advantage of this by pushing noise into the fine patterns where our eyes have trouble seeing it.
That detail turns out to be extremely useful for digital images.
Our vision is, in a sense, doing a little bit of filtering all the time. Large, slowly changing patterns are easy for us to see, while very fine alternating patterns tend to blend together. A field of tiny black and white dots can therefore look like a fairly even gray when viewed from the right distance.
Digital dithering takes advantage of exactly this limitation.
Instead of trying to create a gray pixel directly, the system distributes black and white pixels in a carefully chosen pattern. The resulting error is pushed toward higher spatial frequencies, where our eyes are much less sensitive to it. Up close, you can see the dots. Step back, and they start to disappear into the image.
So when an old one-bit Macintosh screen seemed to show something more sophisticated than black and white, it wasn't actually producing shades of gray.
The gray was, in a sense, being created by your eyes.
02 //Error Diffusion: Passing the Remainder Along
If you want to reduce an image down to pure black and white, the most straightforward thing to do is pick a cutoff in the middle. Anything brighter than 128 becomes white, and anything darker becomes black:
const out = pixel >= 128 ? 255 : 0;It is simple to write, but the results look terrible. Smooth skies turn into harsh concentric bands, and subtle shadows on someone's face collapse into flat patches of black and white.
The reason is pretty clear: every time you round a pixel, you throw away information.
Suppose an original pixel has a brightness of 160. If you round it up to white (255), you have made that spot 95 units brighter than it actually was. That difference does not just disappear. If you want the overall brightness of the image to stay accurate, you have to balance things out somewhere else. The pixels right next to it need to be made slightly darker to compensate.
In 1976, Robert Floyd and Louis Steinberg came up with a neat way to handle this. Instead of throwing the rounding error away, they passed it along to nearby pixels that had not been processed yet:
Figure 2.1: Floyd-Steinberg Error Diffusion Kernel
The four fractions add up to exactly 16/16. That means every bit of the rounding error gets passed along, with nothing lost. If you step back and look at the whole picture, the average brightness stays true to the original.
It produces surprisingly smooth results. But it also introduces a quirk of its own. Because the error is pushed mostly to the right and downward, the feedback can create subtle repeating patterns on flat backgrounds. If you look closely at areas with a gentle gradient, you can often spot faint, diagonal trails creeping across the image. Programmers usually call them worm artifacts.
LIVE MATH SIMULATOR: GRADIENT QUANTIZATION & ERROR DIFFUSION
03 //Looking at the Math in the Frequency Domain
Engineers who work on signal processing look at Floyd-Steinberg a little differently. To them, it is not just an image filter; it is a two-dimensional feedback loop, very much like a sigma-delta converter.
You can see why by looking at how the filter behaves at different frequencies.
If you write out the math in the z-domain, you get a noise transfer function that tells you how different frequencies are handled:
H(1, 1) = 7/16 + 3/16 + 5/16 + 1/16 = 1.0
∴ NTF(1, 1) = 1 - 1.0 = 0 (Low-frequency error is canceled out completely)
At zero frequency—what engineers call DC—the noise transfer function drops all the way to zero. That is why the overall brightness of the image stays so stable over large areas. The algorithm simply does not let low-frequency errors survive.
Instead, that noise gets pushed up into higher spatial frequencies. But because the filter distributes error unevenly in different directions, some frequencies get boosted more than others. That uneven response is where those worm-like patterns come from.
H(1, 1) = 6/8 = 0.75
∴ NTF(1, 1) = 1 - 0.75 = 0.25 (25% of low-frequency error leaks through)
Atkinson's filter behaves quite differently. When you evaluate its response at zero frequency, you do not get zero. You get 0.25.
That means about a quarter of the low-frequency error is not passed along at all. It just leaks out of the system. In engineering terms, it acts like a leaky integrator. Floyd-Steinberg holds onto every scrap of error, while Atkinson lets some of it fade away. And that changes the look of the image quite a bit.
04 //The 25% Philosophy: Why Atkinson Dropped Part of the Error
When Bill Atkinson was writing MacPaint and QuickDraw for the original Mac, he had some very practical problems to solve.
For one thing, the computer was not particularly fast. The Mac ran on an 8 MHz Motorola 68000. Dividing by 16 took noticeable time, but dividing by 8 was fast—it was just a 3-bit shift that the processor could do in a single instruction.
There was also the question of how user interfaces actually look. MacPaint was not just for showing photographs. It had white canvases, window borders, and menus.
If you use Floyd-Steinberg on a large white background, small errors can accumulate as the algorithm works across the screen. Eventually, that accumulated error forces the algorithm to drop a solitary black pixel right in the middle of a clean white area. On a 1984 screen, that single dot looked like a speck of dust on the glass.
Atkinson's solution was simple: just do not keep all the error.
Figure 4.1: Atkinson 6-Neighbor Error Kernel
His filter distributes 1/8 to six neighboring pixels: two to the right, three on the row below, and one two rows down. That adds up to 6/8, or 75%. The remaining 25% is simply thrown away.
Mathematically, it breaks energy conservation. But on the screen, it works remarkably well. Small errors fade away before they can turn into stray dots. White backgrounds stay clean, shadows keep their contrast, and icons look sharp and well-defined.
REF63 Grayscale Tones
100%Smooth Halftones
75%Clean White Backing
4×4Crosshatch Pattern
• Floyd-Steinberg produces the smoothest gradations, but you can see faint diagonal patterns across the uniform background.
• Atkinson leaves the background clean white while preserving strong contrast around edges.
• Bayer 4×4 creates regular geometric patterns without carrying any error from pixel to pixel.
05 //Bayer Takes Another Path: Ordered Thresholds
Around the same time, Bryce Bayer at Eastman Kodak was working on a completely different idea.
He wondered: what if you do not pass errors to neighboring pixels at all? What if you just compare each pixel to a fixed, repeating pattern?
That is ordered dithering. You take a small 4×4 grid of thresholds and repeat it across the entire screen. For each pixel, you check its brightness against the corresponding spot in the grid. If it is brighter than the threshold, you make it white. If not, you make it black.

The numbers in the Bayer matrix are not chosen randomly. They are arranged so that as an area gets darker, new dots appear as far apart from each other as possible.
The big advantage here is speed. Because each pixel is calculated on its own, without waiting for its neighbors, modern graphics cards can run the whole thing in parallel.
The trade-off is a noticeable crosshatch pattern, especially on flat colors. It looks a bit like the print pattern in an old comic book.
Even so, ordered dithering has a big advantage for games: it stays completely steady. When Lucas Pope was making Return of the Obra Dinn, he originally tried error diffusion to get that classic 1-bit Mac look. But because error diffusion carries state from pixel to pixel, moving the camera caused the dots to shift and crawl across surfaces. It felt like the whole world was buzzing. Switching to a screen-space Bayer pattern locked everything in place.
06 //Linear Light vs. sRGB
There is another detail that often catches people off guard: doing dithering math on standard image files.
Most images on a computer are saved with a gamma curve, usually around 2.2. That encoding is designed to match how our eyes perceive brightness, but it means the numbers do not scale linearly with actual light. A pixel value of 186 is not twice as bright as 93. In terms of actual light hitting your eyes, it is roughly four times brighter.
• Conversion to linear light: C_linear = (C_sRGB / 255) ^ 2.2
• In sRGB, numbers do not match physical light directly. Diffusing errors in sRGB values without converting first tends to make shadows too dark and midtones look dull.
If you calculate errors directly using these numbers, you end up treating small changes in dark areas the same as small changes in bright areas. That tends to crush details in the shadows and make midtones look dull.
The right way to do it is to convert the image into linear light first, do all the error diffusion there, and then convert back for display.
The same thing applies when matching colors from limited palettes, like on a Game Boy or a Pico-8. Finding the closest color using raw RGB math often picks the wrong hue, because human vision does not weigh red, green, and blue equally. Converting colors into a perceptual space like Oklab first gives much more natural results.
07 //Serial Dependencies and Serpentine Rows
One awkward thing about error diffusion is that you cannot easily run it on multiple threads.
To calculate a pixel, you need the error from the pixel to its left. To start the next row, you need the error from the row above. That chain of dependencies means you have to process pixels in order, one by one.
Scanning left to right on every line also tends to push errors toward the bottom-right corner, giving images a slight diagonal pull.
A common fix is serpentine scanning: you go left to right on even rows, and right to left on odd rows. Reversing the direction on every line helps balance out the spread of error and keeps the patterns looking more even.
08 //Spectral Profiles: What the Fourier Transform Shows
If you measure a dithered image using standard formulas like mean squared error, the numbers look awful. That makes sense: turning smooth gradients into black and white dots is going to create plenty of numerical error.
To see what is actually happening, researchers use frequency analysis—specifically, a technique called Radial Average Power Spectral Density (RAPSD), popularized by Robert Ulichney in 1987:
Low frequencies near DC are nearly silent, while energy rises smoothly toward higher frequencies.
Some low-frequency error leaks through, creating higher local contrast and cleaner highlights.
Displays sharp spikes corresponding to its regular, repeating crosshatch grid.
Looking at Floyd-Steinberg this way reveals something interesting: it naturally behaves as a blue noise generator. There is very little energy at low frequencies, while the energy climbs smoothly at higher frequencies.
That fits human vision neatly. As Campbell and Robson showed, our eyes are much less sensitive to high-frequency patterns. Blue noise puts most of the error right where we are least likely to notice it.
09 //From the Game Boy to Obra Dinn
Dithering did not go away when screens got better. It gradually turned into a visual style.
On the original Game Boy, artists had only four greenish shades on an unlit screen. In games like The Legend of Zelda: Link's Awakening, they used dithering carefully—stippling sand and water to suggest texture, while keeping characters clean with solid outlines.
Years later, Return of the Obra Dinn showed that a strict 1-bit palette could still carry an entire modern game. By using ordered dithering with a careful eye for light and shadow, the game captured the feel of an old copperplate illustration.
10 //Choosing the Right Approach
Looking back, dithering is a good reminder that computer displays have always relied on a bit of illusion.
Each approach has its own strengths:
- Floyd-Steinberg remains great when you want the smoothest possible transitions in photographic images.
- Atkinson works best when you want crisp contrast, clean white backgrounds, and sharp edges.
- Bayer is usually the best choice for real-time graphics and games where speed and temporal stability matter most.
“There is not really a single mathematically perfect algorithm. The goal has always been to find a pattern of dots that works best with the way our eyes actually see.”
Try These Algorithms Live in PixKit
PixKit runs entirely inside your browser. Upload an image, toggle between Floyd-Steinberg, Atkinson, and Bayer, and download crisp pixel art up to 8x resolution.
Open PixKit Studio