Simple texture loading with DevIL revisited

I wrote an article a while back about loading images to use as textures in OpenGL, and in it I’d written my own single line image to texture-handle function because I couldn’t get the built-in ilutGLLoadImage function to work. Since I’ve been getting blank looks and failure to compile reports about it, I thought I’d go and take a look at it again, and this time ilutGLLoadImage seems to work just fine, so let’s document it up properly shall we?

Pre-requisites

You will need:

  • A C++ compiler of your choice (I prefer Code::Blocks)
  • Working OpenGL drivers
  • A copy of DevIL
  • A copy of GLEW (GL Extension Wrangler – to make the functionality in your OpenGL drivers availble for use)
  • A copy of GLFW (GL FrameWork – to quickly and easily set up a OpenGL context)
  • An image to load

Preparation and libraries

Create a new project in your IDE of choice, and link in the following libraries:
DevIL required libraries

You’ll have to figure out where the libraries are on your own system but these are where they are on mine, where a /usr/local/ address indicates packages I’ve build myself, /usr/lib/ indicates standard system installed packages, and no prefix uses any paths defined by the compiler (in this case /usr/ where the lib and include directories are assumed). I’ve also added /usr/local/ to the include path so that any headers in /usr/local/include/ are picked up before any in /usr/include. Yes, library paths are a pain ;)

Also, be sure that your project includes its own directory as the working directory (i.e. the directory to be in when executing) so that you can place your image (in this example, a file called abstract-image.jpg) in the main project directory and it’ll be picked up when we try to load it. The way that you specify the working directory will vary depending on the IDE you’re using, but in Code::Blocks you set it from Project | Properties | Build targets like this:

Code::Blocks Working Directory

Source Code

