pngview - github

Raspberry Pi GPU Oriented Programming

How to work with the Dispmanx windowing system
Bit
18-10-2020
pngview - github

Introduction

This article tries to explain how it works a binary included in the kelboy-launcher. This binary file is pngview and was programmed with the DispmanX framework.

There is very little information available about this API. There are information notes on the Raspberry Pi forums, but there is no place where I explain in detail how to work with DispmanX.

This article aims to open source support for that bundled binary and shed some light on DispmanX information.

What is pngview?

It is a utility to display a PNG in certain coordinates on the Raspberry Pi using the Dispmanx windowing system. Its origin is in this github repository.

Why make a new development?

The available version did not work as described in the help, and we could even say that they did not work correctly. In Kelboy 2.0 it was required to show the battery, and one of the simplest ways to do it was by using this framework, using the existing pngview implementation, and putting a timeout on some coordinates for a while.

An attempt was made to use the already compiled pngview utility available, but its operation, despite being a mature repository, was not as indicated (the parameter values did not work as designed and the operation was intended to be used with certain keys ); Consequently, it was decided to study its operation and adapt it to the platform.

How it works?

The original binary has this description in his help:

Usage: pngview [-b ] [-d ] [-l ] [-x ] [-y ] 

-b - set background colour 16 bit RGBA
         e.g. 0x000F is opaque black
-d - Raspberry Pi display number
-l - DispmanX layer number
-x - offset (pixels from the left)
-y - offset (pixels from the top)
-n - non-interactive mode

