This shows you the differences between two versions of the page.
ii:labs:s2:02:tasks:01 [2022/04/15 15:18] radu.mantu |
ii:labs:s2:02:tasks:01 [2024/03/17 18:24] (current) florin.stancu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ==== 01. [40p] Reading the frame buffer ==== | + | ==== 01. [30p] Minor Design Changes ==== |
- | 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 [[https://www.kernel.org/doc/html/latest/filesystems/proc.html|procfs]] or [[https://www.kernel.org/doc/html/latest/filesystems/sysfs.html|sysfs]]. Today we are going to look at a special file called **/dev/fb0**. This file is an abstraction of the [[https://www.kernel.org/doc/html/latest/fb/framebuffer.html|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. | + | Our customer wants to make some changes to the website's design: |
- | 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 [[https://pillow.readthedocs.io/en/stable/index.html|PIL module]]. | + | * Add a header image with our logo at the top |
- | + | * Several candidates are present inside ''public/images/''; | |
- | === [10p] Task A - Setting up the environment === | + | * You could either use the [[https://developer.mozilla.org/en-US/docs/Web/CSS/background-image|CSS background property]], or just the old-school ''<img>'' tag; |
- | + | * **Hint:** check out ''style.css'' for existing definitions! | |
- | First of all, 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 [[https://ocw.cs.pub.ro/courses/ii/labs/03/tasks/01|this lab]]. | + | * Also change the background color of the ''<header>'' to match the chosen image on its margins (maybe something blue?); |
- | + | * Make the content box have rounded borders (try ''15px''); | |
- | If you are on Ubuntu, you must first install some prerequisites: | + | * ''border-radius'' *wink* |
- | <code bash> | + | * Use the browser's Web Developer tools to find the HTML tag to select! |
- | $ sudo apt update | + | * Insert some dummy content text (e.g., use a [[https://loremipsum.io/|Lorem Ipsum generator]]); |
- | $ sudo apt install libzlib1g-dev libjpeg-dev | + | |
- | </code> | + | |
- | + | ||
- | <code bash> | + | |
- | # 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 | + | |
- | </code> | + | |
- | + | ||
- | === [20p] Task B - Writing the script === | + | |
- | + | ||
- | To start things off, we are going to use the following skeleton: | + | |
- | + | ||
- | <file python 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() | + | |
- | </file> | + | |
- | + | ||
- | The script uses the [[https://docs.python.org/3/library/argparse.html|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 [[https://ocw.cs.pub.ro/courses/ii/labs/02|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 [[https://pillow.readthedocs.io/en/stable/reference/PixelAccess.html|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 [[https://docs.python.org/3/library/struct.html|struct module]]. Search in the documentation for the format character representing an ''unsigned char'' (equivalent to ''uint8_t''). | + | |
<note tip> | <note tip> | ||
- | Try using {{:ii:labs:s2:02:tasks:sample.zip|this binary file}} as source (''--src'') when testing your script. It's a copy of a 1920x1080 /dev/fb0 that actually contains relevant data. | + | Hint: search for TODOs inside HTML and CSS! |
</note> | </note> | ||
- | 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**. | + | <note important> |
+ | For specifying URLs to other web resources (e.g., images for this task): recall Unix relative paths (''./path/to/file.jpg'')? | ||
+ | HTML also uses them (those URLs will be relative to your current file -- either the ''.html'' or the ''.css'')! | ||
- | === [10p] Task C - Testing the script === | + | In some cases (webpage has sub-paths, e.g., ''/account/details.html''), you may also use absolute URLs (path begins with a ''/'' representing the server's root directory). |
- | + | </note> | |
- | 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: | + | |
- | + | ||
- | <code bash> | + | |
- | $ xrandr --prop | grep primary | + | |
- | eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 344mm x 194mm | + | |
- | </code> | + | |
- | + | ||
- | Note that /dev/fb0 has restricted access permissions for non-owners, so you need to run your script with **sudo**: | + | |
- | + | ||
- | <code bash> | + | |
- | $ 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 | + | |
- | </code> | + | |
- | + | ||
- | Now let's open the screenshot with the default application: | + | |
- | + | ||
- | <code bash> | + | |
- | $ xdg-open screenshot.png | + | |
- | </code> | + | |
- | + | ||
- | The result may be a bit unexpected, depending on how your [[https://www.x.org/wiki/|X display server]] is configured. We'll discuss that in the next exercise :) | + |