Linux Framebuffer Graphics

#linux#raspberry pi

Last Updated:

UPDATE: I’ve consolidated the original code into a set of low-level utilities, which can be found on my github here. These can be used to generate basic dashboards using a raspberry pi and something like AWS’s GetMetricWidgetImage API for cloudwatch.

I was working on a Raspberry Pi project recently and encountered a need for displaying some graphics on the raspberry pi screen without having a window system (I run raspbian lite on all my Pi’s). Luckily, I had some old code which did something similar to what I needed, and could draw basic shapes. It was designed on top of the linux framebuffer, but it had numerous performance issues and wasn’t really feasible for use as a result. You could watch it clear the screen line-by-line.

I decided I should try and optimize it, and try to expand its featureset to include display of PNGs and JPEGs as well, because who doesn’t need that.

Optimizing

The first step in optimizing the code was eliminating as much of the per-pixel logic as possible. For example, I had draw routines that looked a little bit like this (some bounds checking and additional logic removed):

void draw_rect(int x, int y, int w, int h, int color, int* context) {
    for (int cx = x; cx < x + w; cx++) {
        for (int cy = y; cy < y + h; cy++) {
            set_pixel(cx, cy, color, context);
        }
    }
}

I initially wrote this years ago when I was learning C, and didn’t know much about performance. Operating on individual elements in a buffer like that is extremely slow. The solution in this case, was simple: draw as little as possible using per-pixel logic (or memset() if its a grayscale color), and then memcpy() it to fill the rest of the area. These operations are much faster, since they are more optimized and can use SIMD instructions if they are available. As a result, drawing now gives the illusion of an instant color change if you fill the entire screen.

void draw_rect(int x, int y, int w, int h, int color, int* context) {
    // Write the first line
    for (int cx = x; cx < x + w; cx++) {
       set_pixel(cx, y, color, context);
    }

    // Repeat it over the rest of the shape.
    for (int cy = y + 1; cy < y + h; cy++) {
        // Copy one line at a time.
        memcpy(
            &context->data[context->width * cy + x,
            &context->data[context->width * y + x,
            w * sizeof(int)
        );
    }
}

Images!

With this in mind, I took a similar approach to images. First, we load the original image at its full resolution and store it in memory. Then, we do a resize operation to scale and crop it to the necessary size for display (also in memory). Then a screen display operation is just a memcpy() operation for each line of the image. This allowed me to implement a libpng and libjpeg reader that fills the following image data structure:

typedef struct {
    int* data;
    int width;
    int height;
} image_t;

Text?

Fonts take a similar approach. Currently, I’m using fonts which are defined in 8x8 C-arrays, and are imported into a font data structure similar to image_t. The only difference here is the font images have offsets, so the image backing a comma or underscore character can be as small as possible, and drawn at an offset. This provides an advantage over storing a fixed-width image set which contains an image for each character and is the same size.

Next, the fonts are mapped into an array based on their ASCII code, so they can be programmatically accessed and displayed onscreen.

The current fonts implementation is a bit limited, and doesn’t support multiple font sizes, and is limited to drawing on a solid color background. due to a lack of the ability to calculate transparency.

Next Steps

Currently, I’m working on support for transparency. This will require memory usage to increase since we will need to add the ability to layer content on top of each other. Doing so will allow us to implement a better font system, possibly using freetype to render actual fonts.

Github

I’ve put a copy of the routines and a demo makefile up on GitHub, which can be cloned and run. This should work on many Linux distributions with relative ease, I’ve run the current version on Raspbian lite and the original version was written on top of Ubuntu server. This can’t be run with the X-window system (or any) window system that uses the screen for graphics since they won’t play nice.

Change Log

  • 1/6/2018 - Initial Revision
  • 1/24/2018 - fix typos
  • 5/5/2020 - Add link to new tool

Found a typo or technical problem? file an issue!