E-Ink Photo Frame - Part 1

VishnuVishnu
4 min read

TL;DR:

This post briefly looks at what is required to create an image file that can be displayed on the Waveshare 5.65inch ACeP 7-Color E-Paper E-Ink Display Module. It goes into the required format and links to a script that converts images into the correct format, which is available on Github.

Converting Images:

There are a couple of challenges with utilizing the Waveshare libraries. The image must be the exact size of the display and they must be in the right 7 colour format. Finally, it needs to be in bmp format. Otherwise, the code crashes or in the best case it won’t be displayed correctly on the platform.

Format Conversion:

Format conversion is pretty straightforward, you can utilize various tools to convert a file to bmp. The script that has been provided will do this automatically.

def toBMP(input):
    image = Image.open(input).convert('RGB')
    return image

Resizing:

For the “5.65inch ACeP 7-Color E-Paper E-Ink Display Module” which I’ve used the size is 600x448 pixels. This size is different on different screens so make sure that you check the size before beginning to resize the image.

The naïve way to resize an image is to crop it. However, if you crop a modern photo with millions of pixels, you’re going to end up with a tiny corner of an image. To solve this issue, we need to first scale the image down to have a length or height of 600px depending on whether you have a landscape of portrait image. Following that we need to crop the corresponding height or length to 448px.

Again we face an issue, we cannot just crop the height, it may be that after the resizing it may be less than the expected 448px and so we need to pad the image to make it the exact size.

Further to this we should not just crop or pad on the top or bottom, we need to divide it in two and apply it to both the top and bottom. This will minimise the total data loss in the image.

def cutToSize(image, outLength, outHeight):

    length, height = image.size

    if length < height:
        image = image.rotate(90, expand=1)

    length, height = image.size

    ratio = length/outLength
    tempHeight = height/ratio

    image = image.resize((outLength, math.floor(tempHeight)))

    difference = tempHeight - outHeight

    partVal = abs(difference)/2

    bot = math.ceil(partVal)
    top = math.floor(partVal)

    if difference > 0:
        image = image.crop((0, top, outLength, tempHeight-top))
        image = image.rotate(180, expand=1)

    elif difference < 0:
        tempImage = Image.new(image.mode, (outLength, outHeight), (255, 255, 255))
        tempImage.paste(image, (0, top))
        image = tempImage

    return image

Changing the Colors:

The next step is changing the colors. The E-ink display I used only supports 7 colors. The palette can be seen below. This is pretty awesome as for the longest time E-ink only supported black and white. However, it does mean we have to change the image’s colour palette from 256 colours (for BMP files) to 7.

The simplest approach is to find the closest matching color and apply it.

This would look strange, and we would lose a lot of detail. This raises the question, is there a better way?

Yes, there is, and this method is called dithering. Dithering is where randomized noise is introduced to reduce banding as we saw in the previous image. Banding is the solid bands of colours that you saw in the example above. Dithering also creates the illusion of colour depth. It does this by taking advantage of the human eye which perceives a collection of colours as a mixture of these colours. This allows 7 colours to create a much bigger colour space. This allows us to get high quality pictures on the screen without having to sacrifice to much detail in the images.

Applying this in code is not straightforward, the documentation is poor and the what I have built is from combining multiple stack overflow answers and various gists.

def palette7Convert(image):
    palettedata = [0x00, 0x00, 0x00,
                   0xff, 0xff, 0xff,
                   0x00, 0xff, 0x00,
                   0x00, 0x00, 0xff,
                   0xff, 0x00, 0x00,
                   0xff, 0xff, 0x00,
                   0xff, 0x80, 0x00,
                  ]


    for i in range(0, 249 * 3):
        palettedata.append(0)

    p_img = Image.new('P', (600, 448))
    p_img.putpalette(palettedata)

    image = image.quantize(palette=p_img, dither=1)

    return image

Conclusion**:**

Converting an image to be displayed on the 7 colour E ink display is more involved than originally expected. The script allows us to avoid the manual work of creating each image independently thereby saving us time and effort.

0
Subscribe to my newsletter

Read articles from Vishnu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vishnu
Vishnu

A science and tech enthusiast with opinions on a variety of topics.