Showing posts with label graphics. Show all posts
Showing posts with label graphics. Show all posts

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]

Thursday, 3 December 2015

Low-level Graphics on Raspberry Pi (palette)

Again life has happened and the time has slipped. Thanks for the response on the Raspberry Pi forum. To iterate the message from there: I have disabled comments here because I cannot guarantee any kind of decent response time, sorry.

Just a quick, short one now on something I came across trawling through old stuff. Palette animation a.k.a color cycling (Wikipedia) is a technique that might produce interesting effects when applied to a suitably arranged image. In most cases it is likely to be a lot faster than redrawing the actual pixels. Continuing from the fbtest5.c introduced in Part V we can animate the palette like this:
        // draw...
        draw();
        sleep(1);

        int j;
        for(j = 0; j < 16; j++) {
            for(i = 0; i < 16; i++) {
                // rotate the original values based on j changing...
                r[i] = def_r[(i + j + 1) % 16] << 8;
                g[i] = def_g[(i + j + 1) % 16] << 8;
                b[i] = def_b[(i + j + 1) % 16] << 8;
            }
            // 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)) {
                printf("Error setting palette.\n");
            }
            sleep(1);
        }
...which should cycle the color bars in the lower half of the screen.

[Full source available in GitHub]

[Continued in next part Images]

Tuesday, 27 January 2015

Low-level Graphics on Raspberry Pi (part X update III)

Finally managed to find the cause for the (intermittent - reproducible at will only straight after boot) screen blanking issue (mentioned in previous post): the changes in the framebuffer driver seem to require the calls to KDSETMODE to be inside the FBIOPUT_VSCREENINFO calls... Fixed code now in GitHub

Tuesday, 6 January 2015

Low-level Graphics on Raspberry Pi (part X update II)

Finally... in continuation to this post, after some elaboration on the forum I managed to get down to testing this properly - it appears that the changes in the fb driver can be made to work! Replace the 'switch page' by either (ioctl version):
        // switch page
        // ioctl version
        vinfo.yoffset = cur_page * vinfo.yres;
        ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo);
        ioctl(fbfd, FBIO_WAITFORVSYNC, 0);
or (mailbox version):
        // switch page
        // mailbox version
        vx = 0;
        vy = cur_page * vinfo.yres;
        set_fb_voffs(&vx, &vy);
        ioctl(fbfd, FBIO_WAITFORVSYNC, 0);
Curiously the order has to be pan first - wait for vsync after... Obviously if going for the 'ioctl only version' one can scrap all the extra code and there is no need to create the char_dev node etc.

Cleaned up 'pure Linux fb' version of the code available in GitHub. Compile with (as I took out the timing code no need for the rt lib):
gcc -O2 -o fbtestXIV fbtestXIV.c
And run with:
fbset -depth 8
./fbtestXIV
...as it appears the latest Raspbian builds require forcing the framebuffer mode to 8-bit colors before running the examples where 8-bit mode is set. Later the mode can be freely set and the examples run fine... If you want to time the execution use:
time ./fbtestXIV
...which should give just over 10 s if we managed to produce 60 fps for 10 seconds.

Thursday, 4 September 2014

Low-level Graphics on Raspberry Pi (part X update)

BREAKING NEWS: The Raspberry Pi Linux framebuffer driver has been updated and the PAN_DISPLAY function mentioned in Part X has been added - so the smooth double-buffer page-flipping animation should now be possible without further hacks!

Based on a quick test it works fine - will get back with a confirmation after some more testing...

UPDATE 2014-09-10: Well, the pan (and set virtual info) now work fine but it appears that the vsync functionality does not. Also it looks like there was a change made somewhere deeper in the software stack and the mailbox interface based version does not have the vsync anymore either.

UPDATE 2015-01-06: new info

Friday, 18 April 2014

Low-level Graphics on Raspberry Pi (text)

Someone asked me how to go about rendering text - as we are using a framebuffer in graphics mode and can only plot pixels, text output does not come for free and has to be done extending the pixel plotting.

There are basically two different ways to render text: raster fonts and vector fonts. A font is a collection of character representations or 'images' of characters (letters and symbols). As should be obvious: raster font represents the characters as raster images - vector font represents them as collections of lines/polygons. Using the utility functions presented in previous posts, it would be possible to choose either approach.

Let's take a look at simple monospace raster fonts. Each character would be a block of pixels: some filled - some empty. For example a simple version of the letter 'A' as a 8x8 pixel image could be:
...the image occupies 64 pixels: 8 across and 8 down - the pixels representing the character outline would be filled (in this case black). A naive implementation could just plot the pixels:
void draw_char(char a, int x, int y, int c) {
  switch (a) {
    case 'A':
      // top row
      put_pixel(x + 3, y, c);
      put_pixel(x + 4, y, c);
      // next row
      put_pixel(x + 2, y, c);
      put_pixel(x + 5, y, c);
    ...
}
...which obviously would not be very efficient coding wise.

A better solution would be to use a pixel map - a raster image - and copy this image to the desired spot on the screen. The pixel map could be drawn in a drawing program like GIMP, saved to a file, loaded into memory in our program and the character pixel blocks copied from it to the screen. This might be the best way to go, but dealing with image file formats is another story... Alternative way would be to define the pixel maps in code - something along:
char A[] = {
    0, 0, 0, 1, 1, 0, 0, 0,
    0, 0, 1, 0, 0, 1, 0, 0,
    0, 1, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 1, 0,
    0, 1, 1, 1, 1, 1, 1, 0,
    0, 1, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0
};
Now how to avoid the long switch statement... Typically computer systems would use a character encoding scheme like ASCII, which in fact is used by the C language in Linux by default. The scheme defines numerical codes for characters: for example space character is 32 (decimal) and the exclamation mark 33 - a quick test to see this in effect would be:
  printf("%c", 33);
  printf("\n");
  printf("%d", ' ');
...which should print out the exclamation mark and number 32. As this is pretty much a standard and, if leaving out the first 32 non-printable control characters, quite convenient for our purpose, we could define all character pixel maps in one two dimensional array:
#define FONTW 8
#define FONTH 8

char fontImg[][FONTW * FONTH] = {
    { // ' ' (space)
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0  
    },
    { // !
            0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 1, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0
    },
    ...
}
...where the outer array size is left to be decided (by the compiler) based on the actual entries defined, so we can do some of the first characters for testing and leave rest for later.

So we have the characters defined in the ASCII order, but leaving out the first 32 and we can get the appropriate pixel map using the C language char/integer interchangeability:
    char c = '!';
    char *img = fontImg[c - 32];
...now the imgc variable would point to the 8x8 bytes pixel map for the exclamation mark.

Drawing a single character using the put_pixel function familiar form before could be implemented as:
        // loop through pixel rows
        for (y = 0; y < FONTH; y++) {
            // loop through pixel columns
            for (x = 0; x < FONTW; x++) {
                // get the pixel value
                char b = img[y * FONTW + x];
                if (b > 0) { // plot the pixel
                    put_pixel(textX + i * FONTW + x, textY + y, 
                              textC);
                }
                else { 
                    // leave empty (or maybe plot 'text backgr color')
                }
            } // end "for x"
        } // end "for y"
And drawing a string (an array of characters):
    // loop through all characters in the text string
    l = strlen(text);
    for (i = 0; i < l; i++) {
        // get the 'image' index for this character
        int ix = font_index(text[i]);
        // get the font 'image'
        char *img = fontImg[ix]; 
        // loop through pixel rows
        ...
        } // end "for y"
    } // end "for i"
