Andrey Ovcharov

Tech blog

Andrey Ovcharov

Image dithering

A few days ago I have taken part in the discussion on reddit about displaying JPEG image on black and white e-ink screen attached to the ESP8266 MCU. 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. Algorithm is very simple and straightforward. Let’s say we have some library 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 probability of resulting pixel to be either black or white. Obviously dark grey pixels will most likely become black and less likely white. The similar idea applies to the light ones.

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

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.