More than a year ago, I bought this 7 color e-ink display, tested it and somehow forgot about it again. Then just recently, I remembered about it and decided to make it into a digital picture frame that would display our family photos on my working desk.
I also have a stash of unused Raspberry Pis lying around, so I put one of those into use and connected it to the display.
An unused picture frame was also readily available, so it was mostly a matter of putting the existing pieces together.
The backboard of the frame needed a cut-out to make room for the Raspberry Pi Zero, but that was all the "modifications" I had to make. Even the passe-partout matched the dimensions of the screen already.
Here's what I used.
- Raspberry Pi Zero: Actually any Raspberry Pi will work. The Zero is great however, as it does not take up a lot of space.
- Pimoroni ePaper HAT: This is the exact model that I used.
- Power supply: Micro USB, 5V, 2A.
- Picture Frame: e.g. IKEA Ribba
In the past I blogged about setting up a Raspberry Pi for headless operation.
Since then, things have become even easier and all these settings (e.g.
just a click away when using the Raspberry Pi Imager tool.
The actual actual code driving the display is a small Python application using Pimoroni's inky library.
A global instance of
inky is used for that.
inky = auto(ask_user=True, verbose=True)
The main application then simply calls the helper function
display_next_image and sleeps for
This means, each 15 minutes we update the image that gets displayed.
while True: display_next_image() time.sleep(timedelta(minutes=15).total_seconds())
The implementation of
display_next_image looks like this:
def display_next_image(): image = get_next_image() image = rotate_image(image) image = scale_image(image) inky.set_image(image, saturation=0.5) inky.show()
The first function that's called is
Here's where your mileage may vary and you have to decide where to retrieve
the image from. Whether this image is already on a folder on the Raspberry Pi's SD
card or gets downloaded from somewhere else, does not actually matter.
The only important thing is that the
Image type from Python's Pillow
library is used. The methods below rely on that.
rotate_image makes sure that the image is rotated correctly (by looking at its
def rotate_image(image): # Check if the image has EXIF data if hasattr(image, '_getexif'): exif = image._getexif() if exif is not None: # Look for the EXIF orientation tag for tag, orientation in ExifTags.TAGS.items(): if orientation == 'Orientation': break # Check if the orientation tag exists in the EXIF data if tag in exif: # Get the actual orientation value orientation_value = exif[tag] # Determine if rotation is needed based on the orientation value if orientation_value == 1: # No rotation needed (normal orientation) pass elif orientation_value == 3: # Rotate 180 degrees image = image.rotate(180, expand=True) elif orientation_value == 6: # Rotate 270 degrees image = image.rotate(270, expand=True) elif orientation_value == 8: # Rotate 90 degrees image = image.rotate(90, expand=True) return image
Then, the image is scaled to match the dimensions of the e-paper display.
def scale_image(image): aspect_ratio = image.width / image.height target_width, target_height = inky.resolution if aspect_ratio > (target_width / target_height): new_width = target_width new_height = int(target_width / aspect_ratio) else: new_height = target_height new_width = int(target_height * aspect_ratio) image = image.resize((new_width, new_height), Image.ANTIALIAS) padded_image = Image.new('RGB', (target_width, target_height), (255, 255, 255)) left_padding = (target_width - new_width) // 2 top_padding = (target_height - new_height) // 2 padded_image.paste(image, (left_padding, top_padding)) return padded_image;
And finally, the
Image gets displayed by calling
Automatically run the script
To have this script run on startup automatically, I added one line to
... python3 /home/pi/dev/framepi/main.py & exit 0
The Finished Picture Frame
I must say I am really happy with the result. This picture frame is running flawlessly for weeks now and the display quality is much better than I had expected.
The update interval of 15 minutes also turned out to be a good choice for me. It's often enough to see some variation but not too often as the update animations would be a distraction.