GraphicC – The Basics

There is one more design constraint I did not mention last time, and it’s a fairly important one for my purposes.

  • Must be easily interfaced from scripting environments

In my particular case, I’m going to favor LuaJIT and its FFI mechanism, and JavaScript, in the form of node.js.  At the very least, this means that I should not utilize too many fancy features, such as classes, operator or function overloading, templates, and the like.  These things are just nasty to deal with from script, and not really worth the benefits in such a small system.  Sticking to C89, or C99 style conventions, at least for the parts that are intended to be publicly accessible, is a good idea.

With that, how about looking at some of the basics.  First up, the most basic thing is the representation of a pixel.  For me, there is a very clear separation between a pixel and a color.  A pixel is a machine representation of a color value.  It is typically in a form that the graphics card can render directly.  Modern day representations are typically RGBA, or RGB, or BGR, BGRA.  That is, one value between 0-255 for each of the color components.  These values are arranged one way or the other, but these are the most typical.  Of course with most modern graphics cards, and leveraging an API such as OpenGL or DirectX, your pixel values can be floats, and laid out in many other forms.  But, I’ll ignore those for now.

Pixel values are constrained in a way, because they are represented by a limited set of byte values.  Colors are a completely different animal.  Then you get into various color spaces such as Cie, or XYZ, or HSL.  They all have their uses depending on the application space you’re in.  In the end though, when you want to display something, you’ve got to convert from those various color spaces to the RGB pixel values that this graphics system can support.

Since I am dealing with pixel values (and not colors), the very lowest level routines deal with pixel buffers, or pixel frames.  That is an array of pixel values.  Simple as that.

typedef struct _pix_rgba {
	unsigned char r, g, b, a;
} pix_rgba;

// On a little endian machine
// Stuff it such that 
// byte 0 == red
// byte 1 == green
// byte 2 == blue
// byte 3 == alpha
#define RGBA(r,g,b,a) ((unsigned int)(a<<24|b<<16|g<<8|r))
#define GET_R(value) ((unsigned int)value &0xff)
#define GET_G(value) (((unsigned int)value &0xff00) >> 8)
#define GET_B(value) (((unsigned int)value &0xff0000) >> 16)
#define GET_A(value) (((unsigned int)value &0xff000000) >> 24)

// pixel buffer rectangle
typedef struct _pb_rect {
	unsigned int x, y, width, height;
} pb_rect;

typedef struct _pb_rgba {
	unsigned char *		data;
	unsigned int		pixelpitch;
	int					owndata;
	pb_rect				frame;
} pb_rgba;

The ‘pb-rgba’ structure is the base of the drawing system. This is the drawing ‘surface’ if you will. It is akin to the ‘framebuffer’ if you were doing graphics in hardware. Things can only be a certain way. The buffer has a known width, height, and number of bytes per row. So far there is only one defined pixel buffer rectangle type, and that supports the rgba layout. I could create different pixel buffer types for different pixel layouts, such as rgba16, or rgb15, or bitmap. But, I don’t need those right now, so this is the only one.

I debated a long time whether the ‘pix_rgba’ structure was needed or not. As you can see from the macros ‘RGBA’ and friends, you can easily stuff the rgba values into an int32. This is good and bad, depending. The truth is, having both forms is useful depending on what you’re doing. If there are machine instructions to manipulate bytes in parallel, one form or the other might be more beneficial. This will be put to the test in blending functions later.

So, what are the most basic functions of this most basic structure?

#ifdef __cplusplus
extern "C" {
#endif

int pb_rgba_init(pb_rgba *pb, const unsigned int width, const unsigned int height);
int pb_rgba_free(pb_rgba *pb);

int pb_rgba_get_frame(pb_rgba *pb, const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height, pb_rgba *pf);


#ifdef __cplusplus
}
#endif

#define pb_rgba_get_pixel(pb, x, y, value) *value = ((unsigned int *)(pb)->data)[(y*(pb)->pixelpitch)+x]
#define pb_rgba_set_pixel(pb, x, y, value) ((unsigned int *)(pb)->data)[(y*(pb)->pixelpitch)+x] = value


#define pb_rect_contains(rect, x, y) ((x>=(rect)->x && x<= (rect)->x+(rect)->width) && ((y>=(rect)->y) && (y<=(rect)->y+(rect)->height)))
#define pb_rect_clear(rect) memset((rect), 0, sizeof(pb_rect))

The ‘pb_rgba_init’ function is the beginning. It will fill in the various fields of the structure, and most importantly allocate memory for storing the actual pixel values. This is very important. Without this, the data pointer points to nothing, or some random part of memory (badness). The corresponding ‘pb_rgba_free’ cleans things up.

The ‘pb_rgba_get_frame’ function copies a portion of a pixel array into another pixel array. You could consider this to be a ‘blit’ function in most libraries. There’s a bit of a twist here though. Instead of actually copying the data, the data pointer is set, and the fields of the receiving frame are set, and that’s it. Why? Because there are many cases where you want to move something around, without having to create multiple copies. Just think of a simple ‘sprite’ based game where you copy some fixed image to multiple places on the screen. You don’t want to have to make multiple copies to do it, just reference some part of a fixed pixel buffer, and do what you will.

Then there are the two macros ‘pb_rgba_get_pixel’, and ‘pb_rgba_set_pixel’. These will get and set a pixel value. Once you have these in hand, you have the keys to the graphics drawing kingdom. Everything else in the system can be based on these two functions. Something as simple as line drawing might use these. There may be optimizations in cases such as horizontal line drawing, but for the most part, this is all you need to write textbook perfect drawing routines.

And that pretty much rounds out the basics up to pixel setting.

It’s kind of amazing to me. Even before really getting into anything interesting, I’ve had to make decisions about some very basic data structure representations, and there are various hidden assumptions as well. For example, there is an hidden assumption that all parameters to the set/get pixel routines are bounds checked. The routines themselves will not perform any bounds checking. This is nice for a composable system, because the consumer can decide where they want to pay for such checking. Doing it per pixel might not be the best place.

There are assumptions about the endianness of the int32 value on the machine, as well as various other large and small assumptions in terms of interfacing to routines (using macros?). There are even assumptions about what the coordinate system is (cartesian), and how it is oriented (top left is 0,0). Of course all of these assumptions must be clearly documented (preferably in the code itself), to minimize the surprises as the system is built and used.

This is a good start though. Here’s how we could use what’s there so far.

#include "test_common.h"

void test_blit()
{
	size_t width = 640;
	size_t height = 480;

	pb_rgba pb;
	pb_rgba_init(&pb, width, height);

	pb_rgba fpb;
	pb_rgba_get_frame(&pb, 0, 0, width / 2, height / 2, &fpb);

	// draw into primary pixel buffer
	raster_rgba_rect_fill(&pb, 10, 10, (width / 2) - 20, (height / 2) - 20, pBlue);

	// draw the frame in another location
	raster_rgba_blit(&pb, width / 2, height / 2, &fpb);

	// Now we have a simple image, so write it to a file
	int err = write_PPM("test_blit.ppm", &pb);

}

int main(int argc, char **argv)
{
	test_blit();

	return 0;
}

Setup a pixel buffer that is 640 x 480. Draw a rectangle into the pixel buffer (blue color). Copy that frame into another location in the same pixel buffer. Write the pixel buffer out to a file.

test_blit

And that’s about it.

Some basic constants, simple data structures, core assumptions, and pixels are being stuffed into a buffered in such a way that you can view a picture on your screen.

Next up, drawing lines and rectangles, and what is a ppm file anyway?

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s