The Allegro Wiki is migrating to github at https://github.com/liballeg/allegro_wiki/wiki

Tutorial 7: High Level Drawing Primitives and Framerate Calculations

From Allegro Wiki
Jump to: navigation, search
Outdated: Doesn't match current api (edit)

Abstract

You will learn how to use all the high level drawing primitives in this tutorial, where we will be drawing a smiley face. Stroke thickness can be changed by pressing the up and down arrow keys. Also, just for fun, we will compute the frames per second being drawn.

Data Files

   * prim1.c: Source code
   * stw-a5.zip: Additional source files
   * times.ttf: A font needed for this example

Tutorial

As always, make sure that you have the source code in front of you while reading this. Once you are ready, proceed. <highlightSyntax language="c"> #include <allegro5/allegro5.h> #include <allegro5/a5_primitives.h> #include <allegro5/a5_ttf.h> #include <stdio.h> #include <math.h> #include "stw_keyboard.h" </highlightSyntax> Nothing too exciting in the includes. You'll notice that we need to include a special header file to use the primitives add-on. Also, notice that we're using the keyboard routines developed in Introduction to Bitmaps and Keyboard Input. Make sure you downloaded the additional source files above if you do not have stw_keyboard.h or stw_keyboard.c. <highlightSyntax language="c"> struct color_t { ALLEGRO_COLOR white, black, yellow, brown, blue, green; } color; </highlightSyntax> Again, not particularly exciting. We will sort our colors inside the color structure. The yellow smiley face will be drawn on a white background with black outlines, blue eyes, and brown hair. A squiggle drawn in the background is green. <highlightSyntax language="c"> struct data_t { ALLEGRO_DISPLAY *display; ALLEGRO_TIMER *timer; ALLEGRO_EVENT_QUEUE *queue; ALLEGRO_FONT *f1; int refresh_rate; float thickness; bool done; } data; </highlightSyntax> We will sort all our variables inside the data structure. You can call it whatever you want. The thickness variable determines how thick to draw the primitives, and the done variable is a flag for when the program is finished with execution.

Now to some code, we will take a brief glance at how we will compute the framerate. That is, how many frames are drawn every second, or the "frames per second" of the program. <highlightSyntax language="c"> float fps[64]; int fps_idx=0; </highlightSyntax> First, we define an array to hold 64 elements. This may be overkill; you can reduce it if you want, though I suggest that you keep it at a power of 2, as will be explained a little while later. We also define an index into this array. The array will hold the change in time between each frame, which gives us 64 sample points to average the framerate from. <highlightSyntax language="c"> float get_fps() { int i; float sum=0; for(i=0; i<64; i++) sum += fps[i];

return 64.0/sum; } </highlightSyntax> This function returns a float holding the framerate over the last 64 sample points. If we are running at 60 frames per second, then the values provided for about the first second of execution will be wild. All that is going on here is that we add up all the data points, and divide by the number of data points, to get an average count of time spent on each frame. Taking the reciprocol of this, we have frames per second. (One divided by seconds per frame = frames per second.) To optimize the math, we have taken the reciprocol at the same time as computing the average.

