Friday, 4 March 2016

Low-level Graphics on Raspberry Pi vs other Linux distributions

In the very first post on this subject I suggested the examples would apply to other systems as well - luckily I did add the caveat 'some of the code' ;)

As it appears that the support for the framebuffer driver and the functionality it provides seems to in fact differ a lot between Linux distributions and graphics hardware specific drivers. This was way simpler back in the day I started with Linux fb some 20 years ago... So unfortunately cannot promise the examples will fully work on even another Debian based system. Some of you readers had already found this out some time ago, apologies if it seemed I promised more than I delivered.

I have recently played with a Debian 8.3.0 distribution on a desktop PC. I found out that for example changing the bits_per_pixel does not return errors and querying the information back after setting looks like it went through. But trying to plot pixels will not produce the expected result - it appears that the fb is still in 32 bit mode even when it reports 8 bit :/ Of course any 32 bit pixel plotting code works fine...

I modified the original test program a bit to output some more information. One additional bit (well, a string actually :D ) of information is the fb_fix_screeninfo.id which should give us the fb driver 'name' - for my setup this is inteldrmfb:

The 'drm' part there suggests that the driver is implementing the more recent Direct Rendering Manager (DRM) model and the fbdev support is not complete. If running other distributions and interested in low level graphics it would be worth investigation the DRM API libdrm.

Wednesday, 24 February 2016

Low-level Graphics on Raspberry Pi (more images)

In an earlier post we looked at converting an image to 16 bit 5:6:5 format for direct copying to framebuffer. The obvious drawback of that approach is the requirement on the image size to exactly match the display/framebuffer size (or the width at least).

