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.

No comments:

Post a Comment