Putting all this together with setting up the graphics mode etc (full code in GitHub - note the .c and .h both required), compiling with gcc -o fbtestfnt fbtestfnt.c and running with a command like:
$ ./fbtestfnt "A\"BC\"D'E'F,.;:012(8)9+-<=>?"
...should give us a display similar to:
As one can see, the .h file contains only part of the full character set - rest are left as an exercise for the reader ;) Also obviously the 8x8 pixel font is quite small for higher resolutions - the code should provide for easy customisation to support more character sizes and/or multi-colored (possibly anti-aliased) fonts ...or to get creative with additional characters (127+ indexes 'extended ASCII' or maybe PETSCII ...or Easter eggs :P ).

[Continued in next part Palette]

Thursday, 10 April 2014

Low-level Graphics on Raspberry Pi (shapes)

By now (this was started a year ago already) I would assume most of the most interested readers having developed their own drawing functions for creating more useful output. But just in case there are newcomers or someone needs a push to the right direction... Let's take a look at drawing some basic shapes.

In part eight we drew some vertical and horizontal lines by repeating calls to put_pixel for adjacent pixels. How about sloping lines? Not so trivial (except exactly 1:1 slope) - as one pixel step in one direction may need multiple pixels step in the other direction and normally gaps would not be wanted. Luckily this has been studied decades ago and there exists a well known algorithm for drawing sloping lines called the Bresenham's line algorithm which in our case can be implemented as:
void draw_line(int x0, int y0, int x1, int y1, int c) {
  int dx = x1 - x0;
  dx = (dx >= 0) ? dx : -dx; // abs()
  int dy = y1 - y0;
  dy = (dy >= 0) ? dy : -dy; // abs()
  int sx;
  int sy;
  if (x0 < x1)
    sx = 1;
  else
    sx = -1;
  if (y0 < y1)
    sy = 1;
  else
    sy = -1;
  int err = dx - dy;
  int e2;
  int done = 0;
  while (!done) {
    put_pixel(x0, y0, c);
    if ((x0 == x1) && (y0 == y1))
      done = 1;
    else {
      e2 = 2 * err;
      if (e2 > -dy) {
        err = err - dy;
        x0 = x0 + sx;
      }
      if (e2 < dx) {
        err = err + dx;
        y0 = y0 + sy;
      }
    }
  }
}
...which should work for any line orientations.

Drawing other shapes becomes a lot easier using the line function - like rectangle outline:
// (x0, y0) = left top corner coordinates
// w = width and h = height
void draw_rect(int x0, int y0, int w, int h, int c) {
  draw_line(x0, y0, x0 + w, y0, c); // top
  draw_line(x0, y0, x0, y0 + h, c); // left
  draw_line(x0, y0 + h, x0 + w, y0 + h, c); // bottom
  draw_line(x0 + w, y0, x0 + w, y0 + h, c); // right
}
Even filling a rectangle can be implemented using the same:
void fill_rect(int x0, int y0, int w, int h, int c) {
  int y;
  for (y = 0; y < h; y++) {
    draw_line(x0, y0 + y, x0 + w, y0 + y, c);
  }
}
...for each line/y we draw a horizontal line... Of course it might be a good idea to optimize this using memset()...

The Bresenham's line algorithm has been extended to draw circles as well - this is very clever optimisation only calculating the points for one eight of the circle and just mirroring the rest:
void draw_circle(int x0, int y0, int r, int c)
{
  int x = r;
  int y = 0;
  int radiusError = 1 - x;

  while(x >= y)
  {
    // top left
    put_pixel(-y + x0, -x + y0, c);
    // top right
    put_pixel(y + x0, -x + y0, c);
    // upper middle left
    put_pixel(-x + x0, -y + y0, c);
    // upper middle right
    put_pixel(x + x0, -y + y0, c);
    // lower middle left
    put_pixel(-x + x0, y + y0, c);
    // lower middle right
    put_pixel(x + x0, y + y0, c);
    // bottom left
    put_pixel(-y + x0, x + y0, c);
    // bottom right
    put_pixel(y + x0, x + y0, c);

    y++;
    if (radiusError < 0)
 {
   radiusError += 2 * y + 1;
 } else {
      x--;
   radiusError+= 2 * (y - x + 1);
    }
  }
}
...and it is trivial to modify this to draw filled circles (in four horizontal slices):
void fill_circle(int x0, int y0, int r, int c) {
  int x = r;
  int y = 0;
  int radiusError = 1 - x;

  while(x >= y)
  {
    // top
    draw_line(-y + x0, -x + y0, y + x0, -x + y0, c);
    // upper middle
    draw_line(-x + x0, -y + y0, x + x0, -y + y0, c);
    // lower middle
    draw_line(-x + x0, y + y0, x + x0, y + y0, c);
    // bottom 
    draw_line(-y + x0, x + y0, y + x0, x + y0, c);

    y++;
    if (radiusError < 0)
    {
      radiusError += 2 * y + 1;
    } else {
      x--;
      radiusError+= 2 * (y - x + 1);
    }
  }
}

Now use these to draw something - a quick (admittedly pretty much mindless) example:
void draw() {

  int x;
  
  // some pixels
  for (x = 0; x < vinfo.xres; x+=5) {
    put_pixel(x, vinfo.yres / 2, WHITE);
  }

  // some lines (note the quite likely 'Moire pattern')
  for (x = 0; x < vinfo.xres; x+=20) {
    draw_line(0, 0, x, vinfo.yres, GREEN);
  }
  
  // some rectangles
  draw_rect(vinfo.xres / 4, vinfo.yres / 2 + 10, 
            vinfo.xres / 4, vinfo.yres / 4, PURPLE);  
  draw_rect(vinfo.xres / 4 + 10, vinfo.yres / 2 + 20, 
            vinfo.xres / 4 - 20, vinfo.yres / 4 - 20, PURPLE);  
  fill_rect(vinfo.xres / 4 + 20, vinfo.yres / 2 + 30, 
            vinfo.xres / 4 - 40, vinfo.yres / 4 - 40, YELLOW);  

  // some circles
  int d;
  for(d = 10; d < vinfo.yres / 6; d+=10) {
    draw_circle(3 * vinfo.xres / 4, vinfo.yres / 4, d, RED);
  }
  
  fill_circle(3 * vinfo.xres / 4, 3 * vinfo.yres / 4, 
              vinfo.yres / 6, ORANGE);
  fill_circle(3 * vinfo.xres / 4, 3 * vinfo.yres / 4, 
              vinfo.yres / 8, RED);

}
...might be a good idea to make sure we are in 8 bit mode for the color constants to work... Full code in GitHub.

[Continued in next part Text]

Friday, 21 March 2014

Low-level Graphics on Raspberry Pi (part X+2)

In the previous part we did some animation with page flipping ...requiring a bit ugly 'incantation' outside of the application code.

It is worth noting that not all animation or screen effects require page flipping (or v-syncing). There is no need for such for example if the change between 'frames' is small enough - like updating textual or numerical indicators - or the change is stochastic enough - like for example the legendary (at least back in the day in early-mid 1990's) fire effect.

The fire effect might in fact work as a good example ...also admittedly I love it myself and cannot wait to show it off ;) I wrote my first version of the code for a 386sx PC with a plain vanilla VGA card - tweaked into 320x240 pixels 256 color 'Mode X' somewhere around 1993 after seeing the effect on one university course mate's Amiga. Part of the demoscene ethos and big part of the fun was of course to not copy somebody else's ready-made code but to figure out the idea your self. However I am now going to be a spoiler and reveal the code to this effect (well, there are versions already floating in the internet)...

