This is an old revision of the document!


01. [40p] Reading the frame buffer

By now, we all know that not all files are backed by a storage device. While some reside on your HDD/SSD, Linux mounts virtual filesystems such as procfs or sysfs. Today we are going to look at a special file called /dev/fb0. This file is an abstraction of the frame buffer used by your video hardware to render your screen. In it, each pixel is represented as a RGBA (Red-Green-Blue-Alpha) value. Creating a copy of this file to persistent storage will effectively take a screenshot. Unfortunately, this screenshot will not be in any known format (e.g.: JPEG, PNG, etc.) A bit of work still needs to be done to properly format it such that the appropriate software (e.g.: gwenview) can interpret and display it.

In this first exercise, we are going to write a Python3 script that does just that: take the content of /dev/fb0 and output an image file in a format of your choosing. To that end, we are going to use the PIL module.

[10p] Task A - Setting up the environment

If you are working on Ubuntu, you may first need to install some prerequisites:

# install developer versions of compression / jpeg handling libraries
$ sudo apt update
$ sudo apt install zlib1g-dev libjpeg-dev

Next, we are going to set up a virtual environment for us to work in. If you need a reminder on what a venv actually is, refer to this lab.

# this will be our workspace directory
# NOTE: !$ is substituted with the last argument of your previous command
$ mkdir frame_buffer
$ cd !$
 
# create venv and activate it
$ python3 -m venv .venv
$ source .venv/bin/activate
 
# install pillow (an actively maintained fork of PIL)
$ pip3 install pillow

[20p] Task B - Writing the script

To start things off, we are going to use the following skeleton:

fb2img.py
#!.venv/bin/python3
 
import argparse         # argument parsing
import struct           # data unpacking
from PIL import Image   # image processing
 
def main():
    # parse cli arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('FILE', help='output image file')
    parser.add_argument('--src', help='data source',
                        default='/dev/fb0', metavar='/dev/fb*')
    parser.add_argument('--width', help='screen width [px]',
                        type=int, default=1920, metavar=' INT')
    parser.add_argument('--height', help='screen height [px]',
                        type=int, default=1080, metavar='INT')
    cfg = parser.parse_args()
 
    # TODO 1: read contents of cfg.src (the frame buffer)
 
    # TODO 2: split data in groups of 4 bytes
 
    # create a new PIL Image object
    img = Image.new('RGB', (cfg.width, cfg.height))
    px  = img.load()
 
    # set each pixel value
    for i in range(cfg.width):
        for j in range(cfg.height):
            # TODO 3: write each pixel value in px[i,j] as a RGB tuple
            # NOTE  : the four bytes in the groups that you split previously
            #         are in fact in BGRA format; we don't need the Alpha
            #         value but the other three bytes must be revered
            pass
 
    # save image do disk
    # NOTE: format will be determined from the file's extension
    img.save(cfg.FILE, None)
 
    # clean up PIL Image object
    img.close()
 
if __name__ == '__main__':
    main()

The script uses the argparse module to interpret some command-line arguments (the --help flag is implied). If you feel like adding something to these, you're free to do so. It's a good idea to make your script as interactive as possible. The parsed arguments (or their default values) are stored as members of the cfg object. If you're unsure how to access them, just print the whole object once to get a feel for what it contains.

For now, go through each TODO in the skeleton and fill in the blanks. Next are a few hints that might help you. Feel free to ask for help if you get stuck :)

TODO 1

This step is pretty straightforward. Remember that you are working with a binary file!

TODO 2

The result from the previous step will be a byte array. In order to split it into an array of 4-byte RGBA sub-arrays, you can use list comprehension (discussed in this lab. If you have other ideas, you're free to do your own thing.

TODO 3

While img is a PIL abstraction of the image you wish to create, individual pixels will be accessed via px, an instance of the PixelAccess class in PIL. Each pixel is represented as a tuple of three integers (i.e.: not bytes). To unpack each value from the byte-array corresponding to your pixel, you can use the struct module. Search in the documentation for the format character representing an unsigned char (equivalent to uint8_t).

Try using this binary file as source (–src) when testing your script. It's a copy of a 1920×1080 /dev/fb0 that actually contains relevant data.


If your script is rendering this correctly and your resolution is 1920×1080 but the image resulting from your /dev/fb0 is still messed up, note that it might have a different (usually smaller) resolution than your screen. Find out if that's the case:

$ cat /sys/class/graphics/fb0/virtual_size

Remember that you don't have to get the script right from your first try. If you're not familiar with a module, experiment with it a bit in a python shell. Or better yet, use ipython.

[10p] Task C - Testing the script

Finally, it's time to test the script. Notice how your script takes the --width and --height optional arguments (with values defaulting to 1920 and 1080). If you have another resolution but you're not sure what that is, it's not hard to find out:

$ xrandr --prop | grep primary
    eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 344mm x 194mm

Note that /dev/fb0 has restricted access permissions for non-owners, so you need to run your script with sudo:

$ ls -l /dev/fb0
    crw-rw---- 1 root video 29, 0 Apr 14 17:45 /dev/fb0
 
# grant execute permissions if you haven't already
$ chmod +x fb2img.py
$ sudo ./fb2img.py screenshot.png

Now let's open the screenshot with the default application:

$ xdg-open screenshot.png

The result may be a bit unexpected, depending on how your X display server is configured. We'll discuss that in the next exercise :)

ii/labs/s2/02/tasks/01.1679081300.txt.gz · Last modified: 2023/03/17 21:28 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0