pythonraspberry-pipython-imaging-libraryframebuffer

How to show an image direct from memory on RPI?


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?


Solution

  • 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