Create a new project with source code something like this:

  1. #include <iostream>
  2. #include <cstdlib>
  3.  
  4. #include <GL/glew.h>		// Include GLEW, which pulls in OpenGL headers as required
  5. #include <GL/glfw.h>		// Include OpenGL Framework headers
  6.  
  7. #define ILUT_USE_OPENGL		// This MUST be defined before calling the DevIL headers or we don't get OpenGL functionality
  8. #include <IL/il.h>
  9. #include <IL/ilu.h>
  10. #include <IL/ilut.h>
  11.  
  12. GLuint textureHandle;           // Create a uint to store the handle to our texture
  13.  
  14. GLfloat frameCount = 0.0f;
  15.  
  16. void initGL(int width, int height)
  17. {
  18. 	//  ----- Initialise GLEW -----
  19. 	GLenum err = glewInit();
  20. 	if (GLEW_OK != err)
  21. 	{
  22. 		std::cout << "GLEW initialisation error: " << glewGetErrorString(err) << std::endl;
  23. 		exit(-1);
  24. 	}
  25. 	std::cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << std::endl;
  26.  
  27. 	// ----- Window and Projection Settings -----
  28.  
  29. 	// Set the window title
  30. 	glfwSetWindowTitle("ilutGLLoadImage Test | September 2011 | r3dux.org");
  31.  
  32. 	// Setup our viewport to be the entire size of the window
  33. 	glViewport(0, 0, (GLsizei)width, (GLsizei)height);
  34.  
  35. 	GLfloat ratio = (float)width / (float)height;	// Calculate the window ratio
  36.  
  37. 	// Change to the projection matrix, reset the matrix and set up our projection
  38. 	glMatrixMode(GL_PROJECTION);
  39. 	glLoadIdentity();
  40. 	gluPerspective(45.0f, ratio, 0.1f, 100.0f); // Params: Field-of-Vision, window-ration, near-clip-plane, far-clip-plane
  41.  
  42. 	// ----- OpenGL settings -----
  43.  
  44. 	glfwSwapInterval(1);                               // Lock to vertical sync of monitor (normally 60Hz, so 60fps)
  45. 	glEnable(GL_DEPTH);                                // Enable the depth buffer
  46. 	glDepthFunc(GL_LEQUAL);                            // Set our depth function to overwrite if new value less than or equal to current value
  47. 	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Ask for nicest perspective correction
  48. 	glEnable(GL_TEXTURE_2D);                           // Enable 2D textures
  49.  
  50. 	// ----- Initialize DevIL libraries -----
  51.  
  52. 	// DevIL sanity check
  53. 	if ( (iluGetInteger(IL_VERSION_NUM) < IL_VERSION) || (iluGetInteger(ILU_VERSION_NUM) < ILU_VERSION) || (ilutGetInteger(ILUT_VERSION_NUM) < ILUT_VERSION) )
  54. 	{
  55. 		std::cout << "DevIL versions are different... Exiting." << std::endl;
  56. 		exit(-1);
  57. 	}
  58.  
  59. 	// Initialise all DevIL functionality
  60. 	ilInit();
  61. 	iluInit();
  62. 	ilutInit();
  63. 	ilutRenderer(ILUT_OPENGL);	// Tell DevIL that we're using OpenGL for our rendering
  64. }
  65.  
  66. void drawScene()
  67. {
  68. 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the screen and depth buffer
  69.  
  70. 	// Reset the matrix to identity
  71. 	glMatrixMode(GL_MODELVIEW);
  72. 	glLoadIdentity();
  73.  
  74. 	glTranslatef(0.0f, 0.0f, -40.0f); // Move things back into the screen
  75.  
  76. 	glRotatef(frameCount, 0.1f, 1.0f, 0.3f); // Rotate the MVP matrix orientation
  77.  
  78. 	glBindTexture(GL_TEXTURE_2D, textureHandle); // Select the texture to use and bind to it
  79.  
  80. 	static float hsize = 15.0f; // Vertical size of the quad
  81. 	static float vsize = 10.0f; // Vertical size of the quad
  82.  
  83. 	// Draw our textured geometry (just a rectangle in this instance)
  84. 	glEnable(GL_TEXTURE_2D);
  85. 	glBegin(GL_QUADS);
  86. 		glTexCoord2f(0.0, 0.0); glVertex3f(-hsize, -vsize, 0.0f);  // Top left
  87. 		glTexCoord2f(0.0, 1.0);	glVertex3f(-hsize,  vsize, 0.0f);  // Bottom left
  88. 		glTexCoord2f(1.0, 1.0);	glVertex3f(hsize,   vsize, 0.0f);  // Bottom right
  89. 		glTexCoord2f(1.0, 0.0);	glVertex3f(hsize,  -vsize, 0.0f);  // Top right
  90. 	glEnd();
  91. 	glDisable(GL_TEXTURE_2D);
  92.  
  93. 	// ----- Stop Drawing Stuff! ------
  94.  
  95. 	glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
  96. 	frameCount++;
  97. }
  98.  
  99. int main()
  100. {
  101. 	// Window settings
  102. 	int width      = 800, height   = 600;
  103. 	int redBits    = 8,   greenBits = 8,  blueBits    = 8;
  104. 	int alphaBits  = 8,   depthBits = 24, stencilBits = 8;
  105.  
  106. 	// Flag to keep our main loop running
  107. 	bool running = true;
  108.  
  109. 	// Initialise glfw
  110. 	glfwInit();
  111.  
  112. 	// Create a window
  113. 	if(!glfwOpenWindow(width, height, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
  114. 	{
  115. 		std::cout << "Failed to open window!" << std::endl;
  116. 		glfwTerminate();
  117. 		return 0;
  118. 	}
  119.  
  120. 	initGL(width, height); 	                        // Call our initGL function to set up our OpenGL options
  121.  
  122. 	ILstring imageFilename = "abstract-image.jpg";  // Specify filename
  123.  
  124. 	textureHandle = ilutGLLoadImage(imageFilename); // Load image directly to texture
  125.  
  126. 	// Output last image loaded properties
  127. 	// Available properties list is at: http://www-f9.ijs.si/~matevz/docs/DevIL/il/f00027.htm
  128. 	std::cout << "Image width         : " << ilGetInteger(IL_IMAGE_WIDTH)          << std::endl;
  129. 	std::cout << "Image height        : " << ilGetInteger(IL_IMAGE_HEIGHT)         << std::endl;
  130. 	std::cout << "Image bits per pixel: " << ilGetInteger(IL_IMAGE_BITS_PER_PIXEL) << std::endl;
  131.  
  132. 	// Main loop
  133. 	while (running == true)
  134. 	{
  135. 		// Draw our scene
  136. 		drawScene();
  137.  
  138. 		// exit if ESC was pressed or window was closed
  139. 		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam( GLFW_OPENED);
  140. 	}
  141.  
  142. 	glfwTerminate();
  143.  
  144. 	return 0;
  145. }

End Result

If you’ve linked in the libraries and used source code like the above, you should end up with a working texture that looks something like this:
ilutGLLoadImage Example

And information regarding the image being used that looks something like this:
DevIL Image Data

I’ve attached a copy of my Code::Blocks project here for anyone who wants it.

Cheers!

Credits: The wallpaper I used as the texture in this guide is Life by N.Design Studio, which you can find here.

Single-Call OpenGL Texture Loader in DevIL

I’ve been playing around with the DevIL image loading library today, and it has a command to load an image and assign it to an OpenGL texture in one fell swoop like this:

GLuint textureHandle = ilutGLLoadImage("some-picture.jpg");

Only I’m damned if I could get it to work, through I could take the long route and get DevIL to load/bind textures just fine, so I went and wrote my own version of ilutGLLoadImage, which works wonderfully =D

OpenGL DevIL Image Loader

Here’s the code:

  1. // Function load a image, turn it into a texture, and return the texture ID as a GLuint for use
  2. GLuint loadImage(const char* theFileName)
  3. {
  4. 	ILuint imageID;				// Create an image ID as a ULuint
  5.  
  6. 	GLuint textureID;			// Create a texture ID as a GLuint
  7.  
  8. 	ILboolean success;			// Create a flag to keep track of success/failure
  9.  
  10. 	ILenum error;				// Create a flag to keep track of the IL error state
  11.  
  12. 	ilGenImages(1, &imageID); 		// Generate the image ID
  13.  
  14. 	ilBindImage(imageID); 			// Bind the image
  15.  
  16. 	success = ilLoadImage(theFileName); 	// Load the image file
  17.  
  18. 	// If we managed to load the image, then we can start to do things with it...
  19. 	if (success)
  20. 	{
  21. 		// If the image is flipped (i.e. upside-down and mirrored, flip it the right way up!)
  22. 		ILinfo ImageInfo;
  23. 		iluGetImageInfo(&ImageInfo);
  24. 		if (ImageInfo.Origin == IL_ORIGIN_UPPER_LEFT)
  25. 		{
  26. 			iluFlipImage();
  27. 		}
  28.  
  29. 		// Convert the image into a suitable format to work with
  30. 		// NOTE: If your image contains alpha channel you can replace IL_RGB with IL_RGBA
  31. 		success = ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
  32.  
  33. 		// Quit out if we failed the conversion
  34. 		if (!success)
  35. 		{
  36. 			error = ilGetError();
  37. 			std::cout << "Image conversion failed - IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
  38. 			exit(-1);
  39. 		}
  40.  
  41. 		// Generate a new texture
  42. 		glGenTextures(1, &textureID);
  43.  
  44. 		// Bind the texture to a name
  45. 		glBindTexture(GL_TEXTURE_2D, textureID);
  46.  
  47. 		// Set texture clamping method
  48. 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  49. 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  50.  
  51. 		// Set texture interpolation method to use linear interpolation (no MIPMAPS)
  52. 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  53. 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  54.  
  55. 		// Specify the texture specification
  56. 		glTexImage2D(GL_TEXTURE_2D, 				// Type of texture
  57. 					 0,				// Pyramid level (for mip-mapping) - 0 is the top level
  58. 					 ilGetInteger(IL_IMAGE_FORMAT),	// Internal pixel format to use. Can be a generic type like GL_RGB or GL_RGBA, or a sized type
  59. 					 ilGetInteger(IL_IMAGE_WIDTH),	// Image width
  60. 					 ilGetInteger(IL_IMAGE_HEIGHT),	// Image height
  61. 					 0,				// Border width in pixels (can either be 1 or 0)
  62. 					 ilGetInteger(IL_IMAGE_FORMAT),	// Format of image pixel data
  63. 					 GL_UNSIGNED_BYTE,		// Image data type
  64. 					 ilGetData());			// The actual image data itself
  65.  	}
  66.   	else // If we failed to open the image file in the first place...
  67.   	{
  68. 		error = ilGetError();
  69. 		std::cout << "Image load failed - IL reports error: " << error << " - " << iluErrorString(error) << std::endl;
  70. 		exit(-1);
  71.   	}
  72.  
  73.  	ilDeleteImages(1, &imageID); // Because we have already copied image data into texture data we can release memory used by image.
  74.  
  75. 	std::cout << "Texture creation successful." << std::endl;
  76.  
  77. 	return textureID; // Return the GLuint to the texture so you can use it!
  78. }

This means that if I had a picture called SpringLandscape.jpg in the same folder as my code, I could bind it to a texture like this:

GLuint textureHandle = loadImage("SpringLandscape.jpg");

Ha! I’m getting the hang of this coding lark… =P

Here’s some complete example code to get you up and running: DevIL-Image-Loader.cpp

Update 11/2010: I had a think about this the next day and decided I wanted the best quality filtering available (which is MipMaps using the GL_LINEAR_MIPMAP_LINEAR function), so to do this you can replace lines 51 to 53 with:

// Set texture interpolation method to the highest visual quality it can be:
// GL_LINEAR_MIPMAP_LINEAR for minification - i.e. trilinear filtering
// GL_LINEAR for magnification (choices are either GL_NEAREST or GL_LINEAR - we do not use any MIPMAP settings for magnification!!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

And then straight after the glTexImage2D statement, add this line:

glGenerateMipmap(GL_TEXTURE_2D); // Note: This requires OpenGL 3.0 or higher

You’re going to have to abide by that caveat though and use GLEW or something to make the extension available to you, I’d suggest putting something like this into your code immediately after initialising your OpenGL context:

//  ----- Initialise GLEW -----
 
GLenum err = glewInit();
if (GLEW_OK != err)
{
	cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl;
	exit(-1);
}
cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << endl;

Cheers!

Update 09/2011: To use any DevIL functionality in your C++ project, you need to link in libIL, libILU and libILUT (in that order), and then initialise DevIL before you use it with:

	 //  ----- Initialise DevIL -----
	ilutRenderer(ILUT_OPENGL);
	ilInit();
	iluInit();
	ilutInit();
	ilutRenderer(ILUT_OPENGL);

You either have to specify your renderer before or after initialisation, and I don’t know which, so I just call it twice and don’t worry about it. If you know definitively whether it should be called before or after the il/ilu/ilut initialisation calls then share the wealth, bubba ;)