First we have to come up with a nice fiery color palette - this should give us a nice one from golden yellow, to orange, to red, to fading the red to black:
    // Create palette
    unsigned short r[256]; // red
    unsigned short g[256]; // green
    unsigned short b[256]; // blue
    int i;
    for (i = 0; i < 256; i++) {
        if (i < 32) {
            r[i] = i * 7 << 8;
            g[i] = b[i] = 0;
        }
        else if (i < 64) {
            r[i] = 224 << 8;
            g[i] = (i - 32) * 4 << 8;
            b[i] = 0;
        }
        else if (i < 96) {
            r[i] = 224 + (i - 64) << 8;
            g[i] = 128 + (i - 64) * 3 << 8;
            b[i] = 0;
        }
        else {
            r[i] = g[i] = 255 << 8;
            b[i] = 128 << 8;
        }
    }
    struct fb_cmap palette;
    palette.start = 0;
    palette.len = 256;
    palette.red = r;
    palette.green = g;
    palette.blue = b;
    palette.transp = 0; // null == no transparency settings
    // Set palette
    if (ioctl(fbfd, FBIOPUTCMAP, &palette)) {
        printf("Error setting palette.\n");
    }

...and remember to restore the palette at the end of the execution! Good exercise would be to work out other nice color transitions - something like gas fire like white-yellow-blue for example.

For plotting the pixels, the algorithm has three phases: seed, smooth and convect. In seed phase, we 'sow the seeds for the fire' by plotting semi-randomly bright yellow pixels along the bottom line of the screen. In smooth phase, we scan through all pixels on the screen and average them with the surrounding pixels to smooth out clear color edges. And in the convect phase, we make the flames to rise up the screen slowly cooling down and fading into the background. As it is evident in the code, it is good to play around with the values for seed colors, frequency of seed pixels, smoothing factors etc:

        // Draw
        int maxx = var_info.xres - 1;
        int maxy = var_info.yres - 1;
        int n = 0, r, c, x, y;
        int c0, c1, c2;
        while (n++ < 200) {
            // seed
            for (x = 1; x < maxx; x++) {

                r = rand();
                c = (r % 4 == 0) ? 192 : 32;
                put_pixel(x, maxy, fbp, &var_info, c);
                if ((r % 4 == 0)) { // && (r % 3 == 0)) {
                    c = 2 * c / 3;
                    put_pixel(x - 1, maxy, fbp, &var_info, c);
                    put_pixel(x + 1, maxy, fbp, &var_info, c);
                }
            }

            // smooth
            for (y = 1; y < maxy - 1; y++) {
                for (x = 1; x < maxx; x++) {
                    c0 = get_pixel(x - 1, y, fbp, &var_info);
                    c1 = get_pixel(x, y + 1, fbp, &var_info);
                    c2 = get_pixel(x + 1, y, fbp, &var_info);
                    c = (c0 + c1 + c1 + c2) / 4;
                    put_pixel(x, y - 1, fbp, &var_info, c);
                }
            }

            // convect
            for (y = 0; y < maxy; y++) {
                for (x = 1; x < maxx; x++) {
                    c = get_pixel(x, y + 1, fbp, &var_info);
                    if (c > 0) c--;
                    put_pixel(x, y, fbp, &var_info, c);
                }
            }
        }
It appears that for this we need an utility function for reading the current value from a pixel on the screen:
// utility function to get a pixel
char get_pixel(int x, int y, void *fbp, struct fb_var_screeninfo *vinfo) {
    unsigned long offset = x + y * vinfo->xres;
    return *((char*)(fbp + offset));
}
And then there's the annoying blinking console cursor we need to hide...
#include <linux/kd.h>

...
    // hide cursor
    char *kbfds = "/dev/tty";
    int kbfd = open(kbfds, O_WRONLY);
    if (kbfd >= 0) {
        ioctl(kbfd, KDSETMODE, KD_GRAPHICS);
    }
    else {
        printf("Could not open %s.\n", kbfds);
    }

...and remember to restore at end!

[Full source available in GitHub]

This should produce the awesome effect as seen in this screenshot (already sneak-peeked in part 2):
Starfield, shadebobs and plasma effects would not require page flipping / v-syncing and might be worth attempting... some more examples for inspiration or dwelling in the good oldskool days for example here ;)

[Continued in next part Shapes]

Sunday, 16 March 2014

Low-level Graphics on Raspberry Pi (part X+1)

NOTE 2015-01-27: On later Raspbian builds this approach may lead to a lock up! It is adviced to use the 'pure fb' approach - code available in this post.

In previous part we tried a simple animation - with not so perfect results...

Obviously with so many different avenues to tackle for the Raspberry Pi driver developers it is not possible for us users to get everything 'for free' and at instant. I sure wish I had the time and/or the drive to attempt extending the fb driver myself ...or maybe the legendary 'Somebody Else' could do it ;) As the thing is, it does not seem that big of a job: there is already support for the 'page flipping' using double size buffer and panning the display in the Raspberry Pi firmware Mailbox interface.

In the meantime, we could take a stab at trying this out. Raspberry Pi forum users hacking on the 'bare metal' (thanks guys) pointed me before to look at the arch/arm/mach-bcm2708/include/mach/vcio.h in the RPi firmware GitHub sources and to the way to talk to the mailbox:
...
#include "vcio.h"
...
int mboxfd = 0;
...

// helper function to talk to the mailbox interface
static int mbox_property(void *buf)
{
   if (mboxfd < -1) return -1;
   
   int ret_val = ioctl(mboxfd, IOCTL_MBOX_PROPERTY, buf);

   if (ret_val < 0) {
      printf("ioctl_set_msg failed:%d\n", ret_val);
   }

   return ret_val;
}

// helper function to set the framebuffer virtual offset == pan
static unsigned set_fb_voffs(unsigned *x, unsigned *y)
{
   int i=0;
   unsigned p[32];
   p[i++] = 0; // size
   p[i++] = 0x00000000; // process request

   p[i++] = 0x00048009; // get physical (display) width/height
   p[i++] = 0x00000008; // buffer size
   p[i++] = 0x00000000; // request size
   p[i++] = *x; // value buffer
   p[i++] = *y; // value buffer 2

   p[i++] = 0x00000000; // end tag
   p[0] = i*sizeof *p; // actual size

   mbox_property(p);
   *x = p[5];
   *y = p[6];
   return p[1];
}
...
void draw() {
...

        // switch page
        /*
        vinfo.yoffset = cur_page * vinfo.yres;
        vinfo.activate = FB_ACTIVATE_VBL;
        if (ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo)) {
            printf("Error panning display.\n");
        }
        */
        vx = 0;
        vy = cur_page * vinfo.yres;
        set_fb_voffs(&vx, &vy);
        
        //usleep(1000000 / fps);

}

