6 June 2026

Dithering is obsolete (except when it isn’t); creating OpenDithering

Vibecoding my way to the best looking e-paper art

By In Gear, Make, Photography 15 min

Dithering is one of those techniques that feels like it belongs in a museum. It peaked in the ’90s, the era of 256-color palettes and early computer graphics, when you needed to trick the eye into seeing colors that your hardware couldn’t actually produce. Now, even our phones have OLED displays that do HDR, and dithering has become the domain of pixel artists and retro enthusiasts, people who want their images to look like they came from an era of limited hardware. Charming, but firmly in the past.

And then I got into e-paper and discovered it was relevant again.

A screenshot of the Secret of Monkey Island game, where Guybrush is walking in the harbor past a sunet. The dithering makes us believe in this sunset with just 16 colors
Dithering in the Secret of Monkey Island made us believe this was a sunset with just 16 colors

E-paper displays have extremely limited color palettes, even more limited than our ’90s games had. A black-and-white display has, well, either black or white ink. Color e-paper panels might have six colors. The best panel I have only goes up to 16 shades of gray. That’s it. If you want to display a photograph on one of these things, you need to reduce your entire image to a handful of specific ink colors, and still make it look good.

Dithering is how you do that. Instead of simply replacing each pixel with the nearest ink color, which produces hideous flat smears, dithering calculates the difference between what you wanted and what you got, then adjusts the neighboring pixels, slowly building up the image out of the colors you do have available. Done well, your eye blends the result into the illusion of far more tones than the palette actually offers. It’s like a modern reinvention of pointillism.

Seurat's Parade de Cirque painting
Seurat’s Parade de Cirque, a masterpiece of pointillism
Two close-ups of Seurat's Parade de Cirque, demonstrating his pointillism technique
A close-up of Seurat’s technique, relevant for us in ways he couldn’t imagine

There are plenty of traditional dithering tools available, but most of them are focused on producing retro pixel art, not converting images for e-paper displays, which explains why they suck at it. And I’m not the only one who finds them lacking. Alternatives have emerged from all over the e-paper community, bringing great improvements over these unfit tools. But nobody was doing it quite the way I wanted yet.

So during one long weekend, when it was too hot outside to do anything fun, I settled behind Claude Code and vibecoded OpenDithering into existence: a browser-based dithering tool packed with every feature I could want.

A screenshot of the OpenDithering app, showing a sidebar on the left with settings, a source image in the middle, and a dithered image on the right that is calibrated to the device
OpenDithering’s UI: a sidebar with settings, the source image, and the calibrated preview using just the 6 colors of Spectra6

The dual-palette problem

The main problem of every dithering tool that was not made for e-paper: they all treat the color palette values as truth, even though they’re not.

Imagine a color e-paper display has a ‘red’ ink. To show that ink, the display’s firmware expects you to send it a red, green, and blue value of [255, 0, 0]: a perfect red. But if you actually look at the device, the red ink in the display isn’t the color [255, 0, 0]. It might be closer to [180, 30, 25], a deep, slightly brownish red. If you dither using the perfect reference values, you’re calculating against a color that doesn’t actually exist on the physical display. The math is wrong from the start.

This is where the e-paper dithering tools diverge from traditional tools: they solve this with a dual-palette system. Every color in the palette carries two values: a measured value (what the color actually looks like on real hardware) and an ideal value (what the firmware expects). Dithering runs against the measured values, so the image matches what your eye will actually see. When it’s time to export, the tool swaps the palette out, replacing every measured pixel with its ideal counterpart. The preview shows you real colors; the export file gives the display what it needs. In the UI you can toggle between the calibrated and the actual (ideal palette) output, the difference will certainly surprise you.

The calibration data for these measured values comes from multiple community sources, often made by taking photographs of actual hardware. If there’s no actual measured values, we make do with estimations based on the panel characteristics. I’ve added all the ones I could find and organized them as named variants you can select. Different people have measured the same panels and gotten different results; having them as distinct options feels like the most flexible approach. EPDOptimize was the first tool I encountered that solved this dual-palette problem, and I’ve included their palettes as options.

An overview of different measured Spectra6 palettes, compared to the ideal palette. In each column we see a different measured palette, for aitjcize, Wenting, OpenDisplay, EPDOptimize, and EPDOptimize (Legacy). In each row, we see a different color, for black, white, green, blue, red, and yellow.
Comparison of all included Spectra6 measured palettes to their ideal colors

Preprocessing: making the image dither-ready

