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]

Thursday, 3 April 2014

Rant "javac: invalid target release: 1.6"

Argh, now I just cannot help myself not to blurt this out. One of the most frustrating small knacks I have come up against lately.

At work, I need to rebuild one customer specific Java web application using JDK 1.6 to match the base product change to target 1.6.

So I login to the build machine, check out that there is JDK 1.6 installed, update the Ant build.xml with new JDK location + target and start the build...
...
compile:
  [echo] ============================================================
  [echo]  Compiling source files
 [javac] Compiling 12 source files to C:\projects\xxx\build\classes
 [javac] javac: invalid target release: 1.6
 [javac] Usage: javac  
 [javac] where possible options include:
 [javac]   -g                         Generate all debugging info
...
WHAT??!! I scratch my head and try editing all the related properties and build script files - trying out all combinations of parameters ...to no avail.

Ok... Google gives me a load of links to discussions like this one on stackoverflow:
Q:
problem in ant build
...
ant source and target to 1.6 en variable path to jdk 1.6

A:
It might be useful for some to know that ant looks at the JAVA_HOME 
variable when deciding which Java version to use

A:
Check the used Java-version with adding following line to your 
target (at the beginning): ...

etc
Well... In our ant script we tell the exact path to the javac executable, so it cannot be that - changing the JAVA_HOME obviously has no effect in this case either.

So to debug the issue I fire up a command prompt and try it manually:
C:\>%JAVA_HOME%\bin\javac -version
javac 1.6.0_21
javac: no source files
Usage: javac  
where possible options include:
...

C:\>%JAVA_HOME%\bin\javac -target 1.6
javac: invalid target release: 1.6
Usage: javac  
where possible options include:
...
BOOM!! There it is - the same error message outside of Ant.

Hmm, now lets try on another JDK 1.6 update:
C:\>set JAVA_HOME=C:\Progra~1\Java\jdk1.6.0_45

C:\>%JAVA_HOME%\bin\javac -version
javac 1.6.0_45

C:\>%JAVA_HOME%\bin\javac -target 1.6
javac: no source files
Usage: javac  
use -help for a list of possible options

...so there have been some broken JDK build(s) out there and the solution is to upgrade the JDK. Phew.

Luckily I get paid for this ;) ...and the Oracle JDK8 for Raspberry Pi (well, "Linux ARM v6/v7 Hard Float ABI") is still pretty awesome :D