...
// main

    // open a char device file used for communicating with kernel mbox driver
    mboxfd = open(DEVICE_FILE_NAME, 0);
    if (mboxfd < 0) {
        printf("Can't open device file: %s\n", DEVICE_FILE_NAME);
        printf("Try creating a device file with: mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
        
    }
...

From the "vcio.h" we are using the two defines: DEVICE_FILE_NAME "char_dev" and . This char_dev is a special file for communicating with the mailbox. The file must be created using the command mknod (see man):
$ sudo mknod char_dev c 100 0
Save the code as say fbtestXII.c (full source in GitHub), download the vcio.h file to the same directory, build with:
gcc -lrt -o fbtestXII fbtestXII.c
(as the code uses the clock functions from librt) and run with ./fbtestXII. This should display the same gliding and bouncing rectangle, but this time with no tearing and with minimal flicker.

The program outputs the timing info - the (most likely) 16 seconds (and some 700 ms) comes from the fps = 100 and secs = 10 ...it is quite obvious that since the screen refresh is now tied to the vertical sync, we 'only' get 60 fps and 100 * 10 = 1000 loops takes 1000 / 60 = 16.6 s.

Now if we change the code a bit:
...
#define NUM_ELEMS 200
int xs[NUM_ELEMS];
int ys[NUM_ELEMS];
int dxs[NUM_ELEMS];
int dys[NUM_ELEMS];
...
void draw() {

    int i, x, y, w, h, dx, dy;
    struct timespec pt;
    struct timespec ct;
    struct timespec df;

    // rectangle dimensions
    w = vinfo.yres / 10;
    h = w;

    // start position (upper left)
    x = 0;
    y = 0;
    int n;
    for (n = 0; n < NUM_ELEMS; n++) {
        int ex = rand() % (vinfo.xres - w); 
        int ey = rand() % (vinfo.yres - h);
        //printf("%d: %d,%d\n", n, ex, ey);
        xs[n] = ex;
        ys[n] = ey;
        int edx = (rand() % 10) + 1; 
        int edy = (rand() % 10) + 1;
        dxs[n] = edx;
        dys[n] = edy;
    }

    // move step 'size'
    dx = 1;
    dy = 1;

    int fps = 60;
    int secs = 10;
    
    int vx, vy;

    clock_gettime(CLOCK_REALTIME, &pt);
    
    // loop for a while
    for (i = 0; i < (fps * secs); i++) {

        // change page to draw to (between 0 and 1)
        cur_page = (cur_page + 1) % 2;
    
        // clear the previous image (= fill entire screen)
        clear_screen(0);
        
        for (n = 0; n < NUM_ELEMS; n++) {
            x = xs[n];
            y = ys[n];
            dx = dxs[n];
            dy = dys[n];
            
            // draw the bouncing rectangle
            fill_rect(x, y, w, h, (n % 15) + 1);

            // move the rectangle
            x = x + dx;
            y = y + dy;

            // check for display sides
            if ((x < 0) || (x > (vinfo.xres - w))) {
                dx = -dx; // reverse direction
                x = x + 2 * dx; // counteract the move already done above
            }
            // same for vertical dir
            if ((y < 0) || (y > (vinfo.yres - h))) {
                dy = -dy;
                y = y + 2 * dy;
            }

            xs[n] = x;
            ys[n] = y;
            dxs[n] = dx;
            dys[n] = dy;
        }
        
        // switch page
        vx = 0;
        vy = cur_page * vinfo.yres;
        set_fb_voffs(&vx, &vy);
        
    }

    clock_gettime(CLOCK_REALTIME, &ct);
    df = timediff(pt, ct);
    printf("done in %ld s %5ld ms\n", df.tv_sec, df.tv_nsec / 1000000);
}
...
(full source)...and build with (optimisation on):
gcc -O2 -lrt -o fbtestXIII fbtestXIII.c
...we should get 200 colorful, bouncing 'sprites' going all over the screen:
Using the "char_dev" (especially as it has to be created as root) is not the most elegant way, but so far the only solution I know (if we want to stick to the fb) and at least for some uses this may be quite enough.

[Continued in part X+2>

Friday, 14 March 2014

Low-level Graphics on Raspberry Pi (part X)

Now that we have been gradually building the example program to allow us to do something interesting - how about trying a bit of animation?
...
// helper function to draw a rectangle in given color
void fill_rect(int x, int y, int w, int h, int c) {
    int cx, cy;
    for (cy = 0; cy < h; cy++) {
        for (cx = 0; cx < w; cx++) {
            put_pixel(x + cx, y + cy, c);
        }
    }
}

// helper function to clear the screen - fill whole 
// screen with given color
void clear_screen(int c) {
    memset(fbp, c, vinfo.xres * vinfo.yres);
}

void draw() {

    int i, x, y, w, h, dx, dy;

    // start position (upper left)
    x = 0;
    y = 0;
    // rectangle dimensions
    w = vinfo.yres / 10;
    h = w;
    // move step 'size'
    dx = 1;
    dy = 1;

    int fps = 100;
    int secs = 10;
    
    // loop for a while
    for (i = 0; i < (fps * secs); i++) {

        // clear the previous image (= fill entire screen)
        clear_screen(8);
        
        // draw the bouncing rectangle
        fill_rect(x, y, w, h, 15);

        // move the rectangle
        x = x + dx;
        y = y + dy;

        // check for display sides
        if ((x < 0) || (x > (vinfo.xres - w))) {
            dx = -dx; // reverse direction
            x = x + 2 * dx; // counteract the move already done above
        }
        // same for vertical dir
        if ((y < 0) || (y > (vinfo.yres - h))) {
            dy = -dy;
            y = y + 2 * dy;
        }
        
        usleep(1000000 / fps);
        // to be exact, would need to time the above and subtract...
    }

}
...

Save as fbtestX.c (complete code in GitHub) - build with make fbtestX. This should give us a moving white rectangle that bounces off the screen sides... Unfortunately the updates are not smooth (at least on most displays) - there is a quite prominent tearing effect.

Linux framebuffer interface does define some methods to overcome this - we could make the framebuffer virtual size double the height of the (smaller) physical one using the FBIOPUT_VSCREENINFO call:
  // Set variable info
  vinfo.xres = 640; // try a smaller resolution
  vinfo.yres = 480;
  vinfo.xres_virtual = 640;
  vinfo.yres_virtual = 960; // double the physical
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }

  //long int screensize = vinfo.xres * vinfo.yres;
  // have to use the virtual size for the mmap...
  long int screensize = vinfo.xres_virtual * vinfo.yres_virtual;

And change our drawing loop to use the two halves of the virtual buffer using a call to FBIOPAN_DISPLAY for page-flipping tied to a vertical sync using FB_ACTIVATE_VBL:
    int vs = 0;
    // initially show upper half (0) - draw to lower half (1)
    int cur_half = 1;
    for (i = 0; i < 1000; i++) {

        fill_rect(x, y, 40, 40, 4);

        x = x + dx;
        y = y + dy;

        if ((x < 0) || (x > (vinfo.xres - 40)) {
            dx = -dx;
            x = x + 2 * dx;
        }
        if ((y < 0) || (y > (vinfo.yres - 40)) {
            dy = -dy;
            y = y + 2 * dy;
        }

        // switch page
        vinfo.yoffset = cur_page * vinfo.yres;
        vinfo.activate = FB_ACTIVATE_VBL;
        if (ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo)) {
            printf("Error panning display.\n");
        }
    }

Unfortunately these calls have not been implemented in the RPi framebuffer driver (does not seem to be in later versions either yet, see also http://www.raspberrypi.org/phpBB3/viewtopic.php?f=67&t=19073). So running the above code (full code) results in repeated output of Error panning display. and the rectangle flashing even worse (as we miss every second screen update by drawing outside of the visible area).

[Continued in next part]

[UPDATE on changes in the fb driver here]

Wednesday, 3 April 2013

Low-level Graphics on Raspberry Pi (part eight)

Last time, we looked at the differences between display modes regarding color depths. Another attribute of a display mode is the resolution - by default, RPi uses the full HD resolution 1920x1080 as we saw in the first part (of course depending on the actual display attached).

Basically this can be changed to (almost) anything if needed/wanted - one obvious reason would be to limit the number of pixels RPi has to handle. You might have noticed that the color gradients in the previous example appear on the screen somewhat slowly (especially if using the full HD resolution)?

Let's experiment a bit... As briefly mentioned before, there is an utility for changing the frame buffer parameters called fbset (see man fbset). Now if we assume the original resolution is the full HD, running fbtest7 would look like:
$ ./fbtest7
The framebuffer device was opened successfully.
Original 1920x1080, 16bpp
$
...and the image appears like a roller blind drawn down. Now try the following sequence of commands (presented with output, so type only the text on lines starting with $ sign, not including the sign):
$ fbset -g 960 540 960 540 16
$ ./fbtest7
The framebuffer device was opened successfully.
Original 960x540, 16bpp
$ fbset -g 640 360 640 360 16
$ ./fbtest7
The framebuffer device was opened successfully.
Original 640x360, 16bpp
$ fbset -g 448 256 448 256 16
$ ./fbtest7
The framebuffer device was opened successfully.
Original 448x256, 16bpp
$
...with about the same image appearing faster and faster. Also note that even though we change the resolution to smaller and smaller, the RPI VideoCore GPU scales the image up to fill the entire display - hardware accelerated up-scaling for free ;)

The effect on the image quality may not be that apparent with the color gradient example, so let's try something possibly a bit more descriptive:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

// 'global' variables to store screen info
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

// helper function to 'plot' a pixel in given color
void put_pixel(int x, int y, int c)
{
    // calculate the pixel's byte offset inside the buffer
    unsigned int pix_offset = x + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    *((char*)(fbp + pix_offset)) = c;

}

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
void draw() {

    int x, y;

    // fill the screen with blue
    memset(fbp, 1, vinfo.xres * vinfo.yres);
    
    // white horizontal lines every 10 pixel rows
    for (y = 0; y < (vinfo.yres); y+=10) {
        for (x = 0; x < vinfo.xres; x++) {
            put_pixel(x, y, 15);
        }
    }

    // white vertical lines every 10 pixel columns
    for (x = 0; x < vinfo.xres; x+=10) {
        for (y = 0; y < (vinfo.yres); y++) {
            put_pixel(x, y, 15);
        }
    }
    
    int n;
    // select smaller extent 
    // (just in case of a portrait mode display)
    n = (vinfo.xres < vinfo.yres) ? vinfo.xres : vinfo.yres;
    // red diagonal line from top left
    for (x = 0; x < n; x++) {
        put_pixel(x, x, 4);
    }

}

// application entry point
int main(int argc, char* argv[])
{

    int fbfd = 0;
    struct fb_var_screeninfo orig_vinfo;
    long int screensize = 0;


    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd) {
      printf("Error: cannot open framebuffer device.\n");
      return(1);
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
      printf("Error reading variable information.\n");
    }
    printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
       vinfo.bits_per_pixel );

    // Store for reset (copy vinfo to vinfo_orig)
    memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

    // Change variable info - force 8 bit
    vinfo.bits_per_pixel = 8;
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
      printf("Error setting variable information.\n");
    }
    
    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
      printf("Error reading fixed information.\n");
    }

    // map fb to user mem 
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
    fbp = (char*)mmap(0, 
              screensize, 
              PROT_READ | PROT_WRITE, 
              MAP_SHARED, 
              fbfd, 
              0);

    if ((int)fbp == -1) {
        printf("Failed to mmap.\n");
    }
    else {
        // draw...
        draw();
        sleep(5);
    }

    // cleanup
    munmap(fbp, screensize);
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
        printf("Error re-setting variable information.\n");
    }
    close(fbfd);

    return 0;
  
}