Using a dual palette is not the only difference with traditional dithering tools. To produce the best results, the image goes through a preprocessing pipeline that matters much more for e-paper than it did for normal screens.

The first step is dynamic range compression. Like explained, e-paper displays don’t have the full dynamic range a computer screen does, because the ‘black’ ink on a panel isn’t [0, 0, 0], and the ‘white’ isn’t [255, 255, 255]. They’re somewhere in the middle, and that gap varies by panel and by ink. If you feed a full-range image into a display that can only reproduce part of that range, everything that is too bright or too dark becomes flat and washed out. Highlights become smears of white, shadows become smears of black. Dynamic range compression instead remaps the image’s bright and dark areas so they fit into the display’s actual range, using the measured black and white values from the palette. The image is constrained to what the display can physically show.

After that comes tone mapping. There are two modes: a simple contrast slider that scales tones around the midpoint, and a parametric S-curve. The S-curve is more interesting — you can independently control shadow lift, highlight compression, and the overall strength of the effect. These were borrowed from aitjcize’s esp32-photoframe project; the first tool I found with preprocessing built in. Their Balanced preset produces great results across a range of images without needing adjustment, and has been my benchmark for the quality I want OpenDithering to hit. I even included the settings of their Balanced preset in OpenDithering!

Running on automatic

There’s a lot of manual controls exposed now, but one of the trickier parts of preparing an image for e-paper is that dithering can shift the overall colors and brightness in ways that are hard to predict before you see the result. Fiddling with all the sliders manually to compensate takes a long time and still might not give you the best image on your display. In comparison, when I edit my photos in Lightroom, I know I can always hit the Auto button and get a decent result. So that’s what I asked Claude to build me.

A screenshot of the debug panel of OpenDithering's auto-tune tool, showing various statistics around the process of running auto-tone on an image
The debug panel of the auto-tune process, showing how the image was tuned in 4 steps

The Auto-tune button is deceptively simple: it looks at the dithered output, compares it to the source image, and nudges saturation and exposure toward values that close that gap. The loop runs up to eight times but stops as soon as an attempt fails to improve the output. In practice it gets to the sweet spot in three or four steps. The result isn’t a perfect reconstruction, it’s still e-paper, but it reliably gets the result closer to the original than I can.

Color space flexibility

Most dithering tools do everything in RGB. That was fine in the ’90s, but newer techniques have been developed because RGB is a poor approximation of how human vision actually works. The eye is much more sensitive to brightness differences than to hue, and RGB doesn’t work like that.

Color scientists developed perceptually uniform color spaces like CIELAB and OKLab (invented in 2020!) to deal with this. ‘Perceptually uniform’ basically means that they are designed to mimic our eyes, instead of what is simplest to express in numbers. For dithering, this matters a lot: when you’re deciding which palette color is ‘nearest’ for a given pixel, a perceptually uniform metric produces results that look correct to humans, not the computer.

A comparison of the colorspaces of RGB, CIELabe and OKLab.
The differences between colorspaces… the human eye doesn’t perceive color in the perfect geometric order our computers do

For this part, I was inspired by the work by Matt Carter’s e-paper ditherings tests using different combinations of CIELAB, OKLab, and RGB. You might want to try finding the nearest color in OKLab while diffusing the error in RGB; now you can. OpenDithering has five presets covering the practically useful combinations and shows you exactly which spaces are active, so there’s no guessing about what you’re actually running.

The algorithms nobody uses

Classic Floyd-Steinberg dithering is great, it’s been great since 1976. But there’s been research into other algorithms since then, and nobody seems to try them. The newest algorithm that usually gets implemented is Sierra, which dates back to 1989. I’ve added a few newer algorithms that I think could be relevant now, such as:

Eschbach & Knox (1991) is a tone-dependent algorithm that operates in Lab colorspace. The key difference is that it doesn’t work uniformly across all tones: midtones get the full treatment, while shadows and highlights are handled more conservatively. The result is smoother gradients in the tonal regions where other algorithms tend to leave artifacts.

Riemersma (1998) uses a Hilbert curve traversal, which is a complex way of saying that it processes pixels in a fractal pattern (yeah, still complex). This leads to a pattern that is somewhere between ordered and chaotic, which reduces the chance of getting artifacts like you can get with Floyd-Steinberg and similar algorithms.

