I want to create an image in PILLOW and show it on the display on a Raspberry Pi. Until now I have saved the image and have used omxiv to show the image. This is rather time consuming.
from PIL import Image
import os
img = Image.new('RGB', size=(150, 50), color=(0, 0, 255))
save img
....
os.system('omxiv img')
Is there a better and less time consuming way to do this, for example writing direct to the framebuffer?
I did some experiments with the framebuffer on my Raspberry Pi 4. Here is what I managed to work out...
You can get the screen resolution using the fbset
command like this:
fbset -fb /dev/fb0
Sample Output
mode "1280x1024"
geometry 1280 1024 1280 1024 32
timings 0 0 0 0 0 0 0
accel true
rgba 8/16,8/8,8/0,0/0
endmode
That tells me the screen is 1280 px wide and 1024 px high and I need to write 4 bytes per pixel in the order BGRA888.
So, I can do a quick test with ImageMagick to see if I can fill the screen, like this:
# Write to screen buffer - BGRA8888, width=1280, height=1024
convert -size 1280x1024 -depth 8 gradient:lime-magenta bgra:/dev/fb0
and it fills the screen with a lime-magenta gradient. Excellent!
So, having acquired that knowledge and a smattering of confidence, let's try Python...
#!/usr/bin/env python3
import numpy as np
# Map the screen as Numpy array
# N.B. Numpy stores in format HEIGHT then WIDTH, not WIDTH then HEIGHT!
# c is the number of channels, 4 because BGRA
h, w, c = 1024, 1280, 4
fb = np.memmap('/dev/fb0', dtype='uint8',mode='w+', shape=(h,w,c))
# Fill entire screen with blue - takes 29 ms on Raspi 4
fb[:] = [255,0,0,255]
# Fill top half with red - takes 15 ms on Raspi 4
fb[:h//2] = [0,0,255,255]
# Fill bottom right quarter with green - takes 7 ms on Raspi 4
fb[h//2:, w//2:] = [0,255,0,255]
I then tried displaying an image - Lena, of course. So, just for brevity and simplicity, I made Lena exactly the right size and added an alpha channel with ImageMagick:
convert lena.png -resize 1280x1024\! -alpha opaque png32:lena1280.png
Then carried on as follows in the Python session I started above:
from PIL import Image
# Load Lena image
im = Image.open('/home/pi/lena1280.png')
# Convert from PIL Image to Numpy array
n = np.array(im)
# Blit to screen - takes 30ms
fp[:] = n
Note that you would probably do better using OpenCV to load the image, with cv.imread(...,cv.IMREAD_UNCHANGED)
, because that will deliver you a Numpy array directly without needing conversion and the BGR ordering will already match that of the frame buffer.
Other useful commands - for my own reference!
# Retrieve EDID settings from monitor and write into a file called "edid"
tvservice -d edid
# Parse the file we just created to see what the attached monitor is capable of
edidparser edid
Sample Output
Enabling fuzzy format match...
Parsing edid...
HDMI:EDID version 1.3, 0 extensions, screen size 38x30 cm
HDMI:EDID features - videodef 0x80 standby suspend active off; colour encoding:RGB444|YCbCr444|YCbCr422; sRGB is default colourspace; preferred format is native; does not support GTF
HDMI:EDID found monitor S/N descriptor tag 0xff
HDMI:EDID found monitor name descriptor tag 0xfc
HDMI:EDID monitor name is DELL_1907FP
HDMI:EDID found monitor range descriptor tag 0xfd
HDMI:EDID monitor range offsets: V min=0, V max=0, H min=0, H max=0
HDMI:EDID monitor range: vertical is 56-76 Hz, horizontal is 30-81 kHz, max pixel clock is 140 MHz
HDMI:EDID monitor range does not support GTF
HDMI:EDID found preferred DMT detail timing format: 1280x1024p @ 60 Hz (35)
HDMI:EDID established timing I/II bytes are A5 4B 00
HDMI:EDID found DMT format: code 4, 640x480p @ 60 Hz in established timing I/II
HDMI:EDID found DMT format: code 6, 640x480p @ 75 Hz in established timing I/II
HDMI:EDID found DMT format: code 9, 800x600p @ 60 Hz in established timing I/II
HDMI:EDID found DMT format: code 11, 800x600p @ 75 Hz in established timing I/II
HDMI:EDID found DMT format: code 16, 1024x768p @ 60 Hz in established timing I/II
HDMI:EDID found DMT format: code 18, 1024x768p @ 75 Hz in established timing I/II
HDMI:EDID found DMT format: code 36, 1280x1024p @ 75 Hz in established timing I/II
HDMI:EDID standard timings block x 8: 0x714F 8180 0101 0101 0101 0101 0101 0101
HDMI:EDID found DMT format: code 21, 1152x864p @ 75 Hz (4:3) in standard timing 0
HDMI:EDID found DMT format: code 35, 1280x1024p @ 60 Hz (5:4) in standard timing 1
HDMI:EDID filtering formats with pixel clock unlimited MHz or h. blanking unlimited
HDMI:EDID best score mode initialised to DMT (4) 640x480p @ 60 Hz with pixel clock 25 MHz (score 0)
HDMI:EDID best score mode is now DMT (4) 640x480p @ 60 Hz with pixel clock 25 MHz (score 36864)
HDMI:EDID DMT mode (6) 640x480p @ 75 Hz with pixel clock 31 MHz has a score of 11520
HDMI:EDID best score mode is now DMT (9) 800x600p @ 60 Hz with pixel clock 40 MHz (score 57600)
HDMI:EDID DMT mode (11) 800x600p @ 75 Hz with pixel clock 49 MHz has a score of 18000
HDMI:EDID best score mode is now DMT (16) 1024x768p @ 60 Hz with pixel clock 65 MHz (score 94370)
HDMI:EDID DMT mode (18) 1024x768p @ 75 Hz with pixel clock 78 MHz has a score of 29491
HDMI:EDID DMT mode (21) 1152x864p @ 75 Hz with pixel clock 108 MHz has a score of 62324
HDMI:EDID best score mode is now DMT (35) 1280x1024p @ 60 Hz with pixel clock 108 MHz (score 5260929)
HDMI:EDID DMT mode (36) 1280x1024p @ 75 Hz with pixel clock 135 MHz has a score of 49152
HDMI0:EDID preferred mode remained as DMT (35) 1280x1024p @ 60 Hz with pixel clock 108 MHz
HDMI:EDID has only DVI support and no audio support
edidparser exited with code 0
You can turn off/disbale the text cursor in the console like this:
sudo sh -c "TERM=linux setterm -foreground black -clear all >/dev/tty0"
and re-enable it like this:
sudo sh -c "TERM=linux setterm -foreground white -clear all >/dev/tty0"
Keywords: Raspberry Pi, RasPi, framebuffer, fb0, /dev/fb0, Python, Numpy, ImageMagick, direct frame buffer access, edid, HDMI, DVI, monitor capabilities, features, tvservice, edidparser, resolution, bgra8888, blit, bit-blit, cursor