Save that code as fbtest8.c, compile with make fbtest8 and execute the following command sequence:
$ fbset -g 1920 1080 1920 1080 16
$ ./fbtest8
The framebuffer device was opened successfully.
Original 1920x1080, 16bpp
$ fbset -g 960 540 960 540 16
$ ./fbtest8
The framebuffer device was opened successfully.
Original 960x540, 16bpp
$ fbset -g 640 360 640 360 16
$ ./fbtest8
The framebuffer device was opened successfully.
Original 640x360, 16bpp
$ fbset -g 448 256 448 256 16
$ ./fbtest8
The framebuffer device was opened successfully.
Original 448x256, 16bpp
$
...which should produce images like the ones here (the first one at 1920x1080 and the second at 448x256):

Of course the resolution can be changed programmatically too (no need to leave it to the end-user) - change the variable info setting lines in the main to:
    // Change variable info - force 8 bit and resolution
    vinfo.bits_per_pixel = 8;
    vinfo.xres = 320;
    vinfo.yres = 240;
    vinfo.xres_virtual = vinfo.xres;
    vinfo.yres_virtual = vinfo.yres;
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
      printf("Error setting variable information.\n");
    }
Save as fbtest8b.c, compile with make fbtest8b and execute with ./fbtest8b ...and you should get the same image with even bigger pixels ...centered horizontally at the middle of the display with black borders at sides (if you have the typical widescreen display in landscape mode).

So it seems that VC GPU scales to 'best fit' keeping pixels square - if the specified resolution conforms to the display aspect ratio, the image will fill the entire display - if it does not, there will be black borders at either left&right or top&bottom. You might want to experiment with different xres and yres values (for example 512x256, more ideas maybe here) to find a suitable resolution for your use-case - bearing in mind that the GPU seems (based on the hello_pi examples and experimenting) to expect the xres to be a multiple of 32 and yres of 16 - if not, there may be some side effects... For example, for the 'fire effect' in the screenshot in part two, I had to go as low as 320x240 to get a bearable frame-rate.

Code available also in GitHub

[Continued in part X]

Tuesday, 2 April 2013

Low-level Graphics on Raspberry Pi (part seven)

In the previous example, we produced the same image in different display modes (color depths). Let's see if we can find some difference between the modes.

This example draws three color gradient circles partly overlapping:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

// 'global' variables to store screen info
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

void put_pixel_RGB24(int x, int y, int r, int g, int b)
{
    // calculate the pixel's byte offset inside the buffer
    // note: x * 3 as every pixel is 3 consecutive bytes
    unsigned int pix_offset = x * 3 + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    *((char*)(fbp + pix_offset)) = b;
    *((char*)(fbp + pix_offset + 1)) = g;
    *((char*)(fbp + pix_offset + 2)) = r;

}

void put_pixel_RGB565(int x, int y, int r, int g, int b)
{
    // calculate the pixel's byte offset inside the buffer
    // note: x * 2 as every pixel is 2 consecutive bytes
    unsigned int pix_offset = x * 2 + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    // but a bit more complicated for RGB565
    //unsigned short c = ((r / 8) << 11) + ((g / 4) << 5) + (b / 8);
    unsigned short c = ((r / 8) * 2048) + ((g / 4) * 32) + (b / 8);
    // write 'two bytes at once'
    *((unsigned short*)(fbp + pix_offset)) = c;

}

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
void draw() {

    int x, y;
    int r, g, b;
    int dr;
    int cr = vinfo.yres / 3;
    int cg = vinfo.yres / 3 + vinfo.yres / 4;
    int cb = vinfo.yres / 3 + vinfo.yres / 4 + vinfo.yres / 4;

    for (y = 0; y < (vinfo.yres); y++) {
        for (x = 0; x < vinfo.xres; x++) {
            dr = (int)sqrt((cr - x)*(cr - x)+(cr - y)*(cr - y));
            r = 255 - 256 * dr / cr;
            r = (r >= 0) ? r : 0;
            dr = (int)sqrt((cg - x)*(cg - x)+(cr - y)*(cr - y));
            g = 255 - 256 * dr / cr;
            g = (g >= 0) ? g : 0;
            dr = (int)sqrt((cb - x)*(cb - x)+(cr - y)*(cr - y));
            b = 255 - 256 * dr / cr;
            b = (b >= 0) ? b : 0;

            if (vinfo.bits_per_pixel == 16) {
                put_pixel_RGB565(x, y, r, g, b);
            }
            else {
                put_pixel_RGB24(x, y, r, g, b);
            }
        }
    }

}

