DEV Community

Cover image for Display graphics without the use of Wayland or XOrg
Dimitrios Desyllas
Dimitrios Desyllas

Posted on

Display graphics without the use of Wayland or XOrg

As I was looking on the Suburban Railway I noticed that was using Linux:

Error messages and display in Greek suburban railway station

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*
Enter fullscreen mode Exit fullscreen mode

You can list the available framebuffers.

Furthermore foir each framebuffer there are the follwing acompanying text files:

  • /sys/class/graphics/fbX/virtual_size that stores the framebuffer resolution.
  • /sys/class/graphics/fbX/bits_per_pixel that 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Script walkthrough

Upon Lines:

WIDTH=$(cut -d, -f1 /sys/class/graphics/fb0/virtual_size)
HEIGHT=$(cut -d, -f2 /sys/class/graphics/fb0/virtual_size)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:-"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

When writing upon framebuffer the best way to do this is via using dd:

dd of=/dev/fb0 bs=1M status=progress
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)