Monday, January 28, 2013

More OpenGL Textures

    With the SDL_image library setup, OpenGL can now use textures of other image formats.  This post should cover how to load non-BMP images and use them as an OpenGL textures.  Also, OpenGL allows us to save multiple textures and apply them to different triangles.

Image to GL Texture
    The previous post, http://multimediaprogram.blogspot.com/2012/12/textures-in-opengl.html, discusses how to use a BMP image as a GL texture.  A very similar process can be used for making textures from other image formats.  The first step is still to select an image, and for this example, I will use the brick texture in its JPG format:

Brick Texture (JPG)

    Next, the image should be loaded in as an SDL_Surface.  Use the IMG_Load function from the SDL_image library to obtain an SDL_Surface with the image data.  Also, since the saved data can vary among image formats, we can use the SDL_DisplayFormat function to create an SDL_Surface with the same format as the screen.

    To make the GL texture, we can use the same OpenGL functions as last time.  Here is an example of loading and displaying the brick texture:
/*thanks to tohtml.com for syntax highlighting*/

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

SDL_Surface* screen;
SDL_Surface* image; /*will store the converted image*/
SDL_Surface* temp; /*store loaded data, unconverted*/
SDL_Event input;
int loop = 1;
GLuint texture; /*holds the texture ID*/

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*try to load image, exit if there are problems*/
    temp = IMG_Load("texture.jpg");
    if (temp == NULL) {
        printf("Image failed to load\n");
        SDL_Delay(3000);
        SDL_Quit(); /*shutdown SDL*/
        fflush(stdout); /*update stdout*/
        return 0;
    }

    /*convert loaded data into screen format*/
    image = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp); /*done with temp*/

    /*print dimensions of loaded image*/
    printf("image dimensions: %d x %d\n", image->w, image->h);

    /*convert SDL_Surface into GL texture*/
    glEnable(GL_TEXTURE_2D); /*enable 2D texturing*/
    glGenTextures(1, &texture); /*create a texture, store ID*/
    glBindTexture(GL_TEXTURE_2D, texture); /*set current texture*/
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0,
       GL_BGRA, GL_UNSIGNED_BYTE, image->pixels);

    /*set clear color to blue*/
    glClearColor(0, 0, 0.5, 1.0);

    /*set origin to lower left, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0, 4, 0, 3);

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*set color to white*/
       glColor3ub(255, 255, 255);

       /*square*/
       glTexCoord2i(0, 0);
       glVertex2f(1, 0.5);
       glTexCoord2i(0, 1);
       glVertex2f(1, 2.5);
       glTexCoord2i(1, 1);
       glVertex2f(3, 2.5);
       glTexCoord2i(1, 0);
       glVertex2f(3, 0.5);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*print that the program is exitting*/
    printf("User has quit the program\n");

    /*remove texture-related stuff from memory*/
    glDeleteTextures(1, &texture);
    SDL_FreeSurface(image);

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
image dimensions: 256 x 256
User has quit the program

output inside window:
JPG Texture on a Quad


    While this is basically a copy-paste of the first BMP example, there are some important differences to note.  As stated above, IMG_Load is now used in place of SDL_LoadBMP.  Also, it is given the file path to a JPG image rather than a BMP image.  It could also accept a file path to a PNG, TGA, TIFF, or other image file.

    However, the produced SDL_Surface may store its data similar to how its stored in the file.  To account for this, the example uses SDL_DisplayFormat to produce a second SDL_Surface with a predictable data storage.  Since the screen has been set to 32-bit, the SDL_Surface will also have an additional, fourth color channel.  This is usually the transparency and is referred to as alpha.

    As a result, the function glTexImage2D is given the values GL_RGBA and GL_BGRA.  For a non-transparent texture, like the one above, the alpha channel is not necessary, so GL_RGBA could be replaced with GL_RGB since this arguement tells OpenGL how to store the texture.  The GL_BGRA is very much needed since the SDL_Surface still stores the unused, fourth channel.

    This also works with BMP images.  You can give IMG_Load the file path to a BMP image and it will still place that image on screen.  Like BMP images, glTexImage2D is told that the image is stored with the color order blue, green, then red.  I'm not sure why this order works, but changing GL_BGRA to GL_RGBA will swap the red and blue channels in the image (try it!).