// application entry point
int main(int argc, char* argv[])
{

    int fbfd = 0;
    struct fb_var_screeninfo orig_vinfo;
    long int screensize = 0;


    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd) {
      printf("Error: cannot open framebuffer device.\n");
      return(1);
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
      printf("Error reading variable information.\n");
    }
    printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
       vinfo.bits_per_pixel );

    // Store for reset (copy vinfo to vinfo_orig)
    memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
      printf("Error reading fixed information.\n");
    }

    // map fb to user mem 
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
    fbp = (char*)mmap(0, 
              screensize, 
              PROT_READ | PROT_WRITE, 
              MAP_SHARED, 
              fbfd, 
              0);

    if ((int)fbp == -1) {
        printf("Failed to mmap.\n");
    }
    else {
        // draw...
        draw();
        sleep(5);
    }

    // cleanup
    munmap(fbp, screensize);
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
        printf("Error re-setting variable information.\n");
    }
    close(fbfd);

    return 0;
  
}

Save the new code (available also on GitHub) to fbtest7.c and compile using this command (as we now use the sqrt() function the math library, we need to tell the linker this with the '-lm' directive):
gcc -o fbtest7 -lm fbtest7.c
And then execute the following sequence:
fbset -depth 16
./fbtest7
fbset -depth 24
./fbtest7
...note how in the 16 bit mode there are noticeable bands in the color gradients - less so in the 24 bit mode. Note that the above code does not work in the 8 bit mode - it could be modified to produce similar enough image by setting the palette values suitably.

[Continued in part eight]

Friday, 8 March 2013

Low-level Graphics on Raspberry Pi (part six)

