How To: Load an OpenGL Texture using the FreeImage library (or FreeImagePlus, technically)

I’d been using DevIL (or OpenIL if you prefer) as my image loader library, only it hasn’t been updated in a long time and can be a pain to build properly, so I’ve needed to move onto something else. The DevIL fork, ResIL, isn’t quite ready for prime-time and my attempts to build it have resulted in wasted hours and failure (I’ve fixed multiple build errors on the way – but then I hit stuff I can’t see how to fix without trying to re-package the entire thing).

So, new image library, huh? I had a google and saw the FreeImage library (and it’s FreeImagePlus version for C++ – documentation for which can be found here) – and it just happens to be in the Arch community repos so a quick sudo pacman -S freeimage later and we’re ready to rock… Only it didn’t come with any examples, so I dug around on forums and found some code that just about worked, but I wasn’t a big fan of it – so I thought I’d rewrite it into a nicer, more robust method – and I think I’ve achieved just that.

A textured quad at an angle.

The loadTexture Method

 
// Method to load an image into a texture using the freeimageplus library. Returns the texture ID or dies trying.
GLuint loadTexture(string filenameString, GLenum minificationFilter = GL_LINEAR, GLenum magnificationFilter = GL_LINEAR)
{
    // Get the filename as a pointer to a const char array to play nice with FreeImage
    const char* filename = filenameString.c_str();
 
    // Determine the format of the image.
    // Note: The second paramter ('size') is currently unused, and we should use 0 for it.
    FREE_IMAGE_FORMAT format = FreeImage_GetFileType(filename , 0);
 
    // Image not found? Abort! Without this section we get a 0 by 0 image with 0 bits-per-pixel but we don't abort, which
    // you might find preferable to dumping the user back to the desktop.
    if (format == -1)
    {
        cout << "Could not find image: " << filenameString << " - Aborting." << endl;
        exit(-1);
    }
 
    // Found image, but couldn't determine the file format? Try again...
    if (format == FIF_UNKNOWN)
    {
        cout << "Couldn't determine file format - attempting to get from file extension..." << endl;
 
        // ...by getting the filetype from the filename extension (i.e. .PNG, .GIF etc.)
        // Note: This is slower and more error-prone that getting it from the file itself,
        // also, we can't use the 'U' (unicode) variant of this method as that's Windows only.
        format = FreeImage_GetFIFFromFilename(filename);
 
        // Check that the plugin has reading capabilities for this format (if it's FIF_UNKNOWN,
        // for example, then it won't have) - if we can't read the file, then we bail out =(
        if ( !FreeImage_FIFSupportsReading(format) )
        {
            cout << "Detected image format cannot be read!" << endl;
            exit(-1);
        }
    }
 
    // If we're here we have a known image format, so load the image into a bitap
    FIBITMAP* bitmap = FreeImage_Load(format, filename);
 
    // How many bits-per-pixel is the source image?
    int bitsPerPixel =  FreeImage_GetBPP(bitmap);
 
    // Convert our image up to 32 bits (8 bits per channel, Red/Green/Blue/Alpha) -
    // but only if the image is not already 32 bits (i.e. 8 bits per channel).
    // Note: ConvertTo32Bits returns a CLONE of the image data - so if we
    // allocate this back to itself without using our bitmap32 intermediate
    // we will LEAK the original bitmap data, and valgrind will show things like this:
    //
    // LEAK SUMMARY:
    //  definitely lost: 24 bytes in 2 blocks
    //  indirectly lost: 1,024,874 bytes in 14 blocks    <--- Ouch.
    //
    // Using our intermediate and cleaning up the initial bitmap data we get:
    //
    // LEAK SUMMARY:
    //  definitely lost: 16 bytes in 1 blocks
    //  indirectly lost: 176 bytes in 4 blocks
    //
    // All above leaks (192 bytes) are caused by XGetDefault (in /usr/lib/libX11.so.6.3.0) - we have no control over this.
    //
    FIBITMAP* bitmap32;
    if (bitsPerPixel == 32)
    {
        cout << "Source image has " << bitsPerPixel << " bits per pixel. Skipping conversion." << endl;
        bitmap32 = bitmap;
    }
    else
    {
        cout << "Source image has " << bitsPerPixel << " bits per pixel. Converting to 32-bit colour." << endl;
        bitmap32 = FreeImage_ConvertTo32Bits(bitmap);
    }
 
    // Some basic image info - strip it out if you don't care
    int imageWidth  = FreeImage_GetWidth(bitmap32);
    int imageHeight = FreeImage_GetHeight(bitmap32);
    cout << "Image: " << filenameString << " is size: " << imageWidth << "x" << imageHeight << "." << endl;
 
    // Get a pointer to the texture data as an array of unsigned bytes.
    // Note: At this point bitmap32 ALWAYS holds a 32-bit colour version of our image - so we get our data from that.
    // Also, we don't need to delete or delete[] this textureData because it's not on the heap (so attempting to do
    // so will cause a crash) - just let it go out of scope and the memory will be returned to the stack.
    GLubyte* textureData = FreeImage_GetBits(bitmap32);
 
    // Generate a texture ID and bind to it
    GLuint tempTextureID;
    glGenTextures(1, &tempTextureID);
    glBindTexture(GL_TEXTURE_2D, tempTextureID);
 
    // Construct the texture.
    // Note: The 'Data format' is the format of the image data as provided by the image library. FreeImage decodes images into
    // BGR/BGRA format, but we want to work with it in the more common RGBA format, so we specify the 'Internal format' as such.
    glTexImage2D(GL_TEXTURE_2D,    // Type of texture
                 0,                // Mipmap level (0 being the top level i.e. full size)
                 GL_RGBA,          // Internal format
                 imageWidth,       // Width of the texture
                 imageHeight,      // Height of the texture,
                 0,                // Border in pixels
                 GL_BGRA,          // Data format
                 GL_UNSIGNED_BYTE, // Type of texture data
                 textureData);     // The image data to use for this texture
 
    // Specify our minification and magnification filters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minificationFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magnificationFilter);
 
    // If we're using MipMaps, then we'll generate them here.
    // Note: The glGenerateMipmap call requires OpenGL 3.0 as a minimum.
    if (minificationFilter == GL_LINEAR_MIPMAP_LINEAR   ||
        minificationFilter == GL_LINEAR_MIPMAP_NEAREST  ||
        minificationFilter == GL_NEAREST_MIPMAP_LINEAR  ||
        minificationFilter == GL_NEAREST_MIPMAP_NEAREST)
    {
        glGenerateMipmap(GL_TEXTURE_2D);
    }
 
    // Check for OpenGL texture creation errors
    GLenum glError = glGetError();
    if(glError)
    {
        cout << "There was an error loading the texture: "<< filenameString << endl;
 
        switch (glError)
        {
            case GL_INVALID_ENUM:
                cout << "Invalid enum." << endl;
                break;
 
            case GL_INVALID_VALUE:
                cout << "Invalid value." << endl;
                break;
 
            case GL_INVALID_OPERATION:
                cout << "Invalid operation." << endl;
 
            default:
                cout << "Unrecognised GLenum." << endl;
                break;
        }
 
        cout << "See https://www.opengl.org/sdk/docs/man/html/glTexImage2D.xhtml for further details." << endl;
    }
 
    // Unload the 32-bit colour bitmap
    FreeImage_Unload(bitmap32);
 
    // If we had to do a conversion to 32-bit colour, then unload the original
    // non-32-bit-colour version of the image data too. Otherwise, bitmap32 and
    // bitmap point at the same data, and that data's already been free'd, so
    // don't attempt to free it again! (or we'll crash).
    if (bitsPerPixel != 32)
    {
        FreeImage_Unload(bitmap);
    }
 
    // Finally, return the texture ID
    return tempTextureID;
}

