Gratitude Day 23: Dithering (Floyd and Steinberg, take a bow!)

Wed 23 September 2026

Background / Basic Concepts

Another entry in the "Things that old folks care about but young folks don't know much about" department, let's talk about dithering!

Dithering was how you got your 8/16-bit computers to look like they had more colors (or shades of gray) onscreen than they could really produce. It was even used in chiptunes, to make a single-voice PC speaker sound like it actually had four or more voices by quickly switching between the different parts of the tune, sort of like PWM, if you're familiar with that.

On a high-level, "dithering" just means quickly jumping back and forth between discrete values, giving the impression of a values in-between the two. For example, if you wanted to produce a graphic sort of like this:

________░░░░░░░░▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓████████

but the only values you have at your disposal are "_" and "█," then you could do something like this:

________█_______█___█___█_█_█_█_████████

Comparing the two, you can see the vague similarity:

________░░░░░░░░▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓████████

________█_______█___█___█_█_█_█_████████

That's the basic idea.


Thresholding

The most conceptually simple dither isn't a dither at all, but a 50% threshold. In high-level pseudocode it looks something like this:

for pixel in image
do
    if pixel.to_grayscale() >= 128
    then
        newpixel=255
    else
        newpixel=0
    fi
    newimage.append(newpixel)

If you remember what Xeroxes (photocopies) of photos looked like before the advent of digital copiers, the end result will look familiar to you, and it's not great. 😄

Image of Michaelangelo's David in grayscale, sourced from Wikipedia right arrow Image of Michaelangelo's David in threshold mono, sourced from Wikipedia


Randomness dither

In my opinion, the next most conceptually simple method of dithering is the random or randomness dither. In pseudocode, it looks like this:

for pixel in image
do
    if pixel.to_grayscale() >= random(0,255)
    then
        newpixel=255
    else
        newpixel=0
    fi
    newimage.append(newpixel)

It yields an image that looks a little more like the original, but is kind of noisy:

Image of Michaelangelo's David in grayscale, sourced from Wikipedia right arrow Image of Michaelangelo's David in random dither mono, sourced from Wikipedia

However, the randomness dither does somewhat emulate the way that "the real world" (a.k.a. quantum physics?) accomplishes dithering, so if used in a quickly-moving animation, it almost looks plausible:

Image of Michaelangelo's David sourced from Wikipedia, converted to a random-dithered animation with 10 frames


Ordered / Bayer dither

This is probably the dithering algorithm that people are most familiar with, as it's still used in "retro" graphics and games today. It is a nice middle ground between algorithms, because it is most recognizable as a dither, but it also somewhat effective at representing the source image.

Now the matrix math involved in the Bayer algorithm quickly rises above my pay grade, but a very simple 2x2 bayer matrix algorithm would look something like this:

for x in ( 0 to image.size_x() / 2, step 2 )
do
    for y in ( 0 to image.size_y() / 2, step 2 )
    do
        if image.pixel(x,y).to_grayscale() >= 64
            then newimage.assign(x,y)=255
                else newimage.assign(x,y)=0
        fi  
        if image.pixel(x+1,y).to_grayscale() >= 64
            then newimage.assign(x+1,y)=255
                else newimage.assign(x+1,y)=0
        fi  
        if image.pixel(x,y+1).to_grayscale() >= 64
            then newimage.assign(x,y+1)=255
                else newimage.assign(x,y+1)=0
        fi  
        if image.pixel(x+1,y+1).to_grayscale() >= 64
            then newimage.assign(x+1,y+1)=255
                else newimage.assign(x+1,y+1)=0
        fi  
    done
done

Image of Michaelangelo's David in grayscale, sourced from Wikipedia right arrow Image of Michaelangelo's David in bayer/ordered dither mono, sourced from Wikipedia

(Note that the example image is using an 4x4 bayer dither, not the simple 2x2 algorithm I described in the pseudocode)


Floyd-Steinberg

Floyd-Steinberg and its derivatives is the one I remember seeing the most in the early 90s, because it really does an amazing job of preserving both detail and faking shades of gray (or colors) that your system wasn't able to display. I don't see it too much anymore because it's honestly a little too good for most purposes people use dithering for today (an aesthetic retro vibe).

I won't try to explain the algorithm, because I honestly don't remember it well, but I do recall going through Wikipedia's pseudocode for it some years back and producing my own FS dithering program in Processing, so it can't be that hard. 😄

Image of Michaelangelo's David in grayscale, sourced from Wikipedia right arrow Image of Michaelangelo's David in Floyd-Steinberg dither mono, sourced from Wikipedia

(Attribution: the above images were source from the Wikipedia articles on Dithering and Floyd-Steinberg linked in the body of this post, with the exception of the random dither animation, which was generated from the Wikipedia grayscale original using didder and ffmpeg)

Back to index

 

Category: Tech
Tags: Computing   Gaming   Monthly themes   Non-religious post   Retrocomputing