And his calling from the kelboy-launcher scripts (it's a background callback):

pngview battery.png -b 0 -l 300003 -x 290 -y 7 -t 5000 &

And... what exactly was modified?

Basically, the set of instructions that were used to manage the events when pressing the keyboard keys, which were the cause of the erratic behavior, was eliminated, and the algorithm described in its help was left functional.

The fact of using a timeout in the kelboy of 5000 did not work without this modification (the original program forced to kill the process, and that behavior is not correct) and we could not close the application without killing it. Even certain times when we killed it we kept the graphic buffer completely black, as if pngview kept control, since it allowed us in a new implementation to paint on that layer but the console in the background was not shown.

Therefore, the source code that is compiled as part of the kelboy-launcher was as follows:

#define _GNU_SOURCE

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "backgroundLayer.h"
#include "imageLayer.h"
#include "key.h"
#include "loadpng.h"

#include "bcm_host.h"

//-------------------------------------------------------------------------

#define NDEBUG

//-------------------------------------------------------------------------

const char *program = NULL;

//-------------------------------------------------------------------------

volatile bool run = true;

//-------------------------------------------------------------------------

static void
signalHandler(
    int signalNumber)
{
    switch (signalNumber)
    {
    case SIGINT:
    case SIGTERM:

        run = false;
        break;
    };
}

//-------------------------------------------------------------------------

void usage(void)
{
    fprintf(stderr, "Usage: %s ", program);
    fprintf(stderr, "[-b ] [-d ] [-l ] ");
    fprintf(stderr, "[-x ] [-y ] \n");
    fprintf(stderr, "    -b - set background colour 16 bit RGBA\n");
    fprintf(stderr, "         e.g. 0x000F is opaque black\n");
    fprintf(stderr, "    -d - Raspberry Pi display number\n");
    fprintf(stderr, "    -l - DispmanX layer number\n");
    fprintf(stderr, "    -x - offset (pixels from the left)\n");
    fprintf(stderr, "    -y - offset (pixels from the top)\n");
    fprintf(stderr, "    -t - timeout in ms\n");
    fprintf(stderr, "    -n - non-interactive mode\n");

    exit(EXIT_FAILURE);
}

//-------------------------------------------------------------------------

int main(int argc, char *argv[])
{
    uint16_t background = 0x000F;
    int32_t layer = 1;
    uint32_t displayNumber = 0;
    int32_t xOffset = 0;
    int32_t yOffset = 0;
    uint32_t timeout = 0;
    bool xOffsetSet = false;
    bool yOffsetSet = false;
    bool interactive = true;

    program = basename(argv[0]);

    //---------------------------------------------------------------------

    int opt = 0;

    while ((opt = getopt(argc, argv, "b:d:l:x:y:t:n")) != -1)
    {
        switch(opt)
        {
        case 'b':

            background = strtol(optarg, NULL, 16);
            break;

        case 'd':

            displayNumber = strtol(optarg, NULL, 10);
            break;

        case 'l':

            layer = strtol(optarg, NULL, 10);
            break;

        case 'x':

            xOffset = strtol(optarg, NULL, 10);
            xOffsetSet = true;
            break;

        case 'y':

            yOffset = strtol(optarg, NULL, 10);
            yOffsetSet = true;
            break;
        
        case 't':

            timeout = atoi(optarg);
            break;

        case 'n':

            interactive = false;
            break;

        default:

            usage();
            break;
        }
    }

    //---------------------------------------------------------------------

    if (optind >= argc)
    {
        usage();
    }

    //---------------------------------------------------------------------

    IMAGE_LAYER_T imageLayer;

    const char *imagePath = argv[optind];

    if(strcmp(imagePath, "-") == 0)
    {
        // Use stdin
        if (loadPngFile(&(imageLayer.image), stdin) == false)
        {
            fprintf(stderr, "unable to load %s\n", imagePath);
            exit(EXIT_FAILURE);
        }
    }
    else
    {
        // Load image from path
        if (loadPng(&(imageLayer.image), imagePath) == false)
        {
            fprintf(stderr, "unable to load %s\n", imagePath);
            exit(EXIT_FAILURE);
        }
    }

    //---------------------------------------------------------------------

    if (signal(SIGINT, signalHandler) == SIG_ERR)
    {
        perror("installing SIGINT signal handler");
        exit(EXIT_FAILURE);
    }

    //---------------------------------------------------------------------

    if (signal(SIGTERM, signalHandler) == SIG_ERR)
    {
        perror("installing SIGTERM signal handler");
        exit(EXIT_FAILURE);
    }

    //---------------------------------------------------------------------

    bcm_host_init();

    //---------------------------------------------------------------------

    DISPMANX_DISPLAY_HANDLE_T display
        = vc_dispmanx_display_open(displayNumber);
    assert(display != 0);

    //---------------------------------------------------------------------

    DISPMANX_MODEINFO_T info;
    int result = vc_dispmanx_display_get_info(display, &info);
    assert(result == 0);

    //---------------------------------------------------------------------

    BACKGROUND_LAYER_T backgroundLayer;

    if (background > 0)
    {
        initBackgroundLayer(&backgroundLayer, background, 0);
    }

    createResourceImageLayer(&imageLayer, layer);

    //---------------------------------------------------------------------

    DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
    assert(update != 0);

    if (background > 0)
    {
        addElementBackgroundLayer(&backgroundLayer, display, update);
    }

    if (xOffsetSet == false)
    {
        xOffset = (info.width - imageLayer.image.width) / 2;
    }

    if (yOffsetSet == false)
    {
        yOffset = (info.height - imageLayer.image.height) / 2;
    }

    addElementImageLayerOffset(&imageLayer,
                               xOffset,
                               yOffset,
                               display,
                               update);

    result = vc_dispmanx_update_submit_sync(update);
    assert(result == 0);

    //---------------------------------------------------------------------

    int32_t step = 1;
    uint32_t currentTime = 0;

    // Sleep for 10 milliseconds every run-loop
    const int sleepMilliseconds = 10;

    while (run)
    {

        int c = 0;

        usleep(sleepMilliseconds * 1000);

        currentTime += sleepMilliseconds;
        if (timeout != 0 && currentTime >= timeout) {
            run = false;
        }
    }

    if (background > 0)
    {
        destroyBackgroundLayer(&backgroundLayer);
    }

    destroyImageLayer(&imageLayer);

    //---------------------------------------------------------------------

    result = vc_dispmanx_display_close(display);
    assert(result == 0);

    //---------------------------------------------------------------------

    return 0;
}


A simple and functional code, you can learn a lot about how to program on the Raspberry Pi using the Dispmanx.

It needs (including its error handling):

bcm_host_init();

DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open(displayNumber);
assert(display != 0);

DISPMANX_MODEINFO_T info;
int result = vc_dispmanx_display_get_info(display, &info);
assert(result == 0);

BACKGROUND_LAYER_T backgroundLayer;

if (background > 0){
    initBackgroundLayer(&backgroundLayer, background, 0);
}

createResourceImageLayer(&imageLayer, layer);

DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
assert(update != 0);

if (background > 0){
    addElementBackgroundLayer(&backgroundLayer, display, update);
}

if (xOffsetSet == false){
    xOffset = (info.width - imageLayer.image.width) / 2;
}

if (yOffsetSet == false){
    yOffset = (info.height - imageLayer.image.height) / 2;
}

addElementImageLayerOffset(&imageLayer, xOffset, yOffset, display, update);

result = vc_dispmanx_update_submit_sync(update);
assert(result == 0);

It seems simple, and it is, with these simple lines we will have everything we need to paint our image.

Now we want to destroy that image (program completion). For it:

if (background > 0){
    destroyBackgroundLayer(&backgroundLayer);
}

destroyImageLayer(&imageLayer);

result = vc_dispmanx_display_close(display);
assert(result == 0);

return 0;

It seems simple, and it is, it does not require great programming skills.

Later we will explain (when they are introduced in the lemoncrest programs) how to perform effects with this simple technique.

Launcher
This article is considered part of offered documentation by Lemoncrest products.
Comments
Page 1Page 1ear iconeye iconFill 23text filevr