Dizzy (2024) is the one I was really interested in. It was invented by Liam Appelbe, and it’s getting some attention in the e-paper world right now, which is how I found out about it. Instead of a fixed order, it processes pixels randomly, shuffling through the entire image differently every time you run it. The visual result is quite different from classic algorithms: more even, more organic, and entirely without the Floyd-Steinberg-ish artifacts. On a grayscale image, this random pattern looks like the grain you would get in analog photos; quite pleasing! This is my favorite algorithm for images I want to use on my E1003 black and white photo frame.

A black & white photo of the interior of Rundetårn in Copenhagen, dithered to 16-level grayscale using Dizzy. Yes, all of these smooth gradients across the floor and walls come from just 16 distinct shades of gray!

Of course, all the classics (Floyd-Steinberg, Atkinson, Sierra, and a few others) are in there as well, so you can compare them directly on the same image with the same palette.

Out-of-the-box OpenDisplay

You can download PNG or BMP files, but if you’ve got an OpenDisplay-compatible e-paper device, seeing the result is even easier. OpenDithering can push the dithered image straight to the display over Bluetooth from your browser; no app, no cable, no fuss. Hit the OpenDisplay button, pick your device from the browser’s Bluetooth picker, and it streams the image over. The display does a full refresh and you’re done. The connection stays live between uploads too, so iterating on settings and pushing again is just one click. The one catch is that it needs Chrome or Edge; Web Bluetooth is one of those features Firefox and Safari don’t support.

So far I have only tested with my color Spectra6 panels on Seeed EN04 boards, and it doesn’t support OpenDisplay’s encryption yet, so there’s still some ways to go. But I truly believe OpenDisplay is the future standard for e-paper in Home Assistant, so I’m excited to support it.

Two OpenDisplay e-paper art frames on a desk, with a row of books and a plant behind them. The color art frame on the left is in a wooden frame and shows a Home Assistant illustration made by Clelia Rella. The mono art frame on the right is showing a dashboard that shows current electricity usage.
Two of my OpenDisplay e-paper frames

Everything in the browser

When I started the project, I told Claude to make this as easy to use as possible. So it’s hosted on GitHub Pages, built directly from the repo I upload the code into. It runs entirely in the browser and your images never leave your machine. This also means there’s no backend to maintain, which suits a project I built purely as a side-project.

I’m really stoked about the results. In just one week, this browser tool has surpassed every workflow I’ve had for my e-paper art frames in the past two years. Two recommendations to get you started quickly:

  • For a 6-color Spectra6 display, start off on the Balanced preset and then hit the Auto-tune button to see where it gets you. There are definitely images where it has a hard time, especially those with large highlight areas, and you’ll have to go back to manual adjustments. But for the vast majority of my test images, Auto-tune produces better results than anythings I could achieve myself.
  • For a 16-level grayscale display, you want to use the Grayscale preset. It uses the Dizzy dithering algorithm, which results in a fine grain in your images that is barely perceptible on the display. With the 16 shades of gray Dizzy can work with here, the e-paper really can look like an actual black and white photograph. I never have to manually adjust these.

There’s a ton more advanced settings you can play with, but you shouldn’t have to!

Doing it for me

To me, this is a weird little experimentation platform, a sandbox where I can throw anything at the wall in the pursuit of prettier e-paper art frames (as you may have noticed, my particular special interest). Even better, in this sandbox I don’t have to worry about any external factors. I don’t have to think about the processing power of an ESP chip, whether an algorithm is fit for hardware acceleration, how much battery charge this calculation will use. If I want to have auto-tune run Dizzy dithers on millions of pixels 10 times in a row, I can. Nobody cares.

I’m not treating this as a stable software product, so you probably should not rely on OpenDithering for your production e-paper setup. There’s a good chance I’ll change features when you’re least expecting it. But I hope you get inspired to try some of these things I’m discovering and playing with!

As always, my vibecoding warning applies: I did not write this app, Claude Code did. If you don’t trust AI-written code, you should not use this. But I’m a marketer, not a developer, so this code is probably more trustworthy than anything I would have written myself anyway.

OpenDithering only exists because it’s been built on top of the work of others: the measured palettes of EPDOptimize, the preprocessing of aitjcize, the colorspace tests of Matt Carter, the Dizzy dithering by Liam Appelbe, the open protocol of OpenDisplay. And honestly, a ton of other features I copied from their projects, so I get to test all of them together. Because dithering might be old technology. But the problems it solves are still relevant, and the solutions have gotten a lot more interesting than most people realize!

1 Comment
  1. […] Update: I’ve changed the dithering guide to use my own dithering solution, OpenDithering. Try it, or read more about the creation of this tool. […]

    Reply

What do you think?