We will show how to fill the fps array later on when we get to the update function. For now, we will look at the meat of this program: the drawing function. <highlightSyntax language="c"> void draw() { // Get the display dimensions int w = al_get_display_width(); int h = al_get_display_height(); int min = MIN(w,h); </highlightSyntax> Since we will be drawing a smiley face at a variable size to fill the display, we store the display dimensions in easier-to-type variables. Also, the smaller dimension is important, so we store that one as well. <highlightSyntax language="c"> // Draw a useless spline in the background float pts[8] = { 0, h/2, w/3, 0, 2*w/3, h, w, h/2 }; al_draw_spline(pts, color.green, data.thickness); </highlightSyntax> The first thing we draw is a spline. This is not part of the smiley face, but I wanted to demonstrate drawing splines. It uses four control points, as specified by the pts array: all the even indices (0, 2, 4, and 6) are x coordinates, while the odd indices (1, 3, 5, and 7) are the y coordinates. They are given in pairs, with indices 0 and 1 being the first point, 2 and 3 the second, and so on.

Passed to the function al_draw_spline is a pointer to the first element of an array with at least 8 elements. If it is larger than 8 elements, the remaining items will be ignored; if it is smaller, your program will most likely crash, so you should never do this. Next is the color, and finally the thickness. Previous versions of Allegro did not allow you to specify thickness, and this is one of the many cool new features of Allegro 5.

Now, why am I spending all this time talking about splines? I wanted to demonstrate that although the points we specified above for the spline move from left to right, with the height at the middle, top, bottom, and then middle again, the spline does not go through all these points. Instead, you can think of them as weights, which a curve will be pulled towards. A spline approximates the data points provided.

The thickness parameter determines how thick to make the curve, in pixels on either side. Observe what happens when it is changed in the program. Values less than or equal to zero will draw a "hairline" curve. All primitives functions which take a thickness argument will function in a similar manner to this one. <highlightSyntax language="c"> // Draw the ... "hair" al_draw_filled_rectangle(w/2-2*min/5-min/16, h/2-min/3, w/2-2*min/5+min/16, h/2-min/6, color.brown); al_draw_filled_rectangle(w/2+2*min/5-min/16, h/2-min/3, w/2+2*min/5+min/16, h/2-min/6, color.brown); </highlightSyntax> This is the weakest part of the smiley face, I admit. I couldn't think of anywhere else to use boxes, so I drew hair with them. It looks kind of like the boss in the Dilbert comic strip to me. There are two rectangle functions: one to draw a filled rectangle, and one to draw an unfilled rectangle. The filled one, al_draw_filled_rectangle, takes two sets of coordinates in the form x1, y1, x2, y2, and finally a color. The unfilled version of the function, al_draw_rectangle_ex, has the same arguments, with a line thickness variable at the end. <highlightSyntax language="c"> // Draw the face al_draw_filled_circle(w/2, h/2, min/2 - 8 - data.thickness/2, color.yellow); al_draw_circle(w/2, h/2, min/2 - data.thickness/2 - 8, color.black, data.thickness); </highlightSyntax> Circles as well can be drawn either as filled or unfilled. The arguments are the center x coordinate, center y coordinate, and radius. Next comes the color, and in the unfilled version, the line thickness. Here we specify a filled circle for the face, and an unfilled circle for the outline of the face. Note that the coordinates are observant of the thickness used, to prevent drawing portions which will overlap. Optimizing like this is always a good idea. <highlightSyntax language="c"> // Draw the smile al_draw_arc(w/2, h/2 + min/16, min/4, 0, M_PI, color.black, data.thickness); </highlightSyntax> We use a half circle for the mouth. This is implemented using an arc. The first three arguments to al_draw_arc are, respectively, the center x and y coordinate, and the radius. Next is the starting angle to use, in radians. (If you are more familiar with degrees for angles, just remember that 360° = 2π radians, π/2 is a right angle.) Following the starting angle is the angular span of the arc. We want half a circle, so we use M_PI for the span. The angle starts at the rightmost edge of the circle defined, and travels clockwise. If you are a mathematician, you may be thinking this is counter-intuitive. However, since the y component increases downward on the Allegro display, things get flipped around, and a clockwise motion is what should be expected. Finally, we give the color and thickness of the strokes. <highlightSyntax language="c"> // Draw the eyes al_draw_filled_ellipse(w/2 - min/6, h/2 - min/5, min/16, min/8, color.black); al_draw_filled_ellipse(w/2 + min/6, h/2 - min/5, min/16, min/8, color.black); </highlightSyntax> The eyes are constructed with ellipses. These as well have a filled and unfilled version. The arguments are the center x and y coordinates, the horizontal radius, vertical radius, and color. In the unfilled version, there is also a thickness argument on the end. <highlightSyntax language="c"> // Draw the eye "balls" al_draw_line_ex(w/2 - min/6 - data.thickness/2, h/2 - min/5 - data.thickness/2, w/2 - min/6 + data.thickness/2, h/2 - min/5 + data.thickness/2, color.blue, data.thickness); al_draw_line_ex(w/2 + min/6 - data.thickness/2, h/2 - min/5 + data.thickness/2, w/2 + min/6 + data.thickness/2, h/2 - min/5 - data.thickness/2, color.blue, data.thickness); </highlightSyntax> The eye balls are also kind of lame. They are there to demonstrate drawing lines. The arguments are similar to those for al_draw_rectangle: x1, y1, x2, y2, color, thickness. <highlightSyntax language="c"> // Draw soul patch al_draw_filled_triangle(w/2, h/2 + 2*min/5, w/2 - min/8, h/2 + min/3, w/2 + min/8, h/2 + min/3, color.brown); </highlightSyntax> We can also draw filled and unfilled triangles. This essentially just draws three lines, but when the thickness is changed, it still looks good. (The edges would look funny on three thick lines drawn together.) The arguments are similar again here, with the addition of an extra point for the third corner of the triangle. This is a simplification of the function al_draw_ribbon, which is not present in this example. al_draw_ribbon will draw a polygon with straight lines, and takes arguments similar to al_draw_spline: an array with each vertex of the polygon as floats, color, line thickness, and how many vertices are in the array. Again, if the passed array does not have enough memory allocated, you will get errors. <highlightSyntax language="c"> // Draw the "interface" al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA, color.black); al_font_textprintf(data.f1, 4, 4, "fps: %0.2f", get_fps()); al_font_textprintf(data.f1, 4, 14, "Line Thickness: %0.2f " "(Up and Down arrow keys)", data.thickness); } </highlightSyntax> And finally, we draw a little interface to tell the user what the framerate is, as well as the line thickness. You will notice that we use the al_font_textprintf function, which has not yet been used in this tutorial series. This functions like a combination of al_font_textout and sprintf, and proper usage should be evident from the code above. Any number of arguments may be passed to this function.

Next, we will look at the update function. <highlightSyntax language="c"> void update(float dt) { if(key[ALLEGRO_KEY_ESCAPE]) data.done = true; if(key[ALLEGRO_KEY_UP]) data.thickness += 15*dt; if(key[ALLEGRO_KEY_DOWN]) data.thickness -= 15*dt;

// Update the fps counter fps[(fps_idx++) & 63] = dt; } </highlightSyntax> If the user presses the escape key, the program is signalled to end. Pressing the up or down arrow keys will increase or decrease the line thickness, at a rate of 15 units per second.

(& is the bitwise AND operator. If you are unfamiliar with it, looking up bitwise operations in C might be a good idea, as it will not be covered here.) The fps counter is also kept here. We update the index into the array, and then look only at the first 6 bits used for the index into the array. Recall above we suggested you use a power of 2 for your array size. 26=64, and in binary, 63 is 111111. The bitwise AND here truncates the value in question to just the first 6 bits, or just numbers between 0 and 63; this only works for powers of 2. We assign that index to the current time step.

The timerupdate function will not be covered here, as it is virtually identical to previous tutorials, with the exception that we are clearing the screen to white instead of black in this program.

The main function as well will not be covered here. There is no initialization function required for using drawing primitives, and we did not do anything new here, other than define some colors that had not been used in the past.

Compiling

Linux users:

$ gcc prim1.c stw_keyboard.c -o prim1 \ -la5_primitives-4.9.8 -la5_ttf-4.9.8 -la5_font-4.9.8 \ `allegro5-config --libs`

Windows users:

> gcc prim1.c stw_keyboard.c -o prim1.exe \ -la5_primitives-4.9.8 -la5_ttf-4.9.8 -la5_font-4.9.8 \ -lallegro-4.9.8

Remember to change the version on the library with whichever the most recent version is!