Modifying the PPM reading code to read into an image object in memory (I chose to use the struct fb_image from linux/fb.h as teh image object) and combining that with the familiar framebuffer drawing code (put_pixel_RGB565 from part 6)
...
int read_ppm(char *fpath, struct fb_image *image) {
...
    image->data = malloc(width * height * bytes_per_pixel);
...
        // store pixel in memory
        unsigned int pix_offset = (y * width + x ) * bytes_per_pixel;
        *((unsigned short *)(image->data + pix_offset)) = rgb565;
...

void draw(struct fb_image *image) {
    int y;
    int x;
    unsigned char rgb[3];

    for (y = 0; y < image->height; y++) {
        for (x = 0; x < image->width; x++) {
            // get pixel from image
            unsigned int img_pix_offset = (y * image->width + x) * 2;
            unsigned short c = 
                *(unsigned short *)(image->data + img_pix_offset);
            // plot pixel to screen
            unsigned int fb_pix_offset = 
                x * 2 + y * finfo.line_length;
            *((unsigned short*)(fbp + fb_pix_offset)) = c;
        }
    }
}

...
int main(int argc, char* argv[])
{

    // read the image file
    int ret = read_ppm(argv[1], &image);
    if (ret != 0) {
        printf("Reading image failed.\n");
        return ret;
    }
...

Make sure you have a 24 bit PPM image file to start with - convert one from for example PNG using pngtopnm (any bit depth source file should do) - and make sure the image fits the screen (no bounds checking in the code). Then compile and run:
gcc -O2 -o ppmtofbimg ppmtofbimg.c
./ppmtofbimg test24.ppm
Full source and a test image (test24.ppm) in GitHub.

Tuesday, 23 February 2016

Modifying Ctrl+Alt+Del behavior in Debian Jessie vs Wheezy

Finally got up to testing Raspbian Jessie and being a mixed user of Linux and Windows ran into an issue with the 'three finger salute' Ctrl-Alt-Del (C-D-A) key combination. In Windows this is normally used to lock up the UI when going away from the computer and often comes automatically 'from the spine'. In Linux it nowadays seems to normally initiates a reboot ...in the windowing systems this usually pops up a dialog asking to confirm but when working in the console only it just reboots. Which is of course fairly annoying when not intentional :|

The previous release of Raspbian - based on Debian Wheezy - is built on the sysvinit style boot and the Ctrl-Alt-Del behavior is defined in the /etc/inittab file and can be disabled by commenting out the line that looks something like:
...
# What to do when CTRL-ALT-DEL is pressed.
ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
...
So:
sudo nano /etc/inittab
*** find the line with 'ctrlaltdel' (Ctrl-W for search)
*** comment out by adding the hash '#' at beginning of line
*** save file (Ctrl-O) and exit (Ctrl-X)
sudo init q
...the last command 're-reads' the inittab file and applies the change.

But Jessie is built on the systemd style boot and there is no more /etc/inittab. Surprisingly I did not find a clear, full answer to this using Google - some links like Securing Debian Manual (4.8 Restricting system reboots through the consol) are outdated for Jessie - some are unanswered - some only offer partial information - and some discuss slightly different variation of behavior wanted. Combining the information from those it appears the behavior is controlled by /lib/systemd/system/ctrl-alt-del.target (not /etc/systemd/system/*):
ls -la /lib/systemd/system/ctrl*

lrwxrwxrwx 1 root root 13 Aug 30 23:04 /lib/systemd/system/ctrl-alt-del.target -> reboot.target
To change this we need to remove the current link and replace:
sudo rm /lib/systemd/system/ctrl-alt-del.target
sudo ln -s /dev/null /lib/systemd/system/ctrl-alt-del.target
sudo systemctl daemon-reload
...so to disable Ctrl-Alt-Del we point the target to /dev/null i.e. to be completely ignored and again the last command 're-reads' the configuration and applies the change.

Thursday, 11 February 2016

Low-level Graphics on Raspberry Pi (even more palette)

Another effect with palette: fade in/out. We can - in addition to rotating the entries - modify the palette entry RGB values on the fly and make the pixels drawn in this color to fade in or out.

Draw some background stuff (blue and white checkerboard), a black block to hold the actual piece and something to fade:
...
#define DEF_COLOR 14
#define MOD_COLOR (16 + DEF_COLOR)
...
    int x, y;
    int t = vinfo.xres / 24;

    // t x t pix black and white checkerboard
    for (y = 0; y < vinfo.yres; y++) {
        int xoffset = y / t % 2;
        for (x = 0; x < vinfo.xres; x++) {

            // color based on the tile
            int c = (((x / t + xoffset) % 2) == 0) ? 1 : 15;

            // draw pixel
            put_pixel(x, y, c);

        }
    }
    // black block in the middle
    for (y = (vinfo.yres / 2); y < (vinfo.yres / 2 + t); y++) {
        for (x = (vinfo.xres / 4 - t / 2); 
             x < (vinfo.xres / 4 * 3 + t / 2); x++) 
        {
            put_pixel(x, y, 0);
        }
    }
    // something in the color in the extended palette
    for (y = (vinfo.yres / 2 + t / 4); 
         y < (vinfo.yres / 2 + t - t / 4); y++) 
    {
        int n = 0;
        for (x = (vinfo.xres / 4); x < (vinfo.xres / 4 * 3); x++) {
            if (n % (t / 4)) {
                put_pixel(x, y, MOD_COLOR);
            }
            n++;
        }
    }
...
And then modify the associated palette entry:
...
        // fade palette entry out (to black)" and back in
        int j;
        int fps = 60; // frames per second
        int f = 255;
        int fd = -2;
        while (f < 256) {
            r[DEF_COLOR] = (def_r[14] & f) << 8;
            g[DEF_COLOR] = (def_g[14] & f) << 8;
            b[DEF_COLOR] = (def_b[14] & f) << 8;
            // change fade
            f += fd;
            // check bounds
            if (f < 0) {
                // change direction
                fd *= -1;
                f += fd;
            }
            // Note that we set up the 'pal' structure earlier
            // and it still points to the r, g, b arrays,
            // so we can just reuse 'pal' here
            if (ioctl(fbfd, FBIOPUTCMAP, &pal) != 0) {
                printf("Error setting palette.\n");
            }
            usleep(1000000 / fps);
        }
...
Compile with 'gcc -o fbtest5z fbtest5z.c' and run with './fbtest5y'. The yellow bars should fade out and back in:
...imagine for example black background and white text fading in and out... movie titles :D

Full code available in GitHub.

Wednesday, 10 February 2016

Low-level Graphics on Raspberry Pi (more palette)

In a previous post we briefly looked at palette animation. Now (hopefully) a slightly more appetising example of what could be done with this technique.

Let's draw some 'rainbow striped' blocks and customise the palette to include 16 colors sliding from red to yellow:
...
void draw() {
    ...
    // colored blocks
    for (y = 0; y < vinfo.yres; y += t) {
        int xoffset = y / t % 2;
        for (x = t * xoffset; x < vinfo.xres; x += t * 2) {
            int x2, y2;
            for (y2 = 0; y2 < t; y2++) {
                for (x2 = 0; x2 < t; x2++) {

                    // color based on y2 value
                    // using the custom colors (16+)
                    int c = 16 + (y2 % 16);

                    // draw pixel
                    put_pixel(x + x2, y + y2, c);

                }
            }
        }
    }
}
...
int main(int argc, char* argv[])
{
    ...

    // Set palette
    ...
    for(i = 0; i < 16; i++) {
        // red-yellow gradient
        // note that Linux provides more precision (0-65535),
        // so we multiply ours (0-255) by 256
        r[i] = 255 << 8;
        g[i] = ((15 - i) * 16) << 8;
        b[i] = 0;
    }

...

And then animate the palette by rotating the custom color entries:
...
        int j;
        int fps = 30; // frames per second
        int d = 5; // duration in seconds
        // repeat for given time
        for(j = 0; j < fps * d; j++) {
            // store color 0 in temp variables
            int rt = r[0];
            int gt = g[0];
            int bt = b[0];
            // replace colors by copying the next
            for(i = 0; i < 15; i++) {
                r[i] = r[i+1];
                g[i] = g[i+1];
                b[i] = b[i+1];
            }
            // restore last one from temp
            r[15] = rt;
            g[15] = gt;
            b[15] = bt;
            // Note that we set up the 'pal' structure earlier
            // and it still points to the r, g, b arrays,
            // so we can just reuse 'pal' here
            if (ioctl(fbfd, FBIOPUTCMAP, &pal) != 0) {
                printf("Error setting palette.\n");
            }
            usleep(1000000 / fps);
        }
...
Compile with 'gcc -o fbtest5y fbtest5y.c' and run with './fbtest5y'. The blue checker board with holes looks like floating on top of flowing lava waves ;)

Full code available in GitHub.

[Continued in next part Even more palette]

Wednesday, 27 January 2016

Low-level Graphics on Raspberry Pi (images)

One of the recurring themes regarding Raspberry Pi graphics is the 16-bit RGB 5:6:5 display mode vs typically 24-bit images. The '565' mode is the default on HDMI and possibly the only one available on TFT add-on displays like this one from Adafruit.

There are tools that can be used to display images in the framebuffer - which do a suitable color-space conversion and do seem to work with the '565' display mode. However for simple and speedy display of images it would in many cases be useful to be able to convert the image to '565' beforehand. The usual suspect tools Netpbm and Imagemagick do not seem to include an option to convert to '565'. Only tool I know for sure has this is GIMP which is a fairly big interactive GUI application and might not suit (there is some level of scripting support though which might be worth checking out). As a side note IrfanView for Windows can read '565' raw images fine (but does not seem to have an option to save in this mode).

Fellow RPi forum user bullwinkle has found out that ffmpeg supports this mode and - while more typically used for stitching together images to form a video - can also output image files:
ffmpeg -vcodec png -i input.png -vcodec rawvideo -f rawvideo \
    -pix_fmt rgb565 output.raw

I created a simple 'quick and dirty' conversion program for converting a 24-bit PPM image file to 16-bit 'raw' RGB 565 format. The colorspace conversion is fairly simple: take the top 5 bytes of 8-bit red value r >> 3 (bitwise shift right, 8 - 5 = 3), top 6 of green, top 5 of blue - and stitch back together into one 16-bit RGB rgb = (r << 11) + (g << 5) + b:
...
    for (y = 0; y < height; y++) {
        for (x = 0; x < width; x++) {
            if (fread(rgb, 3, 1, fp) == 1) {
                unsigned char r = rgb[0];
                unsigned char g = rgb[1];
                unsigned char b = rgb[2];
                unsigned short rgb565 = ((r >> 3) << 11) + 
                                        ((g >> 2) << 5) + 
                                        (b >> 3);
                unsigned char c1 = (rgb565 & 0xFF00) >> 8;
                unsigned char c2 = (rgb565 & 0xFF);
                fwrite(&c2, 1, 1, stdout);
                fwrite(&c1, 1, 1, stdout);
            }
...
Compile with:
gcc -O2 -o ppmtorgb565 ppmtorgb565.c
And starting off with a 24-bit PNG file use pngtoppm from the Netpbm package to convert PNG->PPM, pipe the output to the new tool and save the final output to a file:
pngtopnm /path/to/image24.png | /path/to/ppmtorgb565 > image565.raw
Or maybe even direct to framebuffer (if 'disk' space is more valuable than run speed):
pngtopnm /path/to/image24.png | /path/to/ppmtorgb565 > /dev/fb1
...as (almost) everything in Linux is a file ;)

Disclaimer: I have not yet tested this on RPi - just on a PC running a Debian VM - will update after properly testing (or seeing comments on the RPi forum in the aforementioned thread). This has now been succesfully tested on RPi. My test images can be found in GitHub too (along a PPM to 24-bit RGB '888' converter).

[Continued in next part More palette]