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); …
2024-03-18 | Open Publication License |