Nine dithering algorithms, measured rather than asserted
Every image on this page is shown at exactly 1:1 with no resampling, because
a dithered image viewed at any other zoom shows the browser's scaling artifacts instead of
the algorithm's. The numbers under each panel come from the same functions the test suite
asserts against.
The masks, and why blue noise is blue
A dither mask cannot be judged by eye. White noise and blue noise both look like noise,
and the difference between them is entirely in the radial power spectrum: blue noise
suppresses low spatial frequencies, which are the ones the eye is most sensitive to.
Blue noise 64x64
White noise 64x64
Bayer 8x8, tiled to 64
mask
low band
high band
high/low
slope
peak/mean
blue?
Blue noise 64x64
4.428e-06
7.214e-02
16,290
1.17
4
yes
White noise 64x64
8.341e-02
8.880e-02
1
0.33
3
no
Bayer 8x8, tiled to 64
0.000e+00
1.239e-02
infinite
3.28
1,673
no
Bayer's ratio is infinite, because a perfectly periodic
matrix has exactly zero energy in the low band. A test that only checked the high-to-low
ratio would therefore rank Bayer as the finest blue noise ever produced. The peak-to-mean
column is what rejects it: Bayer's energy sits in a few sharp spikes, roughly 1,700 times
the mean, and those spikes are the cross-hatch you can see in the first row below. This is
the reason the check in spectrum.py tests three properties instead of one.
Linear ramp
The banding test. Every level from black to white, once.
SourceUndithered, 8 bits per pixel.
Bayer 8x8Ordered, periodic. Cheap and stateless, and the cross-hatch is visible at every level.mean 0.5000 against 0.5000 in source, delta +0.0000
White noiseThe control. Same construction as blue noise with the spectral shaping removed, which is what makes it grainy.mean 0.4999 against 0.5000 in source, delta -0.0001
Blue noise 64x64Void-and-cluster. Stateless like Bayer, aperiodic like noise, with low-frequency energy suppressed.mean 0.4999 against 0.5000 in source, delta -0.0001
Floyd-SteinbergThe default error diffusion. Note what it does to flat mid-grey.mean 0.5000 against 0.5000 in source, delta +0.0000
AtkinsonDiscards a quarter of the error, which expands contrast and crushes the extremes.mean 0.5000 against 0.5000 in source, delta +0.0000
Jarvis-Judice-NinkeA wider kernel, so smoother gradients and more smear.mean 0.5000 against 0.5000 in source, delta +0.0000
StuckiWider again, tuned for sharper edges than Jarvis.mean 0.5000 against 0.5000 in source, delta +0.0000
RiemersmaError diffused along a Hilbert curve instead of a raster scan.mean 0.5000 against 0.5000 in source, delta +0.0000
Halftone, 45 degreesA rotated screen with a round spot, as used in print.mean 0.5007 against 0.5000 in source, delta +0.0007
Step wedge
Sixteen flat patches. Any tone shift shows up as a patch that reads wrong against its neighbours.
SourceUndithered, 8 bits per pixel.
Bayer 8x8Ordered, periodic. Cheap and stateless, and the cross-hatch is visible at every level.mean 0.5000 against 0.5000 in source, delta +0.0000
White noiseThe control. Same construction as blue noise with the spectral shaping removed, which is what makes it grainy.mean 0.4995 against 0.5000 in source, delta -0.0005
Blue noise 64x64Void-and-cluster. Stateless like Bayer, aperiodic like noise, with low-frequency energy suppressed.mean 0.5005 against 0.5000 in source, delta +0.0005
Floyd-SteinbergThe default error diffusion. Note what it does to flat mid-grey.mean 0.5000 against 0.5000 in source, delta +0.0000
AtkinsonDiscards a quarter of the error, which expands contrast and crushes the extremes.mean 0.5000 against 0.5000 in source, delta +0.0000
Jarvis-Judice-NinkeA wider kernel, so smoother gradients and more smear.mean 0.5000 against 0.5000 in source, delta +0.0000
StuckiWider again, tuned for sharper edges than Jarvis.mean 0.5000 against 0.5000 in source, delta +0.0000
RiemersmaError diffused along a Hilbert curve instead of a raster scan.mean 0.5000 against 0.5000 in source, delta +0.0000
Halftone, 45 degreesA rotated screen with a round spot, as used in print.mean 0.4989 against 0.5000 in source, delta -0.0011
Radial gradient
Curved level sets, which is where directional artifacts become obvious.
SourceUndithered, 8 bits per pixel.
Bayer 8x8Ordered, periodic. Cheap and stateless, and the cross-hatch is visible at every level.mean 0.4570 against 0.4568 in source, delta +0.0002
White noiseThe control. Same construction as blue noise with the spectral shaping removed, which is what makes it grainy.mean 0.4577 against 0.4568 in source, delta +0.0009
Blue noise 64x64Void-and-cluster. Stateless like Bayer, aperiodic like noise, with low-frequency energy suppressed.mean 0.4571 against 0.4568 in source, delta +0.0003
Floyd-SteinbergThe default error diffusion. Note what it does to flat mid-grey.mean 0.4568 against 0.4568 in source, delta -0.0000
AtkinsonDiscards a quarter of the error, which expands contrast and crushes the extremes.mean 0.4471 against 0.4568 in source, delta -0.0097
Jarvis-Judice-NinkeA wider kernel, so smoother gradients and more smear.mean 0.4568 against 0.4568 in source, delta -0.0000
StuckiWider again, tuned for sharper edges than Jarvis.mean 0.4568 against 0.4568 in source, delta -0.0000
RiemersmaError diffused along a Hilbert curve instead of a raster scan.mean 0.4568 against 0.4568 in source, delta -0.0000
Halftone, 45 degreesA rotated screen with a round spot, as used in print.mean 0.4566 against 0.4568 in source, delta -0.0002
Zone plate
Frequency sweeps in every direction at once. This is the pattern that exposes aliasing.
SourceUndithered, 8 bits per pixel.
Bayer 8x8Ordered, periodic. Cheap and stateless, and the cross-hatch is visible at every level.mean 0.5005 against 0.5007 in source, delta -0.0002
White noiseThe control. Same construction as blue noise with the spectral shaping removed, which is what makes it grainy.mean 0.5013 against 0.5007 in source, delta +0.0006
Blue noise 64x64Void-and-cluster. Stateless like Bayer, aperiodic like noise, with low-frequency energy suppressed.mean 0.5008 against 0.5007 in source, delta +0.0001
Floyd-SteinbergThe default error diffusion. Note what it does to flat mid-grey.mean 0.5007 against 0.5007 in source, delta -0.0000
AtkinsonDiscards a quarter of the error, which expands contrast and crushes the extremes.mean 0.5003 against 0.5007 in source, delta -0.0004
Jarvis-Judice-NinkeA wider kernel, so smoother gradients and more smear.mean 0.5007 against 0.5007 in source, delta -0.0000
StuckiWider again, tuned for sharper edges than Jarvis.mean 0.5007 against 0.5007 in source, delta -0.0000
RiemersmaError diffused along a Hilbert curve instead of a raster scan.mean 0.5007 against 0.5007 in source, delta -0.0000
Halftone, 45 degreesA rotated screen with a round spot, as used in print.mean 0.5002 against 0.5007 in source, delta -0.0005
Flat mid-grey
The degenerate case. Floyd-Steinberg collapses to a rigid checkerboard here and Riemersma does not.
SourceUndithered, 8 bits per pixel.
Bayer 8x8Ordered, periodic. Cheap and stateless, and the cross-hatch is visible at every level.mean 0.5000 against 0.5000 in source, delta +0.0000
White noiseThe control. Same construction as blue noise with the spectral shaping removed, which is what makes it grainy.mean 0.5000 against 0.5000 in source, delta +0.0000
Blue noise 64x64Void-and-cluster. Stateless like Bayer, aperiodic like noise, with low-frequency energy suppressed.mean 0.5000 against 0.5000 in source, delta +0.0000
Floyd-SteinbergThe default error diffusion. Note what it does to flat mid-grey.mean 0.5000 against 0.5000 in source, delta +0.0000
AtkinsonDiscards a quarter of the error, which expands contrast and crushes the extremes.mean 0.5000 against 0.5000 in source, delta +0.0000
Jarvis-Judice-NinkeA wider kernel, so smoother gradients and more smear.mean 0.5000 against 0.5000 in source, delta +0.0000
StuckiWider again, tuned for sharper edges than Jarvis.mean 0.5000 against 0.5000 in source, delta +0.0000
RiemersmaError diffused along a Hilbert curve instead of a raster scan.mean 0.5000 against 0.5000 in source, delta +0.0000
Halftone, 45 degreesA rotated screen with a round spot, as used in print.mean 0.5006 against 0.5000 in source, delta +0.0006