eCos Reference Manual
Prev |  | Next

# Framebuffer Pixel Manipulation

## Name

Pixel Manipulation -- iterating over the display

## Synopsis

    #include <cyg/io/framebuf.h>

``CYG_FB_PIXEL0_VAR`(FRAMEBUF);`

``void CYG_FB_PIXEL0_SET`(FRAMEBUF, cyg_ucount16 x, cyg_ucount16 y);`

``void CYG_FB_PIXEL0_GET`(FRAMEBUF, cyg_ucount16 x, cyg_ucount16 y);`

``void CYG_FB_PIXEL0_ADDX`(FRAMEBUF, cyg_ucount16 incr);`

``void CYG_FB_PIXEL0_ADDY`(FRAMEBUF, cyg_ucount16 incr);`

``void CYG_FB_PIXEL0_WRITE`(FRAMEBUF, cyg_fb_colour colour);`

``cyg_fb_colour CYG_FB_PIXEL0_READ`(FRAMEBUF);`

``void CYG_FB_PIXEL0_FLUSHABS`(FRAMEBUF, cyg_ucount16 x0, cyg_ucount16 y0, cyg_ucount16 width, cyg_ucount16 height);`

``void CYG_FB_PIXEL0_FLUSHREL`(FRAMEBUF, cyg_ucount16 x0, cyg_ucount16 y0, cyg_ucount16 dx, cyg_ucount16 dy);`

## Description

A common requirement for graphics code is to iterate over parts of the framebuffer. Drawing text typically involves iterating over a block of pixels for each character, say 8 by 8, setting each pixel to either a foreground or background colour. Drawing arbitrary lines typically involves moving to the start position and then adjusting the x and y coordinates until the end position is reached, setting a single pixel each time around the loop. Drawing images which are not in the frame buffer's native format typically involves iterating over a block of pixels, from top to bottom and left to right, setting pixels as the image is decoded.

Functionality like this can be implemented in several ways. One approach is to use the pixel write primitive. Typically this involves some arithmetic to get from the x and y coordinates to a location within framebuffer memory so it is fairly expensive compared with a loop which just increments a pointer. Another approach is to write the data first to a separate buffer in memory and then use a block write primitive to move it to the framebuffer, but again this involves overhead. The eCos framebuffer support provides a third approach: a set of macros specifically for iterating over the frame buffer. Depending on the operation being performed and the details of the framebuffer implementation, these macros may be optimal or near-optimal. Obviously there are limitations. Most importantly the framebuffer device must be known at compile-time: the compiler can do a better job optimizing the code if information such as the frame buffer width are constant. Also each iteration must be performed within a single variable scope: it is not possible to do some of the iteration in one function, some in another.

## The Pixel Macros

All the pixel macros take a framebuffer identifier as their first argument. This is the same identifier that can be used with the other macros like `CYG_FB_WRITE_HLINE` and `CYG_FB_ON`, one of the entries in the configuration option `CYGDAT_IO_FRAMEBUF_DEVICES`. Using an invalid identifier will result in numerous compile-time error messages which may bear little resemblance to the original code. In the examples below it is assumed that `FRAMEBUF` has been `#define`'d to a suitable identifier.

Typical use of the pixel macros will look like this:

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        …
        CYG_FB_PIXEL0_FLUSHABS(FRAMEBUF, x, y, width, height);

The `VAR` macro will define one or more local variables to keep track of the current pixel position, as appropriate to the framebuffer device. The other pixel macros will then use these variables. For a simple 8bpp linear framebuffer there will be just a byte pointer. For a 1bpp display there may be several variables: a byte pointer, a bit index within that byte, and possibly a cached byte; using a cached value means that the framebuffer may only get read and written once for every 8 pixels, and the compiler may well allocate a register for the cached value; on some platforms framebuffer access will bypass the processor's main cache, so reading from or writing to framebuffer memory will be slow; reducing the number of framebuffer accesses may greatly improve performance.

Because the `VAR` macro defines one or more local variables it is normally placed at the start of a function or block, alongside other local variable definitions.

One the iteration has been completed there should be a `FLUSHABS` or `FLUSHREL` macro. This serves two purposes. First, if the local variables involve a dirty cached value or similar state then this will be written back. Second, for double-buffered displays the macro sets a bounding box for the part of the screen that has been updated. This allows the double buffer synch operation to update only the part of the display that has been modified, without having to keep track of the current bounding box for every updated pixel. For `FLUSHABS` the `x0` and `y0` arguments specify the top-left corner of the bounding box, which extends for `width` by `height` pixels. For `FLUSHREL` `x0` and `y0` still specify the top-left corner, but the bottom-right corner is now determined from the current pixel position offset by `dx` and `dy`. More specifically, `dx` should move the current horizontal position one pixel to the right of the right-most pixel modified, such that `(x + dx) - x0` gives the width of the bounding box. Similarly `dy` should move the current vertical position one pixel below the bottom-most pixel modified. In typical code the current pixel position will already correspond in part or in whole to the bounding box corner, as a consequence of iterating over the block of memory.

