As I was looking on the Suburban Railway I noticed that was using Linux:
That made me think can I draw graphics or display anythink in Linux without the use of any sort of display manager/window manager such as wayland or XOrg.
And I found out that yes I can. After a bit of googling I found out that upon linux there are files named as /dev/fb0,/dev/fb1... each file is a mapping to the ram that contains what should be displayed upon the screen.
Via providing the command:
ls -l /dev/fb*
You can list the available framebuffers.
Furthermore foir each framebuffer there are the follwing acompanying text files:
-
/sys/class/graphics/fbX/virtual_sizethat stores the framebuffer resolution. -
/sys/class/graphics/fbX/bits_per_pixelthat stored the color depth.
So if our framebuffer is /dev/fb0 then the following files should exist as well:
/sys/class/graphics/fb0/virtual_size/sys/class/graphics/fb0/bits_per_pixel
The resolution upon /sys/class/graphics/fbX/virtual_size is comma seperated string containing the format width,height for example:
$ cat /sys/class/graphics/fb0/virtual_size
1280,800
Whilst upon /sys/class/graphics/fb0/bits_per_pixel a single integer value is stored containing the color depth (how many bits is used for color):
$ cat /sys/class/graphics/fb0/bits_per_pixel
32
Therefore we can display an image using this shell script:
#!/bin/bash
WIDTH=$(cut -d, -f1 /sys/class/graphics/fb0/virtual_size)
HEIGHT=$(cut -d, -f2 /sys/class/graphics/fb0/virtual_size)
BPP=$(cat /sys/class/graphics/fb0/bits_per_pixel)
case "$BPP" in
16) FORMAT=RGB565 ;;
24) FORMAT=RGB ;;
32) FORMAT=RGBA ;;
*) echo "Unsupported BPP: $BPP"; exit 1 ;;
esac
curl -L "https://picsum.photos/${WIDTH}/${HEIGHT}" |
magick - -resize "${WIDTH}x${HEIGHT}!" "$FORMAT:-" |
dd of=/dev/fb0 bs=1M status=progress
What I do is I get framebuffers with and height and then I get the colo depth. Usinc convert I convert a random image downloaded from "https://picsum.photos into raw data.
Then I place them as raw bytes into the framebuffer using dd. In order for this scipt to work you may need to download imagemagick for ubuntu 26.04 (I used server version in VM) I installed it:
sudo apt install imagemagick-7.q16hdri
Script walkthrough
Upon Lines:
WIDTH=$(cut -d, -f1 /sys/class/graphics/fb0/virtual_size)
HEIGHT=$(cut -d, -f2 /sys/class/graphics/fb0/virtual_size)
We obtain the width and height of the framebuffer. The /sys/class/graphics/fb0/virtual_size file contains the framebuffer resolution, with the width and height separated by a comma, as mentioned above.
The cut command is used to extract each field. The -d, option specifies a comma as the field delimiter, while -f1 selects the first field (the width) and -f2 selects the second field (the height).
Next, we need to determine the framebuffer's bits per pixel (BPP):
BPP=$(cat /sys/class/graphics/fb0/bits_per_pixel)
case "$BPP" in
16) FORMAT=RGB565 ;;
24) FORMAT=RGB ;;
32) FORMAT=RGBA ;;
*) echo "Unsupported BPP: $BPP"; exit 1 ;;
esac
We select the appropriate color format that would be used upon convert command. Using convert is crucial because we need to write the raw data of the image towards the framebuffer.
Images stored on storage devices or retrieved from the internet are usually stored in compressed image formats such as JPEG (.jpg) or PNG (.png). Therefore, before the image can be displayed by the framebuffer, it needs to be decoded and converted into the pixel format supported by the framebuffer.
A framebuffer expects raw pixel data, where each pixel is represented by a specific number of bits or bytes according to the framebuffer's color depth and pixel format.
ImageMagick's magick command allows us to perform this conversion:
magick - -resize "${WIDTH}x${HEIGHT}!" "$FORMAT:-"
The -resize option resizes the input image to the framebuffer's resolution. The "$FORMAT:-" argument specifies the output pixel format and tells ImageMagick to write the resulting raw image data to standard output.
For example, if FORMAT is RGB565, the command produces raw RGB565 pixel data instead of a compressed JPEG or PNG image.
The - immediately after magick specifies the input. It tells ImageMagick to read the image data from standard input, which allows the image to be received through a pipe.
Therefore, when combined with curl, the data flows through the pipeline as follows:
Internet
│
│ compressed JPEG/PNG
▼
curl
│
│ image data through pipe
▼
magick
│
├── decode compressed image
├── resize to framebuffer resolution
└── convert to framebuffer pixel format
│
│ raw pixel data
▼
/dev/fb0
When writing upon framebuffer the best way to do this is via using dd:
dd of=/dev/fb0 bs=1M status=progress
The reason we do not simply pipe the raw data directly into the framebuffer is that dd buffers the incoming data and performs larger write operations. This changes the timing of how the framebuffer is updated.
If we simply pipe the result directly to the framebuffer, the displayed image can be prone to tearing:
This happens because the display hardware is continuously reading from the framebuffer while our process is simultaneously writing new pixel data to it:
┌──────────────────┐
│ Framebuffer │
└──────────────────┘
▲ │
│ │
writing data reading data
│ │
magick Display hardware
│
▼
Display
If the display controller reads the framebuffer while it is being updated, it can encounter a mixture of old and new pixel data. Consequently, different portions of the same displayed frame may come from different versions of the image, resulting in visible tearing.
Using dd with a suitable block size can reduce the visible tearing because dd buffers the incoming data and performs larger write operations to the framebuffer. This reduces the number of individual write operations and changes the timing of the framebuffer update compared with directly forwarding the pipe's output.
However, dd does not actually synchronize the writes with the display's refresh cycle. Therefore, it does not guarantee that tearing will never occur. It simply changes how the data is transferred to the framebuffer, which in practice can make the update appear less prone to tearing.
Conclusion
Linux providesd a simple way of writing data directly into display, allowinf us via a simple bash script (or event python) to display directly into display.
Upon this article we demonstrated a way to display an image downloaded from the internet directly into screen without the use of a display manager. This approach is commonly used on embedded systems though it is not the only one.


Top comments (0)