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!).

28 thoughts on “How To: Load an OpenGL Texture using the FreeImage library (or FreeImagePlus, technically)”

    1. Call glGetError() before calling the loadTexture() method – it could be something that’s happened previously, but the error is only being checked inside loadTexture(), so it looks like it’s coming from there..

      If loadTexture() is the method causing the invalid enum then I’d make a checkGLError(string location) method and call it at multiple stages, like this:

      – do something
      – checkGLError(“1”);
      – do something else
      – checkGLError(“2”);
      – etc.

      In this way you can track down the exact location of the operation with the invalid enum and fix it.

  1. Thanks.
    This is an old post, but I didn’t know where to ask… I got an error loading the texture – invalid value. Could you tell me what are the possible reasons my image fails to load (when it is in the project directory or not)? This is the 4th library I am trying. I tried stb_image, SOIL, libpng, and all fail to load my image.
    I am stalled at this point in openGL.

  2. Have you tried putting the image to load in the same folder as your executable, or specifying a relative path along with the file name?

    If that doesn’t work I’ll try to provide a working example for you tomorrow.

    1. I was so disappointed and confused that I put the file in ever possible folder in my project, and when that failed, I thought there was maybe something wrong with the file, so I downloaded one, and still failed to load image.
      I had been using a resource folder and specifying a relative path along with the file name before, and that failed also.
      I’ll look forward to the example. Thanks

  3. Okay, here’s a Code::Blocks version of the project. It links against your system’s opengl32.dll, but uses local copies of glew/glfw/glm/freeimageplus. I’ve made sure the Windows version works since that’s what I assume you’re working with – if you have freeimageplus installed on your Linux system then the Linux version of the project is also likely to work but I haven’t tested it.

    To build and run this from inside C::B doesn’t require the image, GLSL shaders (MyShader.vert/MyShader.frag) and FreeImage.dll to be in the same folder as the executable because I think I added ‘.’ to C::B’s search path (i.e. look in the current project directory for files).

    To run the executable standalone you DO require the image, shaders and FreeImage.dll to be in the same folder as the exe.

    I’ve left a compiled version in the bin\Debug folder for you so you can at least test that it’s not something odd going on with your machine that’s causing the issue.

    When you launch the exe don’t move the mouse about too much because it’s bound to the camera – and you may end up ‘looking at nothing’. If you just see a white screen move the mouse around and you should be able to find some textured quads rotating about.

    Oh, and here’s the project file: FreeImagePlus_GLSL.

    Cheers & happy coding!

  4. I’m sorry. I’m using Visual Studio. I don’t have code blocks installed, and I am preserving hard drive space. Would it be possible for you to produce the source code in VS. Would that be putting too much on you (I believe you must be busy). I do appreciate the time you took to respond and provide the example.
    If you can do it in VS, since this is the IDE I prefer to work with, I’d be most grateful.

    1. Looking over the source code of your example, I think I will be able to implement these in VS. Since I am using nuget packages, getting the libraries isn’t a problem.
      I’ll let you know how it goes.

      1. Sure. Have a crack at it yourself, and if you’re really struggling I’ll try to find some time this evening to put together a VS version for you.

            1. I only just finished organizing the codes in VS, and just did a build, but I am receiving linker errors from glfw. I realized your file doesn’t have glfw.lib, so I was thinking that may be the problem. I can’t use my glfw library in my nuget packages, since that’s version 3, while your code uses version 2, and I’d have to make some major changes to your code. (not desirable). So I just downloaded the source, and I’ll see if I can solve the problem from there.
              Having the VS example may make it easier for me, but then it might not, so I made a decision, that if I can’t fix these errors, I’ll install codeblocks, and try it from there.
              You’ve already been quite generous, so you deserve a little rest.
              I’ll let you know in a few hours the results. Thanks again.

                1. glfw.a is in the .\glfw\lib folder – and you know it works because I know it works (it’s just statically linked).

                  We also call:

                  #define glew_static

                  before we pull in the glew header. This helps us to embed glew in the exe so it doesn’t need external files.

    1. Ha
      You have more confidence than I do.
      I didn’t beat the errors, so I installed codeblocks – mingw.
      Without making any adjustments, I just click build. This is the output:
      ————– Build: Debug in FreeImagePlus GLSL (compiler: GNU GCC Compiler)—————

      mingw32-g++.exe -Wall -g -Iglew\include -Iglfw\include -Ifreeimage\include -Ifreeimageplus\include -c E:\GameDevelopment\FreeImagePlus_GLSL\Camera.cpp -o obj\Debug\Camera.o
      Execution of ‘mingw32-g++.exe -Wall -g -Iglew\include -Iglfw\include -Ifreeimage\include -Ifreeimageplus\include -c E:\GameDevelopment\FreeImagePlus_GLSL\Camera.cpp -o obj\Debug\Camera.o’ in ‘E:\GameDevelopment\FreeImagePlus_GLSL’ failed

      I don’t have a clue. Any suggestions?

      1. Is that the full error output? Doesn’t seem complete… Is it moaning about the Camera class?

        The CB project provided works just fine for me so I’m a bit confused now. You shouldn’t have to modify it at all for it to build and run.

        1. Yup. That’s the full output log. It ends with –‘E:\GameDevelopment\FreeImagePlus_GLSL’ failed.

          Don’t worry if you can’t figure it out. At least you tried, and I’d say you were a big help. Actually, I don’t think it’s anything you can fix. Something is wrong somewhere, just that we haven’t figured out where.
          I’m beginning to think it may be system security settings , because I have been following openGL tutorials to a T, and only got stuck at loading an image. There must be a reason why all the libraries I tried failed to load a simple texture file. That’s most definitely odd, since there was no problem with the code. I’ll keep at it, until either I solve it, or I don’t. In either case I’ll get to learn c++, openGL, and shaders. So I still win.
          Thanks for you code. I’ll learn from that too. :)
          If I crack it, I’ll let you know. Cheers

  5. I’m trying to put together a VS2017 version for you… and oh my word it’s being nuisance…

    I can pull in glew, glm and GLFW3 via nuget – but it doesn’t like FreeImage/FreeImagePlus at all – so I’ve just copied those in and linked the libs and put the DLLs in the exe folder – but it’s crashing on startup.

    It all BUILDS but fails on calling “glCreateShader” at the moment – I have no idea why.

    Feels like a DLL issue, but…. who knows?

    1. Man, you must like to work. Don’t pull out your hair trying to get this done. I learned to accept failure, especially when I tried a considerable amount.

  6. It’s more that I don’t like to fail than I like to work… especially when I know that something is perfectly do-able but it’s just being an ass at the moment.

    Sometimes you have to be a bit relentless in your pursuit of goals to actually get there – and also have a positive ‘you learn from every failure’ mindset.

    Anyways, I got it all working in VS2017 – here’s the project:
    FreeImagePlus_GLSL_VS2017.zip

    As before, I’ve left the compiled DEBUG executable for you so you can make sure it runs on your machine – but hopefully this time it’ll build and run for you just fine.

    When you launch the exe don’t touch the mouse for a moment and you should see stuff. As soon as you touch the mouse the camera orientation goes a bit whack and you have to ‘find’ the stuff on screen again by moving the mouse around. Once done you can move around with WSAD and mouse movement like an FPS.

    I’ve fixed this orientation issue before but it was a while ago. I think the fix is to set lastMouseX/lastMouseY to the actual mouseX/mouseY before the very first mouse cursor movement callback runs (because lastMouseX and lastMouseY are 0 by default on startup before any mouse cursor callback occurs) – but it’s taken me about 3 hours of extra work to get this running (lol) so I’ve had enough for one day.

    Anyways, hope this helps.

    1. NOPE. That’s not going to help because deleting the 538MB in the .vs folder before I zipped it up now means it’s forgotten all the include folders and libraries.

      Because why not. Why would you simply store the list of headers, libraries and their paths in the .SLN file, when you could store them somewhere deep in a hidden folder with 500MB+ of rubbish.

      I hate visual studio.

      Okay, fixed it by removing a lot of the hidden “.vs” folder junk but not the database file. Also refactored all paths to be relative so the project doesn’t care where it lives, and I fixed the camera jump-on-first move issue. I’ve overwritten the previous file with the fixed one at the same URL: FreeImagePlus_GLSL_VS2017.zip

      I still hate Visual Studio through =P

          1. Dude, this one worked! Unlike the previous that gave me 56 errors and complained about not finding FreeImagePlus. Hats off to you friend.

            Do you see VS is not as bad as you suggest? You’re hating the best IDE in the world bro. All you need is what Microsoft gives you and there is no problem. Try to implement something that don’t belong, and it will complain, which is no different to any other IDE, only… they still trail VS – by far. :)

            I really do appreciate you doing this though. I don’t think you really know how much I appreciate it. When I get my paypal account sorted out, I will make a contribution to any program you produce (so long as it’s not vile). Just let me know what you got.
            Cheers

            1. That’s fantastic – I’m happy you’re happy =D

              VS isn’t all that bad, I just find some aspects of it a little more difficult to use than other IDEs – and because I use a lot of Linux I like to try to write cross-platform code wherever possible. So in that regard, Code::Blocks works well for me because I can create separate project files for both platforms with different library settings but using the same sourcecode.

              If you really want to make a contribution as thanks then maybe just sling a couple of dollars to a charity like Save The Children or UNICEF or something.

              Cheers & happy coding!

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.