In the previous posts we have been plotting pixels using a 8 bit, 256 color, palette display mode. In this mode, every byte of the framebuffer (mmap'ed) memory block present one byte and the value of the byte is an index to the palette (see part three). So to get the color bars in the previous examples, we have plotted values of 0 (zero) to the first bar (black), values of 1 (one) to the second bar (blue) and so on... This picture illustrates the idea - each cell presents one pixel (some columns skipped for compacting), showing both the byte value and the resulting color:
Here is the pixel plotting function used:
void put_pixel(int x, int y, int c)
{
    // calculate the pixel's byte offset inside the buffer
    unsigned int pix_offset = x + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    *((char*)(fbp + pix_offset)) = c;

}

So we are storing one byte (that 'char *' there) ...obviously we could (/should) have defined the color parameter c as a char too, but... (the above code takes the lowest byte of the four byte integer variable, so works as is).

Well, how about other display modes? We have noticed already (part one) that the default mode on RPi is 16 bit and quite often one comes across mentions of 24 bit and 32 bit modes. I suppose the easiest of these to begin with would be the 24 bit mode: 3 bytes per pixel - one byte per each RGB (red, green, blue) value. The RGB values are very similar to the values in the palette for the 8 bit mode. To illustrate this, in the following image we have the two leftmost pixels of the two first color bars - the first pixel occupies three first bytes of the memory buffer. For the black pixels all three byte values are zeroes - for the blue pixels the 'R' and 'G' bytes are zero and 'B' byte is 255 (= full blue):
This could be implemented as the following pixel plotting code:
void put_pixel_RGB24(int x, int y, int r, int g, int b)
{

    // calculate the pixel's byte offset inside the buffer
    // note: x * 3 as every pixel is 3 consecutive bytes
    unsigned int pix_offset = x * 3 + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    *((char*)(fbp + pix_offset)) = r;
    *((char*)(fbp + pix_offset + 1)) = g;
    *((char*)(fbp + pix_offset + 2)) = b;

}


The RPi default 16 bit RGB565 is slightly more complex - there are 2 bytes per pixel and the color components are encoded so that 5 first bits are for the red, 6 middle bits for green and 5 last bits for blue:
In the format similar to the above ones, the memory buffer would look something like this:
...the blue value 31 comes from the fact that there are 5 bits for blue and the binary value of 0b11111 is 31 in decimal. Full red would be 0b1111100000000000 (63488) so the bytes for full red would be 248 and 0 - full green would be 0b0000011111100000 (2016) so the bytes 7 and 224.

The RGB565 pixe plotting function would be something along this:
void put_pixel_RGB565(int x, int y, int r, int g, int b)
{

    // calculate the pixel's byte offset inside the buffer
    // note: x * 2 as every pixel is 2 consecutive bytes
    unsigned int pix_offset = x * 2 + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    // but a bit more complicated for RGB565
    unsigned short c = ((r / 8) << 11) + ((g / 4) << 5) + (b / 8);
    // or: c = ((r / 8) * 2048) + ((g / 4) * 32) + (b / 8);
    // write 'two bytes at once'
    *((unsigned short*)(fbp + pix_offset)) = c;

}

The red value has 5 bits, so can be in the range 0-31, therefore divide the original 0-255 value by 8. It is stored in the first 5 bits, so multiply by 2048 or shift 11 bits left. The green has 6 bits, so can be in the range 0-63, divide by 4, and multiply by 32 or shift 5 bits left. Finally the blue has 5 bits and is stored at the last bits, so no need to move.

The 32 bit mode is usually so called ARGB - where there are 4 bytes per pixel, A standing for 'alpha' = transparency and the rest just like in the RGB24. Modifying the put_pixel_RGB24() to put_pixel_ARGB32() should be trivial.

Note that there are several other display (and especially image and video) modes as well, but the ones covered here are the most standard ones for Linux framebuffers.

To test the above pixel plotting functions, we could try the following (continuing from the code in part five):
void draw() {

    int x, y;

    for (y = 0; y < (vinfo.yres / 2); y++) {
        for (x = 0; x < vinfo.xres; x++) {

            // color based on the 16th of the screen width
            int c = 16 * x / vinfo.xres;
    
            if (vinfo.bits_per_pixel == 8) {
                put_pixel(x, y, c);
            }
            else if (vinfo.bits_per_pixel == 16) {
                put_pixel_RGB565(x, y, def_r[c], def_g[c], def_b[c]);
            }
            else if (vinfo.bits_per_pixel == 24) {
                put_pixel_RGB24(x, y,  def_r[c], def_g[c], def_b[c]);
            }

        }
    }

}

...

// in main()

// comment out setting the bit depth

    // Change variable info
    /* use: 'fbset -depth x' to test different bpps
    vinfo.bits_per_pixel = 8;
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
      printf("Error setting variable information.\n");
    }
    */

// also can comment out setting the palette...

// change the calculation of required memory
    // map fb to user mem 
    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

...
Save the new code to fbtest6.c, compile and then execute the following sequence:
fbset -depth 8
./fbtest6
fbset -depth 24
./fbtest6
fbset -depth 16
./fbtest6
...this should yield three times the exact same color bars. Full source code available in github.

[Continued in part seven]

Thursday, 7 March 2013

Low-level Graphics on Raspberry Pi (part five)

So far we have used some fixed palette values for the colors (see part four) - namely 0-15 to draw the 16 bars. These happen to be the default Linux framebuffer colors (slight variations between distributions to be expected). Since we are using palette indexes, we don't really know what are the actual colors output. The Linux framebuffer interface provides a function FBIOGETCMAP to read the color palette:
  // Get palette information
  unsigned short r[256];
  unsigned short g[256];
  unsigned short b[256];
  unsigned short a[256];
  memset(r, 0, 256 * sizeof(unsigned short));
  memset(g, 0, 256 * sizeof(unsigned short));
  memset(b, 0, 256 * sizeof(unsigned short));
  memset(a, 0, 256 * sizeof(unsigned short));
  struct fb_cmap pal;
  pal.start = 0;
  pal.len = 0;
  pal.red = r;
  pal.green = g;
  pal.blue = b;
  pal.transp = a;
  if (ioctl(fbfd, FBIOGETCMAP, &pal)) {
    printf("Error reading palette.\n");
  }
...would give us the red, green and blue components of the colors in the palette. So most likely r[0]=0, g[0]=0, b[0]=0 for the color index 0 i.e. black (#000000 in HTML) - r[1]=0, g[1]=0, b[1]=255 for color index 1 i.e. blue (#0000FF). Colors from index 16 onwards are most likely all black.

Unfortunately reading this information is not supported by the RPi framebuffer driver ...this surely is not how I wanted these posts to be Raspberry Pi specific :(

After a bit of trial and error (and 'stealing' the initial values from another Linux system using the above code), I managed to get pretty close to the RPi default colors. In this example we initialise some hard-coded values (note that I use the possibly more familiar range 0-255, matching many systems like the HTML color scheme) for the colors (the enum is not used here but might prove handy later on - think of put_pixel(x, y, RED)), set them to the system palette starting at index 16 (i.e. just after the default colors) and then draw the familiar color bars at the top half of the screen - and bars with same colors but using different palette indexes at the lower half for comparison:
...
// after includes

// default framebuffer palette
typedef enum {
  BLACK        =  0, /*   0,   0,   0 */
  BLUE         =  1, /*   0,   0, 172 */
  GREEN        =  2, /*   0, 172,   0 */
  CYAN         =  3, /*   0, 172, 172 */
  RED          =  4, /* 172,   0,   0 */
  PURPLE       =  5, /* 172,   0, 172 */
  ORANGE       =  6, /* 172,  84,   0 */
  LTGREY       =  7, /* 172, 172, 172 */
  GREY         =  8, /*  84,  84,  84 */
  LIGHT_BLUE   =  9, /*  84,  84, 255 */
  LIGHT_GREEN  = 10, /*  84, 255,  84 */
  LIGHT_CYAN   = 11, /*  84, 255, 255 */
  LIGHT_RED    = 12, /* 255,  84,  84 */
  LIGHT_PURPLE = 13, /* 255,  84, 255 */
  YELLOW       = 14, /* 255, 255,  84 */
  WHITE        = 15  /* 255, 255, 255 */
} COLOR_INDEX_T;

static unsigned short def_r[] = 
    { 0,   0,   0,   0, 172, 172, 172, 172,  
     84,  84,  84,  84, 255, 255, 255, 255};
static unsigned short def_g[] = 
    { 0,   0, 172, 172,   0,   0,  84, 172,  
     84,  84, 255, 255,  84,  84, 255, 255};
static unsigned short def_b[] = 
    { 0, 172,   0, 172,   0, 172,   0, 172,  
     84, 255,  84, 255,  84, 255,  84, 255};

...

void draw() {

    int x, y;

    for (y = 0; y < (vinfo.yres / 2); y++) {
        for (x = 0; x < vinfo.xres; x++) {

            // color based on the 16th of the screen width
            int c = 16 * x / vinfo.xres;
    
            // default colors at upper half
            put_pixel(x, y, c);
            // our own colors at lower half
            put_pixel(x, y + (vinfo.yres / 2), c + 16);

        }
    }

}

...
// in main after opening the fb device
    // Set palette
    unsigned short r[256];
    unsigned short g[256];
    unsigned short b[256];
    memset(&r, 0, 256); // initialise with zeros
    memset(&g, 0, 256);
    memset(&b, 0, 256);
    int i;
    for(i = 0; i < 16; i++) {
        // copy the hard-coded values
        // note that Linux provides more precision (0-65535),
        // so we multiply ours (0-255) by 256
        r[i] = def_r[i] << 8;
        g[i] = def_g[i] << 8;
        b[i] = def_b[i] << 8;
    } 
    struct fb_cmap pal;
    pal.start = 16; // start our colors after the default 16
    pal.len = 256; // kludge to force bcm fb drv to commit palette...
    pal.red = r;
    pal.green = g;
    pal.blue = b;
    pal.transp = 0; // we want all colors non-transparent == null
    if (ioctl(fbfd, FBIOPUTCMAP, &pal)) {
        printf("Error setting palette.\n");
    }

...

...I only hope my monitor color tolerance is about ok and your eyes not much sharper than mine ;)

Now you could go and fill the palette with any colors you can come up with and draw something interesting. If you use the first 16 color 'slots' in the palette for your own colors, remember to set the default colors at the end of your program:
  // before closing the fbfd
  // reset palette
  palette.start = 0;
  palette.len = 256;
  palette.red = def_red;
  palette.green = def_green;
  palette.blue = def_blue;
  palette.transp = 0;
  if (ioctl(fbfd, FBIOPUTCMAP, &palette)) {
    printf("Error setting palette.\n");
  }
...otherwise when returning to the console, you might not be able to read any of the text ;) In case this happens (or some other issue with the graphics garbles the screen), one way to fix thing is to type (possibly 'blindly') the commands:
fbset -depth 32
fbset -depth 16
...this (changes the color depth and resets back to the RPi default and) should clear refresh the screen.

[Continued in part six]

Sunday, 3 March 2013

Low-level Graphics on Raspberry Pi (part four)

In the part three we saw how to plot individual pixels in the framebuffer. Now let's turn the plot-pixel code into a reusable function.

First we need to move some of the variables outside of the main function, so we can access them in the new function - then we just move the pixel plotting code from the main into the new function and make main to call it. We will also move the 'draw' code into another function to separate it from the main and make it easier to read.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

// 'global' variables to store screen info
char *fbp = 0;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;

// helper function to 'plot' a pixel in given color
void put_pixel(int x, int y, int c)
{
    // calculate the pixel's byte offset inside the buffer
    unsigned int pix_offset = x + y * finfo.line_length;

    // now this is about the same as 'fbp[pix_offset] = value'
    *((char*)(fbp + pix_offset)) = c;

}

// helper function for drawing - no more need to go mess with
// the main function when just want to change what to draw...
void draw() {

    int x, y;

    for (y = 0; y < (vinfo.yres / 2); y++) {
        for (x = 0; x < vinfo.xres; x++) {

            // color based on the 16th of the screen width
            int c = 16 * x / vinfo.xres;
    
            // call the helper function
            put_pixel(x, y, c);

        }
    }

}

// application entry point
int main(int argc, char* argv[])
{

    int fbfd = 0;
    struct fb_var_screeninfo orig_vinfo;
    long int screensize = 0;


    // Open the file for reading and writing
    fbfd = open("/dev/fb0", O_RDWR);
    if (!fbfd) {
      printf("Error: cannot open framebuffer device.\n");
      return(1);
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get variable screen information
    if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
      printf("Error reading variable information.\n");
    }
    printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
       vinfo.bits_per_pixel );

    // Store for reset (copy vinfo to vinfo_orig)
    memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

    // Change variable info
    vinfo.bits_per_pixel = 8;
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
      printf("Error setting variable information.\n");
    }

    // Get fixed screen information
    if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
      printf("Error reading fixed information.\n");
    }

    // map fb to user mem 
    screensize = vinfo.xres * vinfo.yres;
    fbp = (char*)mmap(0, 
              screensize, 
              PROT_READ | PROT_WRITE, 
              MAP_SHARED, 
              fbfd, 
              0);

    if ((int)fbp == -1) {
        printf("Failed to mmap.\n");
    }
    else {
        // draw...
        draw();
        sleep(5);
    }

    // cleanup
    munmap(fbp, screensize);
    if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
        printf("Error re-setting variable information.\n");
    }
    close(fbfd);

    return 0;
  
}