Using Multiple Textures
    The previous texture post had an example which drew shapes with and without texture.  There is also a way to draw different shapes with different textures.  In other words, we can load two textures and apply each to a different shape.  For the following example, I will use the JPG brick texture, as well as this PNG:

woodTexture.png

     Each loaded image can be converted to a GL texture and assigned an ID.  Rather than disable GL_TEXTURE_2D, we can use the ID with the command glBindTexture in order to specify which texture we currently want.  However, glBindTexture does not work inside a glBegin/glEnd block; so to draw two quads of different textures, we need two separate glBegin and glEnd calls.  The following example should demonstrate this:
/*thanks to tohtml.com for syntax highlighting*/

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

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*GL texture ID's*/
GLuint brick;
GLuint wood;

/*function to load images into textures*/
GLuint loadTexture(char* filepath);

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*load images into textures*/
    brick = loadTexture("texture.jpg");
    wood = loadTexture("woodTexture.png");

    /*set clear color to blue*/
    glClearColor(0, 0, 0.5, 1.0);

    /*set origin to lower left, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0, 4, 0, 3);

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*set color to white*/
       glColor3ub(255, 255, 255);

       /*set current texture to brick image*/
       glBindTexture(GL_TEXTURE_2D, brick);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*left square*/
       glTexCoord2i(0, 1); glVertex2f(0, 0);
       glTexCoord2i(0, 0); glVertex2f(0, 2);
       glTexCoord2i(1, 0); glVertex2f(2, 2);
       glTexCoord2i(1, 1); glVertex2f(2, 0);

       glEnd(); /*need to end to call glBindTexture*/

       /*set current texture to wood image*/
       glBindTexture(GL_TEXTURE_2D, wood);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*right square*/
       glTexCoord2i(0, 1); glVertex2f(2, 1);
       glTexCoord2i(0, 0); glVertex2f(2, 3);
       glTexCoord2i(1, 0); glVertex2f(4, 3);
       glTexCoord2i(1, 1); glVertex2f(4, 1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*remove texture-related stuff from memory*/
    glDeleteTextures(1, &brick);
    glDeleteTextures(1, &wood);

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}

/*function definition*/
GLuint loadTexture(char* filepath) {
    SDL_Surface *temp;
    SDL_Surface *image;
    GLuint output;

    /*try to load image, exit if there are problems*/
    temp = IMG_Load(filepath);
    if (temp == NULL) {
        printf("Image failed to load\n");
        SDL_Delay(3000);
        SDL_Quit(); /*shutdown SDL*/
        fflush(stdout); /*update stdout*/
        exit(0); /*force quit program*/
    }

    /*convert loaded data into screen format*/
    image = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp); /*done with temp*/

    /*convert SDL_Surface into GL texture*/
    glEnable(GL_TEXTURE_2D); /*enable 2D texturing*/
    glGenTextures(1, &output); /*create a texture, store ID*/
    glBindTexture(GL_TEXTURE_2D, output); /*set current texture*/
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->w, image->h, 0,
       GL_BGRA, GL_UNSIGNED_BYTE, image->pixels);

    /*free image from memory since its a texture now*/
    SDL_FreeSurface(image);

    /*return texture ID*/
    return output;
}
prompt/stdout:

output inside window:
Two Quads with Different Textures


    For this example, a function called loadTexture performs all the necessary steps to convert a file path into a GL texture ID, which is returned.  These are the same commands used in the previous example, with the exception of "exit(0);".  Although the main function returns an integer to quit, exit allows the program to end from inside any function and return the input integer (0 in this case).  This exit will end the program in the event that either texture cannot be loaded.

    The GLuint brick is given the texture ID from the file path "texture.jpg" and GLuint wood is the result of "woodTexture.png" input to loadTexture.  By using these ID's in the glBindTexture function, we select which loaded image we want placed on the upcoming shape.  For this example, the left quad is drawn first with the brick texture, followed by the right quad with the wood texture.

    One last thing to mention: rather than disabling GL_TEXTURE_2D for untextured shapes, you can also call "glBindTexture(GL_TEXTURE_2D, 0);".  The value 0 simply means no texture in this case.  Both this option and "glDisable(GL_TEXTURE_2D);" will draw later shapes without texture.

    Thank you for reading, I hope this post was helpful.  The next post will discuss OpenGL transformations and may be two parts.