Usage

To use the method, just set up your code something like this – in your main .cpp file at the top have:

#include <iostream>
#include <cstdlib>
 
// OpenGL/GLEW/GLFW/SDL/Whatever headers
 
// Include the FreeImagePlus library (don't forget to link your project to the .so/.a or to the lib/dll on Windows)
#include <FreeImagePlus.h>
 
using std::string;
using std::cout;
using std::endl;
 
// Create a handle for our texture
GLuint textureID;

In your initialisation/setup method add something like this:

//  ----- Initialise the FreeImage library -----
// Note: Flag is whether we should load ONLY local (built-in) libraries, so
// false means 'no, use external libraries also', and 'true' means - use built
// in libs only, so it's like using the library as a static version of itself.
FreeImage_Initialise(true);

In your main method, once you’ve create an OpenGL context (i.e. window) and called your initialise/setup method, then you can load a texture to use like this:

// Load an image and bind it to a texture
textureID = loadTexture("TestImage.png");
 
// Or load an image using trilinear filtering via mipmaps for minification and GL_LINEAR (the method's default) for magnification
textureID = loadTexture("TestImage.png", GL_LINEAR_MIPMAP_LINEAR);
 
// Or load an image using low quality mipmaps for minification and GL_NEAREST for magnification.
// The point is that you can set the minification and magnification parameters separately, not necessarily that you'd choose these interpolation options.
textureID = loadTexture("TestImage.png", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST);

Finally, on shutdown you may (optionally, as long as you’re not using the Linux/Mac .a version of the library) like to add the following:

// As we're using a .so version of the freeimageplus library on Linux we
// don't need to call DeInitialise (we would only really HAVE to do so on Linux
// or Mac [not Windows!] when using the .a version) - but it doesn't hurt to do it anyway.
FreeImage_DeInitialise();

Wrap-up

Not a great deal to say – it works, it should be relatively robust, it doesn’t leak memory (or at least none that we control), and the code is commented to the hilt and explains why it’s doing things instead of just what it’s doing (some people don’t like verbose code like this – but I teach a lot of programming so I just try to explain everything as clearly as possible as I go – and find I prefer it to ‘terse’ code that I have no idea WTF it’s up to or why it’s bothering).

Many thanks to the FreeImage devs for the library – it’s rather feature-packed and I quite like it (some good example code wouldn’t go amiss though!) =D

Hope you find the code useful! (Bugs? Comments? Suggestions? Feedback always welcome!).