Lesson 3 Basic 2D Graphics Programming with Allegro Doing 2D with Allegro Because we are pushing the 2D envelope to the limit in this course, it is fitting that we should start at the beginning and cover vector graphics. The term vector describes the CRT (Cathode Ray Tube) monitors of the past and the vector graphics hardware built into the computers that used this early technology. Doing 2D with Allegro A graphics primitive is a function that draws a simple geometric shape, such as a point, line, rectangle, or circle. I should point out also that these graphics primitives form the basis of all 3D graphics, past and present; after all, the mantra of the 3D card is the holy polygon. Video Cards The fact of the matter is that video cards are not designed to render games; they are designed to render geometric primitives with special effects. As far as the video card is concerned, there is only one triangle on the screen. It is the programmer who tells the video card to move from one triangle to the next. The video card does this so quickly (on the order of 100 million or more polygons per second) that it fools the viewer into believing that the video card is rendering an entire scene on its own. Video Cards The triangles are hidden away in the matrix of the scene (so to speak), and it is becoming more and more difficult to discern reality from virtual reality due to the advanced features built into the latest graphics chips. Xbox 360 The Xbox 360 can crunch through 3 trillion operations per second with its triple-core, hexathreaded processor. Vertices Taken a step closer, each triangle is made up of three vertices, which is really all the graphics chip cares about. Filling pixels between the three points and applying effects (such as lighting) are tasks that the graphics chip has been designed to do quickly and efficiently. 2D Acceleration The earliest Windows accelerators, as they were known, produced for Windows 3.1 and Windows 95 provided hardware blitting. Blit is a term that means bit-block transfer, a method of transferring a chunk of memory from one place to another. In the case of a graphical blit, the process involves copying a chunk of data from system memory through the bus to the memory present on the video card. In the early years of the PC, video cards were lucky to have 1 MB of memory. Video Memory Contrast this with the latest 3D cards that have 256 MB of DDR (Double Data Rate) memory and are enhanced with direct access to the AGP bus! It simply must be as fast as possible to keep feeding the ravenous graphics chip, which eats textures in video memory and spews them out into the frame buffer, which is sent directly to the screen. Parallel Processing In a very real sense, the graphics card is a small computer on its own. When you consider that the typical high-end PC also has a high-performance sound processing card (such as the Sound Blaster Audigy 2 by Creative Labs) capable of Dolby DTS and Dolby Digital 5.1 surround sound, what we are really talking about here is a multi-processor system. Graphics Fundamentals The basis of this entire chapter can be summarized in a single word: pixel. The word "pixel" is short for "picture element," sort of the atomic element of the screen. The pixel is the smallest unit of measurement in a video system. Graphics Fundamentals But like the atom you know from physics, even the smallest building block is comprised of yet smaller things. In the case of a pixel, those quantum elements of the pixel are red, green, and blue electron streams that give each pixel a specific color. This is not mere theory or analogy; each pixel is comprised of three small streams of electrons of varying shades of red, green, and blue Allegro Graphics Starting with this most basic building block, you can construct an entire game one pixel at a time. Allegro creates a global screen pointer when you call allegro_init. This simple pointer is called screen, and you can pass it to all of the drawing functions. Allegro Graphics A technique called double-buffering (which uses off-screen rendering for speed) works like this: drawing routines must draw out to a memory bitmap, which is then blitted to the screen in a single function call. Until you start using a double buffer, you'll just work with the global screen object. Setting Graphics Mode The first function you'll learn about is set_gfx_mode, which sets the graphics mode. This function is really loaded, although you would not know that just from calling it. set_gfx_mode does a lot of work when called: detecting the graphics card identifying and initializing the graphics system verifying or setting the color depth entering full-screen or windowed mode setting the resolution Setting Graphics Mode A comparable DirectX initialization is 20 to 30 lines of code. This function has the following declaration: int set_gfx_mode(int card, int w, int h, int v_w, int v_h); Setting Graphics Mode If an error occurs setting a particular video mode, set_gfx_mode will return a non-zero value (0 is success) and store an error message in allegro_error, which you can then print out. For an example, try using an invalid resolution for a full-screen display, like this: ret = set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 645, 485, 0, 0); Setting Graphics Mode However, if you specify GFX_AUTODETECT and send an invalid width and height to set_gfx_mode, it will actually run in a window with the resolution you wanted. Running in windowed mode is a good idea when you are testing a game and you don't want it to jump into and out of full-screen mode every time you run the program. Setting Graphics Mode The first parameter, int card, specifies the display mode (or the video card in a dualcard configuration) and will usually be GFX_AUTODETECT. If you want a full-screen display, you can use GFX_AUTODETECT_FULLSCREEN, while you can invoke a windowed display using GFX_AUTODETECT_WINDOWED. Setting Graphics Mode The next two parameters, int w and int h, specify the desired resolution, such as 640x480, 800x600, or 1024x768. The final two parameters, int v_w and int v_h, specify the virtual resolution and are used to create a large virtual screen for hardware scrolling or page flipping. Setting Graphics Mode After you have called set_gfx_mode to change the video mode, Allegro populates the variables SCREEN_W, SCREEN_H, VIRTUAL_W, and VIRTUAL_H with the appropriate values, which come in handy when you prefer not to hard-code the screen resolution in your programs. #include "allegro.h" void main(void) { allegro_init(); //initialize Allegro install_keyboard(); //initialize the keyboard //initialize video mode to 640x480 int ret = set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0); if (ret != 0) { allegro_message(allegro_error); return; } //display screen resolution textprintf(screen, font, 0, 0, makecol(255, 255, 255), "%dx%d", SCREEN_W, SCREEN_H); while(!key[KEY_ESC]); allegro_exit(); } END_OF_MAIN(); textprintf Another interesting function is textprintf, which, as you might have guessed, displays a message in any video mode. The first parameter specifies the destination, which can be the physical display screen or a memory bitmap. void textprintf(BITMAP *bmp, const FONT *f, int x, y, color, const char *fmt, ...); Colors You might have noticed a function called makecol. This function creates an RGB color using the component colors passed to it. If you want to define custom colors you can create your own colors like this: #define COLOR_BROWN makecol(174,123,0) allegro_exit The last function that you should be aware of is allegro_exit, which shuts down the graphics system and destroys the memory used by Allegro. In theory, the destructors will take care of removing everything from memory, but it’s a good idea to call this function explicitly. One very important reason why is for the benefit of restoring the video display. (Failure to call allegro_exit might leave the desktop in an altered resolution or color depth depending on the graphics card being used.) Drawing Bitmaps Use the load_bitmap function to load an image file (multiple formats supported). Then use the blit function to draw the bitmap. I’ll go over bitmaps and sprites in more detail in the next lesson. #include "allegro.h" void main(void) { char *filename = "allegro.pcx"; int colordepth = 32; BITMAP *image; int ret; allegro_init(); install_keyboard(); set_color_depth(colordepth); ret = set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0); if (ret != 0) { allegro_message(allegro_error); return; } //load the image file image = load_bitmap(filename, NULL); if (!image) { allegro_message("Error loading %s", filename); return; } //display the image blit(image, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H); //done drawing--delete bitmap from memory destroy_bitmap(image); //draw font with transparency text_mode(-1); //display video mode information textprintf(screen, font, 0, 0, makecol(255, 255, 255), "%dx%d %ibpp", SCREEN_W, SCREEN_H, colordepth); //wait for keypress while (!key[KEY_ESC]); //exit program allegro_exit(); } END_OF_MAIN(); Loading A Bitmap Obviously you'll need a PCX file to run this program. However, you can just as easily use a BMP, PCX, PNG, GIF, or JPG for the graphics file if you want because Allegro supports all of these formats! Graphics Primitives All of the graphics in a vector system are comprised of lines (including circles, rectangles, arcs, which are made up of small lines). Vector displays are contrasted with bitmapped displays, in which the screen is a bitmap array (the video buffer). On the contrary, a vector system does not have a linear video buffer. Drawing Pixels The simplest graphics primitive is obviously the pixel-drawing function, and Allegro provides one: void putpixel(BITMAP *bmp, int x, int y, int color); Drawing Pixels while(!key[KEY_ESC]) { //set a random location x = 10 + rand() % (SCREEN_W-20); y = 10 + rand() % (SCREEN_H-20); //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); } //draw the pixel putpixel(screen, x, y, color); Drawing Lines The horizontal line-drawing function is called hline, while the vertical line function is called vline: void hline(BITMAP *bmp, int x1, int y, int x2, int color); void vline(BITMAP *bmp, int x, int y1, int y2, int color); Drawing Lines The special-case lines functions for drawing horizontal and vertical lines are not used often. The following line function will simply call hline or vline if the slope of the line is perfectly horizontal or vertical: void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); Drawing Lines while(!key[KEY_ESC]) { //set a random location x1 = 10 + rand() % (SCREEN_W-20); y1 = 10 + rand() % (SCREEN_H-20); x2 = 10 + rand() % (SCREEN_W-20); y2 = 10 + rand() % (SCREEN_H-20); //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); } //draw the line line(screen, x1,y1,x2,y2,color); Drawing Rectangles The next logical step is a two-dimensional object containing points in both the X-axis and the Y-axis. Here is the rect function: void rect(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); Drawing Rectangles while(!key[KEY_ESC]) { //set a random location x1 = 10 + rand() % (SCREEN_W-20); y1 = 10 + rand() % (SCREEN_H-20); x2 = 10 + rand() % (SCREEN_W-20); y2 = 10 + rand() % (SCREEN_H-20); //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); } //draw the rectangle rect(screen,x1,y1,x2,y2,color); Drawing Filled Rectangles Outlined rectangles are boring, if you ask me. They are almost too thin to be noticed when drawn. On the other hand, a true rectangle is filled in with a specific color! That is where the rectfill function comes in handy: void rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); Line-Drawing Callback Feature Allegro provides a really fascinating feature in that it will draw an abstract line by firing off a call to a callback function of your making (in which, presumably, you would want to draw a pixel at the specified (x,y) location. To use the callback, you must call the do_line function, which looks like this: void do_line(BITMAP *bmp, int x1, y1, x2, y2, int d, void (*proc)) Line-Drawing Callback Feature The callback function has this format: void doline_callback(BITMAP *bmp, int x, int y, int d) To use the callback, you want to call the do_line function like you would call the normal line function, with the addition of the callback pointer as the last parameter. Line-Drawing Callback Feature To fully demonstrate how useful this can be, I wrote a short program that draws random lines on the screen. But before drawing each pixel of the line, a check is performed on the new position to determine whether a pixel is already present. This indicates an intersection or collision. When this occurs, the line is ended and a small circle is drawn to indicate the intersection. Line-Drawing Callback Feature while(!key[KEY_ESC]) { //set a random location x1 = 10 + rand() % (SCREEN_W-20); y1 = 10 + rand() % (SCREEN_H-20); x2 = 10 + rand() % (SCREEN_W-20); y2 = 10 + rand() % (SCREEN_H-20); //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); //draw the line using the callback function stop = 0; do_line(screen,x1,y1,x2,y2,color,*doline); } rest(200); Line-Drawing Callback Feature //doline is the callback function for do_line void doline(BITMAP *bmp, int x, int y, int color) { if (!stop) { if (getpixel(bmp,x,y) == 0) { putpixel(bmp, x, y, color); rest(5); } else { stop = 1; circle(bmp, x, y, 5, 7); } } } Drawing Circles / Ellipses The circle function has this declaration: void circle(BITMAP *bmp, int x, int y, int radius, int color); The hollow circle function is interesting, but really seeing the full effect of circles requires the circlefill function: void circlefill(BITMAP *bmp, int x, int y, int radius, int color); Drawing Circles / Ellipses The ellipse function is similar to the circle function, although the radius is divided into two parameters, one for the horizontal and another for the vertical: void ellipse(BITMAP *bmp, int x, int y, int rx, int ry, int color); void ellipsefill(BITMAP *bmp, int x, int y, int rx, int ry, int color); Drawing Circles / Ellipses while(!key[KEY_ESC]) { //set a random location x = 30 + rand() % (SCREEN_W-60); y = 30 + rand() % (SCREEN_H-60); radiusx = rand() % 30; radiusy = rand() % 30; //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); //draw the ellipse ellipsefill(screen, x, y, radiusx, radiusy, color); } rest(25); Circle Callback Function Allegro provides a circle-drawing callback function just as it did with the line callback function. To use do_circle, you must declare a callback function and pass a pointer to this function to do_circle. void docircle(BITMAP *bmp, int x, int y, int d) void do_circle(BITMAP *bmp, int x, int y, int radius, int d); Drawing Spline Curves The spline function draws a set of curves based on a set of four input points stored in an array. The function calculates a smooth curve from the first set of points, through the second and third, toward the fourth point: void spline(BITMAP *bmp, const int points[8], int color); int int int int int points[8] = {0,240,300,0,200,0,639,240}; y1 = 0; y2 = SCREEN_H; dir1 = 10; dir2 = -10; while(!key[KEY_ESC]) { //modify the first spline point y1 += dir1; if (y1 > SCREEN_H) dir1 = -10; if (y1 < 0) dir1 = 10; points[3] = y1; //modify the second spline point y2 += dir2; if (y2++ > SCREEN_H) dir2 = -10; if (y2 < 0) dir2 = 10; points[5] = y2; } //draw the spline, pause, then erase it spline(screen, points, 15); rest(30); spline(screen, points, 0); Drawing Triangles You can draw triangles using the triangle function, which takes three (x,y) points and a color parameter: void triangle(BITMAP *bmp, int x1, y1, x2, y2, x3, y3, int color); Drawing Triangles while(!key[KEY_ESC]) { //set a random location x1 = 10 + rand() % (SCREEN_W-20); y1 = 10 + rand() % (SCREEN_H-20); x2 = 10 + rand() % (SCREEN_W-20); y2 = 10 + rand() % (SCREEN_H-20); x3 = 10 + rand() % (SCREEN_W-20); y3 = 10 + rand() % (SCREEN_H-20); //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); //draw the triangle triangle(screen,x1,y1,x2,y2,x3,y3,color); } rest(100); Drawing Polygons You have already seen polygons in action with the Triangles program, because any geometric shape with three or more points comprises a polygon. To draw polygons in Allegro, you use the polygon function with a pointer to an array of points: void polygon(BITMAP *bmp, int vertices, const int *points, int color); while(!key[KEY_ESC]) { //set a random location vertices[0] = 10 + rand() % (SCREEN_W-20); vertices[1] = 10 + rand() % (SCREEN_H-20); vertices[2] = vertices[0] + (rand() % 30)+50; vertices[3] = vertices[1] + (rand() % 30)+50; vertices[4] = vertices[2] + (rand() % 30)-100; vertices[5] = vertices[3] + (rand() % 30)+50; vertices[6] = vertices[4] + (rand() % 30); vertices[7] = vertices[5] + (rand() % 30)-100; //set a random color red = rand() % 255; green = rand() % 255; blue = rand() % 255; color = makecol(red,green,blue); //draw the polygon polygon(screen,4,vertices,color); } rest(50); Filling Regions The last function I want to introduce to you in this chapter is floodfill, which fills in a region on the destination bitmap (which can be the screen) with the color of your choice: void floodfill(BITMAP *bmp, int x, int y, int color); Drawing Text The text_mode function sets text output to draw with an opaque or transparent background. Passing a value of -1 will set the background to transparent, while passing any other value will set the background to a specific color. int text_mode(int mode); Drawing Text The textout function is the basic text output function for Allegro. There are three alternate vesions that align the text in different ways: void textout(BITMAP *bmp, const FONT *f, const char *s, int x, y, int color); void textout_centre(BITMAP *bmp, const FONT *f, const char *s, int x, y, color); void textout_right(BITMAP *bmp, const FONT *f, const char *s, int x, y, color); Drawing Text A slightly different take on the matter of text output is textout_justify, which includes two X coordinates, one for the left edge of the text and one for the right edge, along with the Y position. In effect, this function tries to draw the text between the two points. This really is more useful when you are using custom fonts. void textout_justify(BITMAP *bmp, const FONT *f, const char *s, int x1, int x2, int y, int diff, int color); Drawing Text Allegro provides several very useful text output functions that mimic the standard C printf function, providing the capability of formatting the text and displaying variables. The base function is textprintf, and it looks like this: void textprintf(BITMAP *bmp, const FONT *f, int x, y, color, const char *fmt, ...);