Sunday, January 13, 2013

Other Image Formats in SDL

    While SDL by itself will only support BMP images, the extension library SDL_image can load many other image formats.  This includes popular formats like PNG, JPG, TIFF, and GIF.  SDL_image will place the image data into an SDL_Surface, but the stored pixel format may differ from the screen format.  However, SDL can convert loaded images into the screen pixel format with one function.

SDL_image Setup:
    Like with SDL, we must have the header and library files for the SDL_image library.  According to the Lazy Foo tutorial page http://lazyfoo.net/SDL_tutorials/lesson03/index.php, these files can be found on the page http://www.libsdl.org/projects/SDL_image/.  The tutorial page can help with the setup step-by-step, but here is a brief overview for each operating system:

For Linux:
    The SDL_image page contains an RPM file which may be of assistance.  Otherwise, the packages "libsdl-image1.2-dev" and "lidsdl-image1.2" should contain the required library files.

    Since new library files are in use, we must link the SDL_image library when compiling.  This only requires adding "-lSDL_image" to the end of the compiling command.  For example,
"gcc (file name).c -o (program name) -lSDLmain -lSDL -lSDL_image" will link both SDL and the extension library SDL_image.


For Mac:
    The SDL_image page has a DMG file which is hopefully useful.  Alternatively, here is a page which discusses setting up SDL_image and additional extension library SDL_tff in Xcode: http://www.experimentgarden.com/2009/06/getting-sdl-sdl-image-and-sdl-ttf-to.html.

    For compiling with GCC, be sure to add "-lSDL_image" to the end of the compiling command, as shown above.  If these steps don't work, I apologize and highly recommend the Lazy Foo tutorial page.


For Windows:
    Both Visual Studio and MinGW users will need the SDL_image-devel-1.2.12-VC.zip package.  Inside, there will be a header and lib folder.  The header folder contains a single .h file which can be put with the other SDL headers.  The lib folder will have one .lib file which should go with the SDL.lib and SDLmain.lib files (or libSDLmain.a for MinGW).

    To link the library with MinGW, add "-lSDL_image"  at the end of the compiling command.  For example,
"gcc (file name).c -o (program name) -lmingw32 -lSDLmain -lSDL -lSDL_image" will link SDL_image to an SDL program.


    The DLL's in the lib folder are also required for SDL_image to work with an SDL program.  The file SDL_image.dll should be alongside SDL.dll: in the same directory as the compiled program.  Certain image formats will require zlib1.dll and the libpng, libjpg, libtiff, and libwebp DLL's are used to load PNG's, JPG's, TIFF's, and WEBP's.  Place whichever DLL's you will need alongside your program (or all of them if you're not sure which ones are required).

Linking for IDE's:
    If you are using Code::Blocks, Eclipse, Visual Studio, Xcode, or another IDE, be sure to link the SDL_image library file to your project.  This may be a .o, .a, .la, or .lib file, but be sure it is linked like the SDL and SDLmain library files.

Sample Program:
    Hopefully you now have SDL_image setup and ready to use.  As an example, we can try loading a non-BMP image and blitting it on the screen.  The following example will use the same sample image as BMP SDL post, except in JPG format (as it shows in the other posts):
640x480 test image


    To load the image, we only need to include the header "SDL/SDL_image.h" and call IMG_Load instead of SDL_LoadBMP.  Here is a program similar to the one used in the SDL BMP post:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_image.h> /*new header*/
#include <stdio.h>

