Andrey Ovcharov

Professional Software Engineer and hobbyist Hardware enthusiast

Andrey Ovcharov

Simple image dithering algorithm

A few days ago I have taken part in the discussion on Reddit about displaying JPEG image on black and white e-ink screens attached to the ESP8266 MCU. The original poster asked how to use external service to convert RGB image into dithered black and white one.

In fact, there is no need to use external service for this transformation and MCU is definitely capable to do it. The algorithm is very simple and straightforward. Let’s say we have some libraries to decode JPEG image. We’ll need it either way as the display requires decoded bitmap to display. The library Bodmer/JPEGDecoder does this job pretty well and we can use it.

Then every pixel should be converted from RGB to Grayscale color space. It can be done with simple conversion:

grey = (0.3 * R) + (0.59 * G) + (0.11 * B)

Grayscale pixel has value in range 0 to 255 and we need to convert it into one bit black and white. Let’s think of grey value as the probability of resulting pixel to be either black or white. Obviously dark grey pixels will most likely become black and less likely white. A similar idea applies to the light ones.

Simple random number generator can help us to implement this behavior:

black_white = random(255) < grey ? 255 : 0

That’s it. Just two lines of code. No need to use external services. The full python script demonstrating this algorithm:

import random
from PIL import Image

im = Image.open('test.jpg')

im_gray = Image.new('RGB', im.size)
im_bw = Image.new('RGB', im.size)

width, height = im.size
for x in range(width):
    for y in range(height):
        R, G, B = im.getpixel((x, y))

        gray = int((R * 30 + G * 59 + B * 11) / 100)
        bw = 255 if random.randrange(255) < gray else 0
        
        im_gray.putpixel((x, y), (gray, gray, gray))
        im_bw.putpixel((x, y), (bw, bw, bw))

im_gray.save('gray.jpg')
im_bw.save('bw.jpg')

And the sample image:

Photo by Radu Florin on Unsplash

It’s not perfect but the result can be easily improved by adding two threshold values for black, white and dithered grey pixel values.