
Understanding how dithering works, visually.
Understanding how dithering works, visually.
tap/click the right side of the screen to go forward →
I’ve always been fascinated by the dithering effect. It has a unique charm that I find so appealing.
← tap/click the left side to go back
I was even more amazed when I learned how dithering works.
← or use arrow keys to navigate →
Look closely, and you’ll see this animation is made of alternating black and white pixels.
But these black and white pixels are specifically arranged to create the illusion of multiple shades.
That’s what dithering does: it simulates more color variations than what are actually used.
Here, it uses black and white to give the impression of multiple gray shades.
To me, dithering is about creating the most out of what we have, and that's what amazes me the most!
It inspired me to learn more about it, and now I want to share what I’ve learned.
Please note that this is just part one out of three, so I’ll only scratch the surface here.
I’ll go deeper in the next parts, which will come soon. Stay tuned!
First, let’s explore the dithering basics with this grayscale image example.
A grayscale image has various gray shades, from black to white.
Imagine a display that only shows black or white pixels, no grays. We must turn some pixels black and others white—but how?
One way is to map each pixel to the closest available color.
Pixels darker than medium gray turn black and lighter ones turn white.
This splits pixels into black or white groups.
However, this creates a harsh image with abrupt black-white transitions.
Shadow details vanish as gray pixels become fully black or white.
Dithering fixes this by selectively pushing some pixels towards the opposite color.
Some light gray pixels that are closer to white turn black.
Likewise, some dark grays turn white.
And it's done in a way that produces special patterns which simulate shades by varying the black-and-white pixel densities.
Denser black pixels are used in darker areas, while denser white pixels are used in lighter ones.
Next question: How are these patterns generated?
One simple dithering method, known as ordered dithering, uses a threshold map.
A threshold map is a grid of values representing brightness levels, from 0 (darkest) to 1 (brightest).
To dither, we compare each input pixel’s brightness to a corresponding threshold value.
If a pixel’s brightness exceeds the threshold (it’s brighter than the threshold), the pixel turns white. Otherwise, it turns black.
Repeating this for all pixels gives us the black-and-white dither patterns.
The threshold map is designed to output patterns where the black-and-white pixel density matches the input image’s shades.
So brighter input produces patterns with more white, while darker input produces more black.
These black-and-white density variations are what create the illusion of gray shades when viewed from a distance.
To dither larger images, we extend the threshold map to match the image size and follow the same principle:
Compare each pixel’s brightness to the threshold map, then turn it black or white accordingly.
The image now uses only two colors, but its overall appearance is preserved.
The variations in shades are now replaced by variations in black/white pixel density of the dithering patterns.
And that’s how dithering works in a nutshell: it replicates shades with fewer colors, which are strategically placed to maintain the original look.
I find it a bit ironic how I used to think dithering ‘adds’ a cool effect, when what it actually does is ‘remove’ colors!
That's all for now! We’ve reached the end, but there’s still a lot more to explore.
For example, we haven’t explored the algorithm to create a threshold map. (spoiler: there are many ways!)
There’s also another algorithm called error diffusion, which doesn’t use a threshold map.
Each algorithm creates a distinct, unique look, which I believe deserves its own article.
And that's why I decided to break this series into three parts.
In the next part, I’ll dive into various algorithms for creating threshold maps.
In the final part, I’ll focus on the error diffusion algorithm.
We'll dive even deeper into dithering's mechanisms in these next 2 parts, so stay tuned!
Thank you for reading!
visualrambling.space is a personal project by Damar, someone who loves to learn about different topics and rambling about them visually.
If you like this kind of visual article, please consider following me on X/Twitter and sharing this with your friends.
I'll keep creating more visual articles like this!
https://x.com/damarberlari
_blank
This is halftone (i.e., an apparent palette with more colors than the actual palette, by ensuring that you aren't just rounding the same way everywhere) but it isn't dithering in my opinion. To me, dithering means fading away the banding that occurs when the palette (or the apparent palette achieved via halftone) isn't large enough to avoid banding on its own.
The halftone technique demonstrated here takes a palette of 2 colors and increases it to something on the order of 20 apparent colors, but even with 20 there are extremely obvious bands.
That banding can be virtually eliminated by having way more colors (say, 256 if grayscale, 256^3 if RGB) or it can be virtually eliminated via dithering. I suspect the "error diffusion" technique (which is teased at the end of this demo) does what I'm talking about.
Noise is the key to dithering, and I don't see any noise in this demo. Everything is deterministic.
But the presentation is spectacular!
Error-diffusion dithering or dithering with a precomputed blue-noise or white-noise pattern are also deterministic.
One standard point of view is that what introduces noise is quantization, in the sense that the quantized image has an unwanted difference from the desired image, and dithering consists of techniques to shape that noise. The Bayer-matrix ordered dithering algorithm presented here concentrates the noise at high frequencies where the eye is less sensitive to it, but it still retains some noise at lower frequencies, which is the banding you're commenting on.
Dave Long had the sharp observation earlier today that Bresenham-style line drawing is a form of dithering, where the signal being represented is the position of the pen rather than the brightness of the visual field. (I see that dreamcompiler made the same observation two days ago: https://news.ycombinator.com/item?id=45728962 but I don't know if Dave saw it there)
We had a good discussion of dithering here a couple of days ago, connected with a significantly more comprehensive article with a less fancy presentation: https://news.ycombinator.com/item?id=45728231
> Dave Long had the sharp observation earlier today that Bresenham-style line drawing is a form of dithering, where the signal being represented is the position of the pen rather than the brightness of the visual field.
Bresenham is the equivalent of a sharp quantization, with no dithering. Using true 1D dithering for line drawing would instead result in a "wobbly" almost hand-drawn output.
You're still thinking about dithering being about colors. It's about finding the best member of set B to stand in for a member of set A when |A|>|B|.
In color dithering A is the set of colors in the original image and B is a smaller set of colors. Often just pure black and pure white, but that doesn't have to be the case.
In Bresenham A is the relative x,y coordinates of the ideal next pixel in the line if the pixels were infinitely small (thus |A| = infinity), while B contains the relative x,y coordinates of the 3 pixels that are actually available: +1,0; 0,+1; and +1,+1 (with appropriate rotation for the other 3 quadrants).
An important feature of Bresenham's is that the error inherent in this assignment is carried forth into the decision made for the next pixel, such that the total error is diffused along the line and its average value stays close to zero. Such error diffusion is also a feature of the best color dithering algorithms, but not the one described in TFA -- ordered dithering -- because ordered dithering is not a very good algorithm and is not used much today except when its peculiar artifacts are desired.
And yes, Bresenham's original algorithm does set each pixel it chooses to pure black, but this has nothing to do with its error diffusion mechanism. Bresenham's with grayscale is also a thing and it results in even better line approximations, but it's usually referred to as antialiased Bresenham.
I think zozbot was talking about the position and not the color, and they are correct that the positions of the pixels that get drawn in Bresenham are the nearest neighbors of the ideal positions, rather than having any sort of dithering applied to them. (I'm reluctant to call "nearest neighbor" a sort of dithering.) What's getting dithered is the slope, not the position. I was wrong about that.
It's error-diffusion dithering. You maintain an error accumulator from one pixel to the next, which keeps track of your departure from the desired slope, and you make a diagonal rather than paraxial move when it overflows. (Or is that only DDA?) But I guess you're right that the position isn't being dithered. It's the slope.
I'm tempted to try the wobbly algorithm now to see what it looks like!
The cool thing about Bresenham is that you're constantly picking the pixel closest to the desired slope but you never have to do the explicit division of deltaY/deltaX. The division happens implicitly as you draw the line, using only addition and subtraction of integers. Which was quite handy for the primitive computers circa 1960 when Jack Bresenham invented it.
You mean the desired position. If you were always picking the pixel closest to the desired slope, your lines would all be at multiples of 45°.
I agree that the lack of division is surprising and beneficial.
Another dithering discussion two weeks later: https://news.ycombinator.com/item?id=45814939
You made me curious. It looks like dithering is still an accepted name for this kind of technique: https://en.wikipedia.org/wiki/Ordered_dithering
Glad I snuck in that it's just my opinion! But the article you linked to sort of admits what I'm saying:
> The above thresholding matrix approach describes the Bayer family of ordered dithering algorithms. A number of other algorithms are also known; they generally involve changes in the threshold matrix, equivalent to the "noise" in general descriptions of dithering.
Basically, I'm leaning into "general descriptions of dithering" with my noise requirement, and the lack of noise in "ordered dithering" leads me to consider it not-quite-dithering.
The very first sentence of the general Dithering article [0] connects with my perspective as well:
> preventing large-scale patterns such as color banding
Aside: I probably misspoke with the word "halftone" earlier; apparently that's a specific thing as opposed to an umbrella term. I'm not sure there's a great word (other than "dither"...) for techniques to trade resolution for color.
Dithering is the right term. It was called this even as far back as the Win 3.1 era where program installers typically showed you a full screen window with a background gradient that went from dark blue to black and used ordered dithering to make it look semi-respectable.
The threshold map of ordered dithering is still a source of noise, it just happens to be carefully pre-baked so that (original image + noise)==output makes the output more legible than what you'd get from just mapping the original image pixels to the nearest available color.
The error diffusion is static and baked into the thresholds chosen, but it's still there and choosing the error diffusion properly still matters to getting a reasonable output.
I think that text is somewhat misleading because it leads people to believe that ordered dithering is not a kind of dithering, and also because the noise in general descriptions of dithering is not equivalent to changes in the threshold matrix. Rather, it is equivalent to differences between the input image and the output image. So I've fixed it.
I'm a pixel artist and everyone I know who uses this kind of technique calls it dithering.
Dithering as a mechanism to reduce/remove banding can be very impressive.
The color Next machines only had 12 bit displays, 4 bits per channel, but with careful use of dithering it was often indistinguishable from a 24-bit display (so called "true color")
> Dithering means fading away the banding that occurs when the palette (or the apparent palette achieved via halftone) isn't large enough to avoid banding on its own.
Here[0] is a good summary of dithering/noise to reduce color banding. Interestingly, for my game[1] I found that Valve's animated dithering technique (which uses variation over time in lieu of a fixed noise pattern) worked best, as the other techniques described there altered the perceived lightness of the resulting image.
[0]: https://blog.frost.kiwi/GLSL-noise-and-radial-gradient/
[1]: A multiplayer Noita-like (more at https://slowrush.dev), where the radiance-cascades-powered lighting tends to cause obvious banding patterns on really dark backgrounds.
It's called ordered dithering.
Two videos from Daniel Shiffman's Coding Train:
Turning Images into Dots: The Magic of Dithering https://www.youtube.com/watch?v=0L2n8Tg2FwI
Coding Challenge 181: Weighted Voronoi Stippling https://www.youtube.com/watch?v=Bxdt6T_1qgc
Very cool way to visualize it but I will be honest the threshold map doesn’t make sense. This didn’t seem to explain how to form the map, how to choose the threshold values, and so on. It showed grey pixels passing through white, black, and grey pixels and moved onto generalizing this pattern.
Is this just me being dumb or the curse of knowledge where something is so obvious to the author that they don’t even bother explaining it?
How to build the ordered threshold map isn’t obvious, there are some very clever ideas & techniques.
That said, it might help to keep the ‘threshold’ part in mind. The grey pixels turn into black & white via thresholding, i.e., dithered_color = (raw_color > threshold_color) ? white : black; It might have helped if at the start the point was made that using a threshold map made of solid middle grey results in the un-dithered image.
You can use a random number for the threshold, i.e., dithered_color = (raw_color > random()) ? white: black; For either random threshold or a threshold map to approximate the original gray tone, the average threshold value needs to be 0.5. That’s a big clue in how you might start making your own ordered threshold map!
The next step is thinking a little about which pixels turn white as the gray value moves slowly from black to white. It’s best if each time a dithered pixel turns white, it’s not right next to the last one that turned white; you want there to be separate between white pixels when only some of them are white. 50% gray should be a checker board, for example, maybe as opposed to an 8x8 region where the left half is black and right half is white. 25% grey should perhaps be 1 white pixel in the top left corner of every 2x2 region.
There are a few different ways to achieve those goals, with slightly different tradeoffs, but those are the main things to consider. You might have a lot of fun designing your own threshold map before reading about how others do it. This is insanely easy to experiment with on ShaderToy…
They say that part 2 will discuss how that’s formed, and 3 will discuss error diffusion dithering.
An incredibly beautiful visualisation but I felt the same. As well as being confused by the threshold map, at first the text seems to suggest that the 'binary' image is the input to the dithering algorithm in order to 'flip' some of the whites to black and vice versa but then it uses a gray area as input to the threshold map.
On the next episode of dragon ball Z...