Applying C - Framebuffer Graphics
Written by Harry Fairhead   
Monday, 10 June 2019
Article Index
Applying C - Framebuffer Graphics
Color
The Main Program

Finally we need to create a color value to store in the pixel. The problem here is that the format used depends on the number of bits used for each pixel and how the RGBA values are packed. This is coded by the fields in fb_var_screeninfo:

struct fb_bitfield red;	
struct fb_bitfield green;	
struct fb_bitfield blue;
struct fb_bitfield transp;

and the fb_bitfield stuct is:

struct fb_bitfield {
 __u32 offset;	/* beginning of bitfield	*/
 __u32 length;	/* length of bitfield		*/
 __u32 msb_right;	/* != 0 :Most significant
bit is right */ };

This is officially a legacy way of doing the job but it has been in use for so long that it is widely supported.

This allows you to pack the RGBA values correctly no matter what the format is. Pixels are always stored in an integer number of bytes and padding bits are added according. Suppose the red fb_bitfield was offset=16, length=8, msb_right=0 this would mean that the red color value was stored in the pixel starting at bit 16 and was 8 bits e.g 0x00RR0000. Suppose we have the color value stored in variables r, g, b and a, then we could assemble a 32 bit pixel value using:

uint32_t r=0x00,g=0x00,b=0xFF,a=0xFF;    
uint32_t pixel =(r<<vinfo.red.offset) |
(g<<vinfo.green.offset) |
(b<<vinfo.blue.offset) |
(a<<vinfo.transp.offset);

That is, shift by the offset and OR the results together. Notice that this simple way of packing a pixel color value doesn't take care of all of the possibilities - it doesn't deal with the possibility that the color values could be less than 8-bits or that the most significant bit could be on the right. However, this is typical of a 32 bit RGBA pixel format. In any real application you would have to do a lot more bit manipulation to ensure that your program worked with a range of display modes.

We can now store the pixel value in the framebuffer:

*((uint32_t*) (fbp + location)) = pixel;

Notice that we have to cast the pointer to ensure that the pixel is stored as a 4-byte value.

The modern way of doing the same job is to specify a FOURCC code. This is a set of four-character codes that specify the pixel format without specifying it in the way that the old API does. You simply have to know what a particular FOURCC code means and implement it. For example, the FOURCC code RGBA or 0x41424752 specifies the same format as used in the example above. The framebuffer signals that it supports FOURCC by setting a bit in the capability field - most don't.

It is time to put all of this information together and draw something on the screen.

Drawing a Line

As an example we can create a function that draws a pixel of a given color value on the screen and then use it to draw a line:

#include <stdio.h>
#include <stdlib.h>
#include <linux/fb.h>
#include <fcntl.h> 
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <inttypes.h>
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
size_t size;
uint8_t *fbp;
void setPixel(uint32_t x, uint32_t y, uint32_t r,
uint32_t g, uint32_t b, uint32_t a) { uint32_t pixel = (r << vinfo.red.offset)|
(g << vinfo.green.offset)| (b << vinfo.blue.offset)| (a << vinfo.transp .offset); uint32_t location = x*vinfo.bits_per_pixel/8 +
y*finfo.line_length; *((uint32_t*) (fbp + location)) = pixel; }
int main(int argc, char** argv) { int fd = open("/dev/fb0", O_RDWR); ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); ioctl(fd, FBIOGET_FSCREENINFO, &finfo); size = vinfo.yres * finfo.line_length; fbp = mmap(0, size, PROT_READ |
PROT_WRITE, MAP_SHARED, fd, 0); uint32_t x = 0; uint32_t y = 400; for (x = 0; x < 800; x++) { setPixel(x, y, 0xFF, 0xFF, 0x00, 0xFF); } return (EXIT_SUCCESS); }

The program assumes that the graphics are in 32-bit per pixel color mode.
If this isn't the case then to set the mode, just after the two existing ioctl calls, add:

vinfo.grayscale = 0;
vinfo.bits_per_pixel = 32;
ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);

Notice that the line drawn on the screen goes over any windows that might be in its way and it isn't persistent in the sense that if anything writes over it, such as a window, it is wiped out.

Bounce

As a very simple demonstration of using the framebuffer, let's bounce a ball around the screen - the whole screen, not just in a window. The basic idea is very simple - save the contents of a small block of the screen, draw a "ball" in this block and finally restore the original contents of screen.

We need some graphics utility functions to get started. The getRawPixel and setRawPixel functions simply work with a 32-bit RGBA value:

uint32_t getRawPixel(uint32_t x, uint32_t y) {
  uint32_t location = x * (vinfo.bits_per_pixel / 8) + 
y * finfo.line_length; return *((uint32_t*) (fbp + location)); } uint32_t setRawPixel(uint32_t x, uint32_t y,
uint32_t pixel) { uint32_t location = x * (vinfo.bits_per_pixel / 8) +
y * finfo.line_length; *((uint32_t*) (fbp + location)) = pixel; }

Using these it is easy to write a setPixel to a color function:

void setPixel(uint32_t x, uint32_t y, struct color c) {
  uint32_t pixel = (c.r << vinfo.red.offset)|
                     (c.g << vinfo.green.offset) |
(c.b << vinfo.blue.offset) |
(c.a << vinfo.transp .offset); setRawPixel(x, y, pixel); }

We don't need a getPixel function in this program. We do need a setBlock function to draw the ball:

void setBlock(uint32_t x, uint32_t y, uint32_t L,
struct color c) { for (int i = 0; i < L; i++) { for (int j = 0; j < L; j++) { setPixel(x + i, y + j, c); } } }

It draws a square block of width L in color c with top left-hand corner at x,y.



Last Updated ( Monday, 01 July 2019 )