SDL_Surface *screen; /*visible surface*/
SDL_Surface *image; /*holds data about loaded image*/
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);

    /*try to load image, exit if there are problems*/
    image = IMG_Load("testImage.jpg");
    if (image == NULL) {
        printf("Image failed to load\n");
        SDL_Delay(3000);
        SDL_Quit(); /*shutdown SDL*/
        fflush(stdout); /*update stdout*/
        return 0;
    }
    
    /*print dimensions of loaded image*/
    printf("image width = %d pixels\n", image->w);
    printf("image height = %d pixels\n", image->h);

    /*apply image to the screen*/
    SDL_BlitSurface(image, NULL, screen, NULL);

    /*loop is needed to keep window open*/
    while (loop != 0) {
        /*read all events per cycle*/
        while (SDL_PollEvent(&input)) {
            /*check for close button clicks*/
            if (input.type == SDL_QUIT) loop = 0;
        }

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

    /*perform final commands then exit*/
    SDL_FreeSurface(image); /*free image data*/
    SDL_Quit(); /*shutdown SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
image width = 640 pixels
image height = 480 pixels

output inside window:
JPG image on screen


    The only notable differences are the new header and the IMG_Load with "testImage.jpg" as input.  IMG_Load requires the filepath as input, which can be the name of the file only.  For this example, the filepath is "testImage.jpg" which tells us the location of the JPG image.  Similar to the SDL BMP post, there is error-checking for when the image does not load successfully.

Format Conversion:
    So far the screen format has been 32-bit RGB pixels.  When SDL loads an image, there is a chance it will keep the format of the loaded pixel data.  While SDL is capable of converting formats before blitting, this does require extra work.  Also, we will want images in a predictable format if we plan to use them as OpenGL textures.

    The function SDL_DisplayFormat requires an existing SDL_Surface as input.  It will then copy and convert the input SDL_Surface to the format of the display or screen.  The copy is returned, which means the input SDL_Surface can be freed from memory if no longer used.  Here is an example which converts the JPG image and continually applies it to the screen:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_image.h> /*new header*/
#include <stdio.h>

SDL_Surface *screen; /*visible surface*/
SDL_Surface *image; /*holds data about loaded image*/
SDL_Surface *temp;
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);

    /*try to load image into temp, exit if there are problems*/
    temp = IMG_Load("testImage.jpg");
    if (temp == NULL) {
        printf("Image failed to load\n");
        SDL_Delay(3000);
        SDL_Quit(); /*shutdown SDL*/
        fflush(stdout); /*update stdout*/
        return 0;
    }

    /*convert temp to display format, save into image*/
    image = SDL_DisplayFormat(temp);
    
    /*print dimensions of loaded image*/
    printf("image width = %d pixels\n", image->w);
    printf("image height = %d pixels\n", image->h);

    /*loop is needed to keep window open*/
    while (loop != 0) {
        /*read all events per cycle*/
        while (SDL_PollEvent(&input)) {
            /*check for close button clicks*/
            if (input.type == SDL_QUIT) loop = 0;
        }

        /*apply image to the screen (temp is less efficient)*/
        SDL_BlitSurface(image, NULL, screen, NULL);

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

    /*perform final commands then exit*/
    SDL_FreeSurface(temp); /*free temp data*/
    SDL_FreeSurface(image); /*free image data*/
    SDL_Quit(); /*shutdown SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
image width = 640 pixels
image height = 480 pixels

output inside window:
JPG image on screen


    The variable temp holds the loaded image with a non-display format.  SDL_DisplayFormat will create a new SDL_Surface which is given to the variable image.  Since image is a copy, both temp and image must be freed from memory.

    The surface blitting is now done in a loop, which means every cycle will apply the image to the screen.  By applying the display-formatted image to the screen, we save a lot of work from being done each cycle (even with the 20 millisecond delay).  Changing image to temp in "SDL_BlitSurface(image, NULL, screen, NULL);" should have a significant performance impact: for my computer, it went from 1% processor usage to around 10%.

    Even BMP's use a non-display format, so the last example of the SDL BMP post could be optimized if SDL_DisplayFormat were used.  BMP images typically store images as 24- or 32- bit pixels with blue, green, red color order.  The display is usually 32-bit RGB color, which is what SDL_DisplayFormat will convert an SDL_Suface to.

    Thank you for reading.  For another tutorial page, see http://lazyfoo.net/SDL_tutorials/lesson02/index.php and http://content.gpwiki.org/index.php/SDL:Tutorials:2D_Graphics.  For documentation about SDL_image, see http://jcatki.no-ip.org:8080/SDL_image/.

    The next post will discuss using the SDL_image library for GL textures.