If a pixel variable has been used only for reading framebuffer memory, not for modifying it, then it should still be flushed. A `FLUSHABS` with a width and height of 0 can be used to indicate that the bounding box is empty. If it is known that the framebuffer device being used does not support double-buffering then again it is possible to specify an empty bounding box. Otherwise portable code should specify a correct bounding box. If the framebuffer device that ends up being used does not support double buffering then the relevant macro arguments are eliminated at compile-time and do not result in any unnecessary code. In addition if there is no cached value or other state then the whole flush operation will be a no-op and no code will be generated.

Failure to perform the flush may result in strange drawing artefacts on some displays which can be very hard to debug. A `FLUSHABS` or `FLUSHREL` macro only needs to be invoked once, at the end of the iteration.

The `SET` macro sets the current position within the framebuffer. It can be used many times within an iteration. However it tends to be somewhat more expensive than `ADDX` or `ADDY`, so usually `SET` is only executed once at the start of an iteration.

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
        …
        CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, 0, 0);

The `GET` macro retrieves the x and y coordinates corresponding to the current position. It is provided mainly for symmetry, but can prove useful for debugging.

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
        …
    #ifdef DEBUG
        CYG_FB_PIXEL0_GET(FRAMEBUF, new_x, new_y);
        diag_printf("Halfway through: x now %d, y now %d\n", new_x, new_y);
    #endif
        …
        CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, 0, 0);

The `ADDX` and `ADDY` macros adjust the current position. The most common increments are 1 and -1, moving to the next or previous pixel horizontally or vertically, but any increment can be used.

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
        for (rows = height; rows; rows--) {
            for (columns = width; columns; columns--) {
                <perform operation>
                CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
            }
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, -1 * width);
            CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
        }
        CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, width, 0);

Here the current position is moved one pixel to the right each time around the inner loop. In the outer loop the position is first moved back to the start of the current row, then moved one pixel down. For the final flush the current x position is off by `width`, but the current y position is already correct.

The final two macros `READ` and `WRITE` can be used to examine or update the current pixel value.

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        CYG_FB_PIXEL0_SET(FRAMEBUF, x, y);
        for (rows = height; rows; rows--) {
            for (columns = width; columns; columns--) {
                cyg_fb_colour colour = CYG_FB_PIXEL0_READ(FRAMEBUF);
                if (colour == colour_to_replace) {
                    CYG_FB_PIXEL0_WRITE(FRAMEBUF, replacement);
                }
                CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
            }
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, -1 * width);
            CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
        }
        CYG_FB_PIXEL0_FLUSHREL(FRAMEBUF, x, y, width, 0);

## Concurrent Iterations

Although uncommon, in some cases application code may need to iterate over two or more blocks. An example might be an advanced block move where each copied pixel requires some processing. To support this there are `PIXEL1`, `PIXEL2` and `PIXEL3` variants of all the `PIXEL0` macros. For example:

        CYG_FB_PIXEL0_VAR(FRAMEBUF);
        CYG_FB_PIXEL1_VAR(FRAMEBUF);

        CYG_FB_PIXEL0_SET(FRAMEBUF, dest_x, dest_y_);
        CYG_FB_PIXEL1_SET(FRAMEBUF, source_x, source_y);
        for (rows = height; rows; rows--) {
            for (columns = width; columns; columns--) {
                colour = CYG_FB_PIXEL1_READ(FRAMEBUF);
                <do some processing on colour>
                CYG_FB_PIXEL0_WRITE(FRAMEBUF, colour);
                CYG_FB_PIXEL0_ADDX(FRAMEBUF, 1);
                CYG_FB_PIXEL1_ADDX(FRAMEBUF, 1);
            }
            CYG_FB_PIXEL0_ADDX(FRAMEBUF, -100);
            CYG_FB_PIXEL0_ADDY(FRAMEBUF, 1);
            CYG_FB_PIXEL1_ADDX(FRAMEBUF, -100);
            CYG_FB_PIXEL1_ADDY(FRAMEBUF, 1);
        }

        CYG_FB_PIXEL0_FLUSHABS(FRAMEBUF, source_x, source_y, width, height);
        CYG_FB_PIXEL1_FLUSHABS(FRAMEBUF, 0, 0, 0, 0);  // Only used for reading

The `PIXEL0`, `PIXEL1`, `PIXEL2` and `PIXEL3` macros all use different local variables so there are no conflicts. The variable names also depend on the framebuffer device. If the target has two displays and two active framebuffer devices then the pixel macros can be used with the two devices without conflict:

        CYG_FB_PIXEL0_VAR(FRAMEBUF0);
        CYG_FB_PIXEL0_VAR(FRAMEBUF1);
        …

Prev | Home | Next
Framebuffer Drawing Primitives | Up | Writing a Framebuffer Device Driver