Now save the file as fbtest4.c, compile with make fbtest4.c and execute ./fbtest4 - you should see the same vertical color bars at the upper half of the screen as in part three. Making the put_pixel function should help to come up with new ideas for images to draw...

[Continued in part five]

Tuesday, 22 January 2013

Low-level Graphics on Raspberry Pi (part three)

So, after memmap'ing the framebuffer (see part two), it appears as a contiguous section of RAM - an array of bytes. That is why we declared the 'frame buffer pointer' variable fbp as:
  char *fbp = 0;
and the memmap:
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 0);
behaves about the same as if we allocated the buffer ourselves like:
#include <stdio.h>

int main(int argc, char* argv[])
{
    char *fbp = 0;
    char fb[100];

    fb[0] = 'a';

    fbp = fb; // similar to mmap ...

    printf("fb[0] = %c\n", fb[0]);
    printf("*fbp = %c\n", *fbp);
}
where the pointer variable fbp points to the first byte of the array fb. You might want to read more about C data-types and arrays vs pointers from your preferred source of C programming information...

Basically all RAM memory can be seen as an array of bytes - something like this:
The same could also be used to illustrate a particular array within the memory, where the array start byte would be the 'box zero' (fb[0] in the above example) and so on until the length of the allocated array at 'box n' (fb[99]).

As the framebuffer represents the two-dimensional display 'surface', we could illustrate the pixels as:
In case of a 8 bpp ('8 bits per pixel', 256 color, one byte per pixel) display mode, this would directly map to the framebuffer memory layout: so the "width'th" byte (fb[width]) in the array would represent the left-most pixel on the second (from the top) scanline and the 5th pixel on the 3rd row (highlighted in grey) would match the byte at '3 x width + 5' (fb[3 * width + 5]).

However, there are numerous other display modes generally used and in fact the default mode of RPi is 16 bpp (as seen in part one). This means that to be able to handle the framebuffer as 'just bytes', we need to switch to 8 bpp display mode.

This can be done from the command-line using the command:
fbset -depth 8
Now run the fbtest from the part one - it should output '..., 8 bpp'. If you try the fbtest2 from part two, you will most likely not get the same result as in 16 bpp mode - probably just a black screen (will have to verify this). To reset bit-depth back to original use '-depth 16' (or whatever fbtest said it was before).

So what's with the drawing now? The reason to the different result is that in 16 bpp mode, the bytes (byte-pairs) in the buffer describe the actual pixel color - in 8 bpp mode, the byte value is an index to a palette. In 16 bpp a value of 0 (in both bytes) would yield black - in 8 bit the value 0 yields the color stored in the palette at index 0 and could basically be anything in the RGB colorspace (incidently, the color 0 in the default Linux framebuffer palette seems to be black ...but it could be set to anything).

Speaking of the default palette: why not take a look what colors it contains? This example (building on the previous examples) changes the display mode to 8 bpp, draws vertical bars of varying colors (the 16 colors of the default palette), pauses for 5 seconds and restores the display settings:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>

// application entry point
int main(int argc, char* argv[])
{
  int fbfd = 0;
  struct fb_var_screeninfo orig_vinfo;
  struct fb_var_screeninfo vinfo;
  struct fb_fix_screeninfo finfo;
  long int screensize = 0;
  char *fbp = 0;


  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("Original %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // Store for reset (copy vinfo to vinfo_orig)
  memcpy(&orig_vinfo, &vinfo, sizeof(struct fb_var_screeninfo));

  // Change variable info
  vinfo.bits_per_pixel = 8;
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &vinfo)) {
    printf("Error setting variable information.\n");
  }
  
  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  // map fb to user mem 
  screensize = finfo.smem_len;
  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 
                    0);

  if ((int)fbp == -1) {
    printf("Failed to mmap.\n");
  }
  else {
    // draw...
    int x, y;
    unsigned int pix_offset;

    for (y = 0; y < (vinfo.yres / 2); y++) {
      for (x = 0; x < vinfo.xres; x++) {

        // calculate the pixel's byte offset inside the buffer
        // see the image above in the blog...
        pix_offset = x + y * finfo.line_length;

        // now this is about the same as fbp[pix_offset] = value
        *((char*)(fbp + pix_offset)) = 16 * x / vinfo.xres;

      }
    }

    sleep(5);
  }

  // cleanup
  munmap(fbp, screensize);
  if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &orig_vinfo)) {
    printf("Error re-setting variable information.\n");
  }
  close(fbfd);

  return 0;
  
}

Save as fbtest3.c, 'make fbtest3' and run as './fbtest3' - this should produce vertical color bars at the upper half of the screen:
Hmm, so far there has been very little Raspberry Pi specific stuff in this series - all the code should work on most (if not all) Linux systems as is ...maybe I should have thought more about the title ;)

[Continues in part four]

Sunday, 20 January 2013

Low-level Graphics on Raspberry Pi (part two)

In the part one we looked at how to get hold of the framebuffer and output some basic information about the display.

Not exactly very impressive, but a good start. Now let's actually draw something!

The framebuffer does not provide any functions/methods for drawing - instead, it just gives access to the 'raw' bytes of the buffer. Basically one could just use the standard file redirect '>' to output into the framebuffer:
some-bytes-from-somewhere > /dev/fb0
Obviously this is not very usable, but redirect the other way provides a quick-and-dirty raw screencapture that might come in handy:
cat /dev/fb0 > screenshot.raw
Proper way to access the buffer, is to memory map the file to a section of RAM using the mmap function.

Here we extend the example from part one - after opening the framebuffer device, we get also the fixed display information (to easily get the buffer size), map the file to the user accessible memory and finally draw something just setting the bytes in the buffer to some values:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>


int main(int argc, char* argv[])
{
  int fbfd = 0;
  struct fb_var_screeninfo vinfo;
  struct fb_fix_screeninfo finfo;
  long int screensize = 0;
  char *fbp = 0;

  // Open the file for reading and writing
  fbfd = open("/dev/fb0", O_RDWR);
  if (!fbfd) {
    printf("Error: cannot open framebuffer device.\n");
    return(1);
  }
  printf("The framebuffer device was opened successfully.\n");

  // Get fixed screen information
  if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) {
    printf("Error reading fixed information.\n");
  }

  // Get variable screen information
  if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) {
    printf("Error reading variable information.\n");
  }
  printf("%dx%d, %d bpp\n", vinfo.xres, vinfo.yres, 
         vinfo.bits_per_pixel );

  // map framebuffer to user memory 
  screensize = finfo.smem_len;

  fbp = (char*)mmap(0, 
                    screensize, 
                    PROT_READ | PROT_WRITE, 
                    MAP_SHARED, 
                    fbfd, 0);

  if ((int)fbp == -1) {
    printf("Failed to mmap.\n");
  }
  else {
    // draw...
    // just fill upper half of the screen with something
    memset(fbp, 0xff, screensize/2);
    // and lower half with something else
    memset(fbp + screensize/2, 0x18, screensize/2);
  }

  // cleanup
  munmap(fbp, screensize);
  close(fbfd);
  return 0;
}


Save the above code to a file called fbtest2.c - then compile and link:
make fbtest2

Run the executable:
./fbtest2

...you should see the upper half of the display turn white and the lower blue! Hmm, except if you are in some other display mode than 16 bpp, you might get a different result...

With a bit of imaginative manipulation of what byte values to put into the buffer, one could draw more complex images - for example something like this:
...I will be getting into 'pixel plotting' in the following parts - hopefully soon...

[Continues in part three]