Friday, August 24, 2012

Drawing in SDL

    Hopefully you have a working C and SDL setup by now and are ready to use more SDL functions.  First, we need a window that operates normally: exiting only when it is instructed rather than after a delay.  SDL has simple drawing, coloring, and updating functions which can be used with C to draw almost anything.

Window Setup:
    In order to instruct SDL to wait for an exit command, we must first understand how SDL handles input.  A structure called SDL_Event has data for many different types of input including mouse, keyboard, joystick, and exit commands.  To determine which type of event has occurred, the data field "type" of an SDL_Event variable can be compared against the enumerations SDL_MOUSEMOTION, SDL_KEYDOWN, SDL_JOYAXISMOTION, SDL_QUIT, and many more.

    If multiple events occur at the same time, they are given a priority and placed in order; this allows events to be processed one at a time.  For this example, only the quit event will be handled:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <stdio.h>

/*holds data about the drawable surface*/
SDL_Surface *screen;

SDL_Event input; /*holds input*/
int loop = 1; /*set to zero to exit*/

int main(int argc, char** argv) {
    /*start up SDL and setup window*/
    SDL_Init(SDL_INIT_EVERYTHING);
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);

    /*loop is needed to keep window open*/
    while (loop == 1) {
        /*read all events per cycle*/
        while (SDL_PollEvent(&input)) {
            if (input.type == SDL_QUIT) {
                printf("exiting...\n");
                loop = 0; /*exit if quit*/
            }
        }
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*shutdown SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
exiting...
output inside window:
Still blank

    The variable screen is a pointer to the data type SDL_Surface.  It points to a structure which holds the information about a drawable surface in SDL; in this case, it is the background of the window.  You can find out the width, height, color depth, and even the color of individual pixels from the SDL_Surface structure, which makes it ideal for drawable surfaces and imported images.  It is assigned this information from the SDL_SetVideoMode function and will prove vital for drawing operations.

    The next variable is called input and is of type SDL_Event.  Note that screen and input are names I chose to use, you can use whichever titles you wish.  To give information about user input, the command SDL_PollEvent is given the address of our SDL_Event variable to change its data.  This function will become false once all input has been processed, so it is given its own loop within the program's loop to allow processing of any number of events.

    Integer variable loop is created to track the status of the program loop: when its value changes, the loop is broken and the final commands are called.  The value 0 is used to indicate no more looping because it usually means false, but the comparison "loop == 1" allows any value other than 1 to mean the same.

Rectangles:
    One shape SDL draws well is rectangles.  In fact, SDL has the SDL_Rect structure to describe rectangles based on position, width, and height.  The data fields for the struct are x, y, w, and h, which mean x coordinate, y coordinate, width, and height.  x and y are of type "Sint16" while w and h use the type "Uint16".

    These type names are not standard C: SDL simply gives different names to existing data types using a typedef command.  For Sint16 and Uint16, they are really the data types signed short and unsigned short respectively.  As for the int16 part, a short uses 16 bits and is an integer type of data.  SDL has titles for char (Sint8), unsigned char (Uint8), int (Sint32), unsigned int (Uint32), long (Sint64), and unsigned long (Uint64).

    Before we can draw the rectangle, two more functions are needed.  SDL_FillRect requires a pointer to an SDL_Surface struct, a pointer to an SDL_Rect struct, and a Uint32 to represent a color.  The SDL_Surface* parameter will be the screen variable since it is being drawn on and the SDL_Rect* parameter will be to an SDL_Rect we create.  For now, the Uint32 color will be given the value 0xFFFFFFFF (4,294,967,295 or the color white).

    The function SDL_Flip has a single SDL_Surface* and updates whichever surface is input.  This will be the screen for the following example:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <stdio.h>

/*holds data about the drawable surface*/
SDL_Surface *screen;

SDL_Event input; /*holds input*/
int loop = 1; /*set to zero to exit*/

int main(int argc, char** argv) {
    SDL_Rect block;
    block.x = 200; /*200 pixels from left*/
    block.y = 100; /*100 pixels from top*/
    block.w = 400; /*400 pixels wide*/
    block.h = 300; /*300 pixels tall*/

    /*start up SDL and setup window*/
    SDL_Init(SDL_INIT_EVERYTHING);
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);

    /*print out the properties of block variable*/
    printf("position: (%d, %d)\n", block.x, block.y);
    printf("dimensions: %u x %u\n", block.w, block.h);

    /*loop is needed to keep window open*/
    while (loop == 1) {
        /*draw block rectangle on screen*/
        SDL_FillRect(screen, &block, 0xFFFFFFFF);

        /*read all events per cycle*/
        while (SDL_PollEvent(&input)) {
            if (input.type == SDL_QUIT) {
                loop = 0; /*exit if quit*/
            }
        }

        SDL_Delay(20); /*wait 20ms*/
        SDL_Flip(screen); /*update the screen*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*shutdown SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
position: (200, 100)
dimensions: 400 x 300
output inside window:
A White Rectangle

    For this example, multiples of 100 are used to set the properties of a rectangle, but any numbers may be used.  Once the position and dimensions of the rectangle are set, we can draw it giving SDL_FillRect our variables screen and block and an integer value used to indicate a color.

    The SDL_FillRect and SDL_Flip commands are executed every cycle of the program loop.  Since the rectangle does not move, drawing and updating could both be performed before "while (loop == 1)" to get the same result.  For later, interactive examples, this will not be the case.

    The command "SDL_Delay(20)" was added; this was to allow the computer to rest 20 milliseconds (for a maximum of 50 cycles or "frames" per second) before updating the screen.  By letting the computer rest, less effort is put towards drawing frames we may not notice.  Since video games attempt 60 frames per second, a delay of 16ms or less may be more ideal.

Colors:
    The only 32-bit value of a color we can know without doubt is the one used for white: 0xFFFFFFFF.  This is because within this value are the lesser values used for alpha (opacity), red, green, and blue (which are all maximum for white).  The format of a surface determines how these lesser values combine to form a 32-bit value.

    Since 32 bits are divided among 4 components, each component uses 8 bits like the char data type.  The most common format is (alpha << 24) + (red << 16) | (green << 8) | blue.  This means the 8 alpha bits shifted just left of the 8 red bits, which are left of the 8 green, and green is left of blue bits.  Luckily, hexadecimal shows this pattern nicely: for a number like 0x89ABCDEF, 0x89 is alpha, 0xAB is red, 0xCD is green, and 0xEF is blue (but only if the common format is in use).

    For other formats, the red and blue bits may be swapped or alpha may be on the other end.  To account for these possibilities, the function SDL_MapRGB requires a pointer to an SDL_PixelFormat struct in addition to Uint8 values for red, green, and blue.  Luckily, the screen variable has this information under its "format" data field.

    Another function is SDL_MapRGBA, which allows input for alpha.  For more information about this function (and SDL_MapRGB) visit http://sdl.beuc.net/sdl.wiki/SDL_MapRGBA.  However, alpha values are often ignored when drawing rectangles, so we can say alpha = 0 and still have an opaque rectangle drawn.  Also, 0x00FFFFFF is equal to 0xFFFFFF, allowing for colors of only three components (alpha ignored).  Here are some rectangles with color:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <stdio.h>

/*holds data about the drawable surface*/
SDL_Surface *screen;

SDL_Event input; /*holds input*/
int loop = 1; /*set to zero to exit*/

int main(int argc, char** argv) {
    SDL_Rect magenta_block; /*three rectangles*/
    SDL_Rect yellow_block, cyan_block;

    Uint32 magenta, yellow, cyan; /*colors*/
    Uint8 red, green, blue; /*components*/

    /*setup block positions and dimensions*/
    magenta_block.x = 50;
    magenta_block.y = 75;
    magenta_block.w = 200;
    magenta_block.h = 150;

    yellow_block.x = 100;
    yellow_block.y = 200;
    yellow_block.w = 150;
    yellow_block.h = 200;

    cyan_block.x = 200;
    cyan_block.y = 100;
    cyan_block.w = 200;
    cyan_block.h = 200;

    /*start up SDL and setup window*/
    SDL_Init(SDL_INIT_EVERYTHING);
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);

    /*setup colors after screen initialized*/
    magenta = SDL_MapRGB(screen->format, 255, 0, 255);
    yellow = 0xFFFF00; /*red and green*/
    cyan = SDL_MapRGB(screen->format, 0, 255, 255);

    /*Get and print yellow components*/
    SDL_GetRGB(yellow, screen->format, &red, &green, &blue);
    printf("yellow: RGB(%u, %u, %u)\n", red, green, blue);

    /*loop is needed to keep window open*/
    while (loop == 1) {
        /*draw rectangles on screen*/
        SDL_FillRect(screen, &magenta_block, magenta);
        SDL_FillRect(screen, &yellow_block, yellow);
        SDL_FillRect(screen, &cyan_block, cyan);

        /*read all events per cycle*/
        while (SDL_PollEvent(&input)) {
            if (input.type == SDL_QUIT) {
                loop = 0; /*exit if quit*/
            }
        }

        SDL_Delay(20); /*wait 20ms*/
        SDL_Flip(screen); /*update the screen*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*shutdown SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
yellow: RGB(255, 255, 0)
output inside window:
3 Rectangles with Color

    Here we see three rectangles of different colors.  The three colors used are the secondary colors of light: magenta, yellow, and cyan, which are the basic ink colors for printers.  We can break down a color into its red, green, and blue components with the function SDL_GetRGB by giving it the Uint32 color, format, and addresses of three Uint8 variables to write to.  This is how the components of yellow were determined.

    The order of the drawing makes a difference: note that magenta is drawn first and cyan last.  In the final picture, the magenta block is obscured by the yellow and cyan blocks because every new draw command will overwrite the previous commands.  Here, several pixels are colored magenta, then yellow, and finally stop at cyan.

    To wrap up this post, some references are needed.  For information about RGB color, wikipedia has the page http://en.wikipedia.org/wiki/RGB_color_model.  For color ideas, here is a table of hexadecimal colors used with websites: http://www.tayloredmktg.com/rgb/.  For more documentation about SDL_FillRect, the page http://www.libsdl.org/docs/html/sdlfillrect.html is helpful, and the page http://www.libsdl.org/docs/html/index.html for SDL in general.

    Thank you for reading, the next post will cover keyboard interaction.

No comments:

Post a Comment