Click here to Skip to main content
15,867,453 members
Articles / General Programming / Debugging

Simple two file graphics library for C/C++

Rate me:
Please Sign up or sign in to vote.
4.85/5 (32 votes)
16 Apr 2012CPOL5 min read 91.7K   3.2K   82   11
A two file graphics library for debugging otherwise graphically deficient applications.

Example image created with this library

363908/ezdib.png

Limited resource examples

363908/ezdib_mono_dotmatrix.png

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.

C++
// Create an image
HEZDIMAGE hDib = ezd_create( 640, -480, 24 );

// Fill in the background with a dark gray color
ezd_fill( hDib, 0x606060 );

// Save a test image
ezd_save( hDib, "test.bmp" );

// Free resources
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 ;)

C++
// Draw random green line
ezd_line( hDib, 100, 100, 200, 200, 0x00ff00 ),

// Random yellow box
ezd_fill_rect( hDib, 300, 200, 350, 280, 0xffff00 );

// Draw random white dot
ezd_set_pixel( hDib, 50, 50, 0xffffff );

// Rectangle
ezd_rect( hDib, 50, 50, 100, 100, 0x000000 );

// Circle
ezd_circle( hDib, 100, 100, 10, 0x000000 );

// Fill the circle
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.

C++
// Create a medium font
HEZDFONT hFont = ezd_load_font( EZD_FONT_TYPE_MEDIUM, 0, 0 );

// Draw some familiar text
ezd_text( hDib, hFont, "Hello World!", -1, 10, 10, 0xffffff );

// Free the memory
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.

C++
| [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.

C++
// Example font map
static const unsigned char my_font [] =
{
	// Default glyph
	'.', 1, 6,	0x08,

	// Tab width
	'\t', 8, 0,

	// Space
	' ', 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.

C++
// User buffer
const int w = 320, h = 240;
char user_header[ EZD_HEADER_SIZE ];
char user_image_buffer[ w * h / 8 ];

// Initialize image header
hDib = ezd_initialize( user_header, sizeof( user_header ), w, -h, 1,
                           EZD_FLAG_USER_IMAGE_BUFFER );
if ( !hDib )
    return -1;

// Set custom image buffer
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

363908/ezdib_mono.png

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.

C++
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" );

// Create a 'fake' dot matrix display
hDmd = ezd_create( w, -h, 24, 0 );
if ( !hDmd )
    return -1;

// Give our dot matrix display a black background
ezd_fill( hDmd, 0 );

// Create video 'driver'
hDib = ezd_create( w, -h, 1, EZD_FLAG_USER_IMAGE_BUFFER );
if ( !hDib )
    return -1;

// Set pixel callback function
ezd_set_pixel_callback( hDib, &dotmatrix_writer, hDmd );

// Draw into hDib
...

Example 'dot matrix device' output

363908/ezdib_dotmatrix.png

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.

C++
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 ) )

        // Write the character
        p->buf[ y * p->sw + x ] = (unsigned char)f;

    else

        // Write the 'color'
        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;

    // Null terminate
    ascii[ ( w + 1 ) * h ] = 0;

    // Fill in new lines
    for ( y = 0; y < h - 1; y++ )
        ascii[ y * ( w + 1 ) + w ] = '\n';

    // Set pixel callback function
    ad.sw = w + 1; ad.buf = ascii;
    ezd_set_pixel_callback( hDib, &ascii_writer, &ad );

    // Fill background with spaces
    ezd_fill( hDib, ' ' );

    // Border
    ezd_rect( hDib, 0, 0, w - 1, h - 1, '.' );

    // Head
    ezd_circle( hDib, 30, 10, 8, 'o' );

    // Mouth
    ezd_arc( hDib, 30, 10, 5, 0.6, 2.8, '-' );

    // Eyes
    ezd_set_pixel( hDib, 28, 8, 'O' );
    ezd_set_pixel( hDib, 32, 8, 'O' );

    // Nose
    ezd_line( hDib, 30, 10, 30, 11, '|' );

    // Draw some text
    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 );

    // Show our buffer
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralUseful utility Pin
BrainlessLabs.com16-Apr-12 0:19
BrainlessLabs.com16-Apr-12 0:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.