(The source code of which is on Github)
I thought it is about time to write something about my library of C functions for manipulating bitmap images. It has been with me for many years and I've used it in a lot of my hobby projects.
So here I will give a broad overview of the library and then I might write more about the specific points of interest at a later date.
The origins of bmp.c
was modest. I originally wanted only a chunk of C code
that could take a buffer containing an image and write it to a Windows BMP
file.
Seeing as I was now able to read from a BMP file, it wasn't much of a stretch to add the ability to write them as well. From there I kept on adding support for other file formats, like PNG, JPEG, GIF, PCX and TGA until evolved into the 4000+ line behemoth I present here today.
The most complicated part of the library was the saving and loading of GIF images.
A GIF file contains several blocks, and each of those blocks can contain a structure with flags that might describe the blocks that follow, or palette information, or LZW compressed data blocks.
Fortunately the GIF specification is well written, so I managed to implement my encoder and decoder in a finite amount of time. Implementing the LZW compression/decompression was probably the most interesting part of the endeavour. I had to reference some other sources to explain everything; you'll find the links in the source code.
The only time I bumped my toe was when I couldn't get animated GIFs to animate. The problem turned out to be that I missed the Netscape Application Block (NAB), because it is not part of the GIF89 spec. Wikipedia describes it in detail, so when I found it I got animated GIFs working quite quickly.
I actually started my GIF encoder/decoder in a separate source file where I
implemented the specification as completely as possible. You'll find that
implementation in misc/gif.c
(and misc/gif.h
).
The implementation in bmp.c
was then stripped of anything that wasn't needed.
For example, bm_load()
only returns a single Bitmap
object so it only
bothers with the first frame of animated GIFs, and bm_save()
doesn't allow
any customization when saving a file, so it just chooses sensible defaults.
If you want more control over how your GIFs are saved and loaded, I invite you
to look at the GIF-related files in the misc/
directory. They implement the
spec more completely, and provides support for animated GIFs and so on.
If, like me, you've grown up with Windows, these two file formats would seem a bit esoteric.
X Bitmap(XBM) images are encoded as valid C source code files, the content of which defines an array of bytes, each byte representing 8 monochrome pixels.
So a file test.xbm
may look like this (from Wikipedia):
#define test_width 16
#define test_height 7
static char test_bits[] = {
0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80,
0x00, 0x60 };
You would #include "test.xbm"
in your source files to have the image compiled
into your program. With this particular example you would call Bitmap
*img = bm_from_Xbm(test_width, test_height, test_bits)
to get a bitmap object
for use in your program.
I implemented support for XBM because I liked the idea of compiling graphic resources into my programs so that the executable can be distributed without any additional files.
I later added support for X PixMap(XPM) images. These are also encoded as C source code.
This is an example from Wikipedia:
/* XPM */
static char * XFACE[] = {
/* <Values> */
/* <width/cols> <height/rows> <colors> <char on pixel>*/
"48 4 2 1",
/* <Colors> */
"a c #ffffff",
"b c #000000",
/* <Pixels> */
"abaabaababaaabaabababaabaabaababaabaaababaabaaab",
"abaabaababaaabaabababaabaabaababaabaaababaabaaab",
"abaabaababaaabaabababaabaabaababaabaaababaabaaab",
"abaabaababaaabaabababaabaabaababaabaaababaabaaab"
};
You would then just call Bitmap *img = bm_from_Xpm(XFACE);
in your code and
you're off.
What I like about this format is that you can basically add images to your programs in almost the same way that you would add string and numeric literals, and you can manipulate the pictures with nothing more than a text editor. They are also in colour, and have all the advantages of XBM images as well.
The PCX and TGA file formats are a bit old fashioned, but I've run into them on a couple of places on the internet (specifically, older OpenGL tutorials).
Both formats are fairly straightforward, and there are lots of resources on the internet for how they work. Both of them uses Run-length encoding for compression, so once I implemented the PCX reader and writer, the TGA code was quite simple.
I also wanted support for JPEG and PNG, but in this particular case I didn't want to reinvent the wheel, so I implemented support for libjpeg and libpng to save and load those file types respectively.
The misc/pbm.c
file in the distribution is a parser for the
Netpbm image formats.
The Netpbm file formats all store images as ASCII text files and were created for early email systems where transferring binary files was difficult.
They are larger than the equivalent binary files would be, and aren't that common today, but they can still be useful. I have one particular example where I created a Java program that ran on a server without a GUI, but having a graphical representation of its internal states would've been helpful in troubleshooting. In this case I generated several PBM files as the program ran, and then converted them to GIFs on my own computer afterwards.
Naturally, once you have an image in memory, you'd want to manipulate it.
I support drawing lines through Bresenham's line algorithm, drawing rectangles, circles, ellipses and bezier curves. I also provide a flood fill function to colour the image.
There are also several functions to blit (copy) one Bitmap
to another. All of
them takes a region of a source image and copies them to a destination. The
main difference between these is whether the function uses a mask colour to
hide the background or not. Games and so on will probably use the background
mask.
The bm_blit_ex()
function is probably the most interesting of these because
the region on the destination doesn't need to be the same size as on the
source, in which case the image is stretched to fit into the destination
region. It actually uses Bresenham's
algorithm again to
do this efficiently.
There are also several functions to manipulate colors. One of my personal
favourites is bm_atoi()
that takes a string and parses it into a colour. The
parser took inspiration from the way colours are specified in CSS: It supports
colour names like "red"
(based on the web
colours), hex colours like
"#00FF00"
, and the "RGB(255,0,0)"
and "HSL(0,100%, 50%)"
formats.
The library also has functions to manipulate the RGB, HSL and alpha values of colours and interpolate between colours.
There are a couple of ways to render text using the module.
Originally, I supported rendering only 8x8 bitmapped characters, which was fine because at the time the main purpose of the library was to support a retro-style game engine I was developing at the time.
The 8x8 character fonts were stored as XBM files, as described above, and then
blitted to the bitmap in the bm_puts()
and related functions.
At some point I started feeling that this was a bit limiting, so I abstracted
the text rendering away into a structure called BmFont
that has pointers to
functions that know how to render the text.
This new abstraction allowed me to add support for rendering text through
FreeType fonts. There is nothing particularly
complex about my implementation: the files in the ftypefont/
subdirectory of
the distribution gives you a set of functions that creates and destroys the
BmFont
objects that wrap around FreeType objects, from where you can use the
normal bm_puts()
and related functions to render text onto the bitmap.
I've implemented functions over the years to apply several other manipulations to images:
The library has a function called bm_bind()
that wraps the Bitmap
object
around an array of bytes that represents an image in memory.
It requires that the array of bytes be in a specific format (ARGB, specifically). Fortunately it is a very common format on modern computers, so the library can be used to manipulate a wide variety of graphics:
A neat consequence of supporting SDL is that it also works in a Web browser if you use emscripten to compile your program to a web application.
There was one problem: the canvas as used by emscripten has its pixels in the
ABGR format. I've had to add ABGR
preprocessor directive that swaps the R and
B channels (so you add -DABGR=1
in the CFLAGS
-see for example
emscripten.mak
file in the abovementioned Chip-8 emulator).
It works, but there might still be some lurking bugs since I don't use it that often.
I've also played around with using Cairo graphics
to draw onto the Bitmap
object (see the misc/cairoback.c
file in the
distribution). Cairo already supports a wide variety of back-ends, though, so I
can't actually think of a use case where this library would be useful if you're
already pulling in Cairo into your software.
I've found it very useful to have a bitmap module that I can just copy and paste into a new directory whenever I start with a hobby project where I want to play around with bitmapped graphics.
I'll be the first to admit that it could be optimized in many areas, and I really try to improve it as I go along.
Yet, shying away from platform specific optimizations and keeping the code clean has had the benefit of keepiing the library portable and flexible. I've used it under Windows and Linux and in web browsers (through emscripten); It has worked with OpenGL textures, SDL surfaces and Win32 GDI handles. I've also seen it working on Android (through the SDL surfaces and the NDK). The API has also stabilized over the years and it has become quite robust.
As mentioned already, the source code is on Github.