Example image created with this library
Limited resource examples
Downloads
Introduction
I am always needing to add simple graphics output to console,
services, embedded, or other applications that do not have natural
graphics ability for debugging purposes. So I have brought together
several functions to make this easy.
In this article I will provide code and explanation for a simple graphics
library that can perform in the most limited circumstances.
So features of this library that I believe will make it unique and worthwhile will be...
- Usable in C and C++.
-
Be small and contained entirely in two files,
Namely ezdib.h and ezdib.c.
Just drop them in and go.
-
Support saving dib (.bmp) files.
-
Provide basic functions for drawing pixels, circles, rectangles, fills, text,
as well as raw buffer access. You can tell from the image provided much of what
is possible.
-
Be completely self-contained. This includes the font functions and fonts.
-
Be easy to customize and extend, including adding new fonts.
Creating a blank image.
The ezd_create() functions allocates the memory for the image and returns
all the image details opaquely behind a handle of type HEZDIMAGE. The following
code creates a 640 x 480 image using 24 bits per pixel. This library is only
supporting 1, 24 and 32 bit pixel depths. I believe this provides good functionality
without compromising simplicity.
Notice the negative height. Somewhere in the distant past, someone decided
to store Windows images upside down. Using a negative height will cause the images to
be stored in memory right side up. All functions in this library will work just fine
either way, but be aware that coordinates on the Y axis will be reversed depending on
which way you choose to go.
HEZDIMAGE hDib = ezd_create( 640, -480, 24 );
ezd_fill( hDib, 0x606060 );
ezd_save( hDib, "test.bmp" );
ezd_destroy( hDib );
Drawing and text functions.
I have attempted to keep these functions as simple as possible while
still keeping them reasonably fast. My hope is that you can look at these
functions and easily understand what's going on and have no problems adding
your own functionality.
Line drawing is implemented using incremental division or
Bresenham's Line Algorithm.
The flood fill is a simple four directional fill.
The rest aren't interesting enough to mention, but all the details are in the code ;)
ezd_line( hDib, 100, 100, 200, 200, 0x00ff00 ),
ezd_fill_rect( hDib, 300, 200, 350, 280, 0xffff00 );
ezd_set_pixel( hDib, 50, 50, 0xffffff );
ezd_rect( hDib, 50, 50, 100, 100, 0x000000 );
ezd_circle( hDib, 100, 100, 10, 0x000000 );
ezd_flood_fill( hDib, 100, 100, 0x000000, 0x808080 );
Probably the most interesting of the drawing functions is the text functions.
The following code exemplifies creating a font and drawing text into the image.
HEZDFONT hFont = ezd_load_font( EZD_FONT_TYPE_MEDIUM, 0, 0 );
ezd_text( hDib, hFont, "Hello World!", -1, 10, 10, 0xffffff );
ezd_destroy_font( hFont );
The fonts are in a very simple raster based format. Only 8 bit ASCII
characters are supported. The format is outlined below.
| [character] | [width] | [height] | [bitmap] ~ |
-
character - One byte representing the glyph character
-
width - One byte specifies the character width in bits
-
height - One byte specifies the character height in bits
-
bitmap - One bit per pixel bitmap padded to one byte
Here is an exert from an actual font definition. You can simply pass a
pointer to the byte array to the ezd_load_font() function. If
EZD_STATIC_FONTS is not defined, ezd_load_font() will copy
the font table, index the glyphs, and return a handle. If EZD_STATIC_FONTS
is defined, the function will simply return the pointer you passed in, cast
to type HEZDFONT.
static const unsigned char my_font [] =
{
'.', 1, 6, 0x08,
'\t', 8, 0,
' ', 3, 0,
'!', 1, 6, 0xea,
'@', 4, 6, 0x69, 0xbb, 0x87,
'#', 5, 6, 0x57, 0xd5, 0xf5, 0x00,
'$', 5, 6, 0x23, 0xe8, 0xe2, 0xf8,
...
'0', 3, 6, 0x56, 0xd4, 0x31,
'1', 2, 6, 0xd5, 0x42,
'2', 4, 6, 0xe1, 0x68, 0xf0,
...
'A', 4, 6, 0x69, 0xf9, 0x90,
'B', 4, 6, 0xe9, 0xe9, 0xe0,
'C', 4, 6, 0x78, 0x88, 0x70,
...
};
...
HEZDFONT hFont = ezd_load_font( my_font, 0, 0 );
Embedded systems
I have added several features to specifically address the issues faced
by embedded designs. This library may still be a bit beefy for a tiny 8051
system, but as long as you have a reasonable system, you should be able to
squeeze this library in. If you enable all the following macros, you will
basically be left with a pure C implementation.
The following macros are explained in ezdib.c and can
be defined there, although, good practices would probably be to define these
in your make or project files.
#define EZD_STATIC_FONTS
Defining this macro will eliminate the glyph indexing. This means that
font rendering may be slightly slower, but no extra memory will have to be
allocated.
#define EZD_NO_MEMCPY
If you don't have string.h, you can enable this macro and the library
will use internal substitutes for memcpy and memset.
These will probably be a bit slower of course.
#define EZD_NO_ALLOCATION
You can enable this macro to disable the use of
malloc, calloc, and free.
You will then need to pass in static buffers using ezd_initialize()
and ezd_set_image_buffer(). The EZD_HEADER_SIZE
macro will indicate the minimum space needed for the image header.
See the code below.
const int w = 320, h = 240;
char user_header[ EZD_HEADER_SIZE ];
char user_image_buffer[ w * h / 8 ];
hDib = ezd_initialize( user_header, sizeof( user_header ), w, -h, 1,
EZD_FLAG_USER_IMAGE_BUFFER );
if ( !hDib )
return -1;
if ( !ezd_set_image_buffer( hDib, user_image_buffer, sizeof( user_image_buffer ) ) )
return -1;
#define EZD_NO_FILES
You're embedded system may not support or even have a file system. Use this
macro to disable file system output.
#define EZD_NO_MATH
If you don't have math.h. This is used by the circle drawing
functions.
Monochrome images / one bit per pixel color depths.
This library also supports one bit per pixel color depths. So if you're stuck
with a monochrome LCD display, and can afford the space for a back buffer, you can
use this mode to render images. Maybe if you're memory mapped you can render
straight to the device using ezd_set_image_buffer().
Example rendered to a monochrome image
Unbuffered graphics output
What about the worst case scenario. You have a tiny processor with no space
for a back buffer. Or maybe you just want to use some type of serial display
device, like a dot matrix screen.
As long as you can provide a 'set pixel' function that takes the X and Y coords,
a color, and render it. You can use this library.
Here's an example that uses a custom set pixel function to emulate a dot matrix
display.
typedef struct _SDotMatrixData
{
int w;
int h;
HEZDIMAGE pDib;
} SDotMatrixData;
int dotmatrix_writer( void *pUser, int x, int y, int c, int f )
{
HEZDIMAGE hDib = (HEZDIMAGE)pUser;
if ( !hDib )
return 0;
...
return 1;
}
SDotMatrixData dmd;
const int w = 640, h = 480;
HEZDIMAGE hDmd;
printf( "Creating dotmatrix.bmp\n" );
hDmd = ezd_create( w, -h, 24, 0 );
if ( !hDmd )
return -1;
ezd_fill( hDmd, 0 );
hDib = ezd_create( w, -h, 1, EZD_FLAG_USER_IMAGE_BUFFER );
if ( !hDib )
return -1;
ezd_set_pixel_callback( hDib, &dotmatrix_writer, hDmd );
...
Example 'dot matrix device' output
Unbuffered ASCII output
As a final fun example, here's a complete program that implements a set pixel
function that renders the output to an ASCII string buffer.
typedef struct _SAsciiData
{
int sw;
unsigned char *buf;
} SAsciiData;
int ascii_writer( void *pUser, int x, int y, int c, int f )
{
SAsciiData *p = (SAsciiData*)pUser;
if ( !p )
return 0;
unsigned char ch = (unsigned char)( f & 0xff );
if ( ( '0' <= ch && '9' >= ch )
|| ( 'A' <= ch && 'Z' >= ch )
|| ( 'a' <= ch && 'z' >= ch ) )
p->buf[ y * p->sw + x ] = (unsigned char)f;
else
p->buf[ y * p->sw + x ] = (unsigned char)c;
return 1;
}
int main( int argc, char* argv[] )
{
int b, x, y;
HEZDIMAGE hDib;
HEZDFONT hFont;
SAsciiData ad;
const int w = 44, h = 20;
char ascii[ ( w + 1 ) * h + 1 ];
char user_header[ EZD_HEADER_SIZE ];
hDib = ezd_initialize( user_header, sizeof( user_header ),
w, -h, 1, EZD_FLAG_USER_IMAGE_BUFFER );
if ( !hDib )
return -1;
ascii[ ( w + 1 ) * h ] = 0;
for ( y = 0; y < h - 1; y++ )
ascii[ y * ( w + 1 ) + w ] = '\n';
ad.sw = w + 1; ad.buf = ascii;
ezd_set_pixel_callback( hDib, &ascii_writer, &ad );
ezd_fill( hDib, ' ' );
ezd_rect( hDib, 0, 0, w - 1, h - 1, '.' );
ezd_circle( hDib, 30, 10, 8, 'o' );
ezd_arc( hDib, 30, 10, 5, 0.6, 2.8, '-' );
ezd_set_pixel( hDib, 28, 8, 'O' );
ezd_set_pixel( hDib, 32, 8, 'O' );
ezd_line( hDib, 30, 10, 30, 11, '|' );
hFont = ezd_load_font( EZD_FONT_TYPE_SMALL, 0, 0 );
if ( hFont )
ezd_text( hDib, hFont, "The\nEnd", -1, 4, 4, '#' );
if ( hFont )
ezd_destroy_font( hFont );
if ( hDib )
ezd_destroy( hDib );
printf( "%s\n", ascii );
return 0;
}
............................................
. .
. o .
. ooooooo .
. TTT h h eeee oo oo .
. T h h e oo oo .
. T hhhh eee o o .
. T h h e o o .
. T h h eeee o O O o .
. o o .
. oo | oo .
. EEEE n n ddd o | o .
. E nn n d d o - - o .
. EEE n nn d d o - - o .
. E n n d d o ----- o .
. EEEE n n ddd oo oo .
. oo oo .
. ooooooo .
. o .
............................................
Wishlist
I hope you find the code useful and flexible. Some things I'd like
to add to the project would be.
-
AVI, animated gif, or other video output.
-
Font editing script, maybe written in javascript? The one I used for
creating these fonts is a bit cumbersome.