Wat!

Loading Transparent PNGs in OpenGL for Dummies!

I’m writing this because as far as I can remember, nothing has taken my time this much. I woke up yesterday and began working on a PNG Importer for my new OpenGL/C++ based side scroller game engine which I’m going to use to make my first iOS games, and after tons of research I realized it’s three in the morning and I haven’t yet figured out how to load transparent PNGs in my game.

Okay, let’s back off for a sec.

Previously I wrote I’m going to give something called AirPlay SDK a try and write my iOS games with that. My reason was to skip Objective-C and be able to make a cross-platform code that works on Android as well. I looked at some of the examples for AirPlay SDK and found it very cryptic. That’s when I told myself “Screw it, Let’s go with Objective-C like the rest of the world.”

So I set up a Mac OS X virtual machine and created a test iOS project to see how writing Objective-C feels like. It’s actually a pretty straightforward language (even though it feels very very uncomfortable for those who had experience with C++-like languages the first time they see it), and you can write normal C code in it with no problems. Sweet.

So again I was getting comfy choosing to write my code in Objective-C, but then I learned that if I rename my source files from .m to .mm, the compiler compiles them as something it calls Objective-C++ which allows me to write my engine in C++! But with a few limitations of course:

  • A C++ class cannot derive from an Objective-C class and vice versa.
  • C++ namespaces cannot be declared inside an Objective-C declaration.
  • Objective-C classes cannot have instance variables of C++ classes that do not have a default constructor or that have one or more virtual methods, but pointers to C++ objects can be used as instance variables without restriction (allocate them with new in the -init method).
  • C++ "by value" semantics cannot be applied to Objective-C objects, which are only accessible through pointers.
  • An Objective-C declaration cannot be within a C++ template declaration and vice versa. However, Objective-C types, (e.g., Classname *) can be used as C++ template parameters.
  • Objective-C and C++ exception handling is distinct; the handlers of each cannot handle exceptions of the other type.
  • Care must be taken since the destructor calling conventions of Objective-C and C++’s exception run-time models do not match (i.e., a C++ destructor will not be called when an Objective-C exception exits the C++ object’s scope). The new 64-bit runtime resolves this by introducing interoperability with C++ exceptions in this sense.

Basically it means that you can write your C++ code and Objective-C code in the same file, but you can’t mix their stuff together. So for example I have to connect my engine code which is going to be written in C++ to another Interface code which handles the iOS stuff and is written in Objective-C. It’s gonna be a little bit messy, but it gives me the freedom to write my main code in C++, which is pretty cool.

Being able to write my engine in regular C++ gives me the advantage to write my code on Windows and Visual Studio and makes it much easier to test. So I made a Win32 project and began writing an OpenGL based engine.

So, back to the PNG stuff…

There are lots of tutorials on importing TGA files on OpenGL, but TGA is a crappy format. I chose to go with PNG because it’s way easier to work with. So I began searching for docs and guides and tutorials on Importing PNG files in OpenGL. I found libpng which seems to be the official SDK for working with PNGs.

I downloaded the source code package, which contains a lot of source files and is written in C, and copied all of them to my project Smile. Then I realized that libpng needs another library called zlib to function. So I downloaded zlib’s source files and again copied them all to my project. This is of course not the right way to use another library in a project, but I wanted my code to be as portable as possible. whatever.

I wasn’t in the mood to read all those docs for libpng to figure out a task as simple as reading PNGs and converting them to raw bytes in the memory. So I tried to find a sample code that does this instead. I found this in wikibooks:

#include <GL/gl.h>
#include <GL/glu.h>
#include <png.h>
#include <cstdio>
#include <string>

#define TEXTURE_LOAD_ERROR 0

using namespace std;
/** loadTexture
*     loads a png file into an opengl texture object, using cstdio , libpng, and opengl.
* 
*     param filename : the png file to be loaded
*     param width : width of png, to be updated as a side effect of this function
*     param height : height of png, to be updated as a side effect of this function
* 
*     return GLuint : an opengl texture id.  Will be 0 if there is a major error,
*                                     should be validated by the client of this function.
* 
*/

GLuint loadTexture(const string filename, int &width, int &height) 
{
   //header for testing if it is a png
   png_byte header[8];
 
   //open file as binary
   FILE *fp = fopen(filename.c_str(), "rb");
   if (!fp) {
     return TEXTURE_LOAD_ERROR;
   }
 
   //read the header
   fread(header, 1, 8, fp);
 
   //test if png
   int is_png = !png_sig_cmp(header, 0, 8);
   if (!is_png) {
     fclose(fp);
     return TEXTURE_LOAD_ERROR;
   }
 
   //create png struct
   png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
       NULL, NULL);
   if (!png_ptr) {
     fclose(fp);
     return (TEXTURE_LOAD_ERROR);
   }
 
   //create png info struct
   png_infop info_ptr = png_create_info_struct(png_ptr);
   if (!info_ptr) {
     png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
     fclose(fp);
     return (TEXTURE_LOAD_ERROR);
   }
 
   //create png info struct
   png_infop end_info = png_create_info_struct(png_ptr);
   if (!end_info) {
     png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
     fclose(fp);
     return (TEXTURE_LOAD_ERROR);
   }
 
   //png error stuff, not sure libpng man suggests this.
   if (setjmp(png_jmpbuf(png_ptr))) {
     png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
     fclose(fp);
     return (TEXTURE_LOAD_ERROR);
   }
 
   //init png reading
   png_init_io(png_ptr, fp);
 
   //let libpng know you already read the first 8 bytes
   png_set_sig_bytes(png_ptr, 8);
 
   // read all the info up to the image data
   png_read_info(png_ptr, info_ptr);
 
   //variables to pass to get info
   int bit_depth, color_type;
   png_uint_32 twidth, theight;
 
   // get info about png
   png_get_IHDR(png_ptr, info_ptr, &twidth, &theight, &bit_depth, &color_type,
       NULL, NULL, NULL);
 
   //update width and height based on png info
   width = twidth;
   height = theight;
 
   // Update the png info struct.
   png_read_update_info(png_ptr, info_ptr);
 
   // Row size in bytes.
   int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
 
   // Allocate the image_data as a big block, to be given to opengl
   png_byte *image_data = new png_byte[rowbytes * height];
   if (!image_data) {
     //clean up memory and close stuff
     png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
     fclose(fp);
     return TEXTURE_LOAD_ERROR;
   }
 
   //row_pointers is for pointing to image_data for reading the png with libpng
   png_bytep *row_pointers = new png_bytep[height];
   if (!row_pointers) {
     //clean up memory and close stuff
     png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
     delete[] image_data;
     fclose(fp);
     return TEXTURE_LOAD_ERROR;
   }
   // set the individual row_pointers to point at the correct offsets of image_data
   for (int i = 0; i < height; ++i)
     row_pointers[height - 1 - i] = image_data + i * rowbytes;
 
   //read the png into image_data through row_pointers
   png_read_image(png_ptr, row_pointers);
 
   //Now generate the OpenGL texture object
   GLuint texture;
   glGenTextures(1, &texture);
   glBindTexture(GL_TEXTURE_2D, texture);
   glTexImage2D(GL_TEXTURE_2D,0, GL_RGBA, width, height, 0,
       GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*) image_data);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 
   //clean up memory and close stuff
   png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
   delete[] image_data;
   delete[] row_pointers;
   fclose(fp);
 
   return texture;
}

The image_data pointer in this code contains what I need. With a single glDrawPixels call I could draw the image on the screen.

But there’s a tiny problem. libpng is very big. I had to add tons of files to my project to be able to render a tiny image. So I found another library called lodepng. It’s made of only two files: A source file and a header, and can load the image with no problems. It’s also easier to understand since it’s written in C++ instead of C. You can download the files from here.

So here’s how to load a PNG into memory using lodepng:

  1. Add lodepng.h and lodepng.cpp to your project.
  2. Load the PNG using LodePNGLoadFile( vector, stdstring ) function.
  3. Decode your data using LodePNG::Decoder class.
  4. Your image is now ready to be rendered. You might have to flip it though.

This code can get you going:

GLboolean NTexture::load(string path)
{
	std::vector rawImage;

	LodePNG::loadFile( rawImage, path );
	LodePNG::Decoder decoder;
	std::vector image;

	decoder.decode( image, rawImage.empty() ? 0 : &rawImage[0],

		(unsigned)rawImage.size() );

	size.x = decoder.getWidth();
	size.y = decoder.getHeight();
	imageSize = image.size();
	colorDepth = decoder.getBpp();
	format = GL_RGBA;
	type = GL_UNSIGNED_BYTE;
	pixmap = new GLubyte[imageSize];
	
	//
	// Flip and invert the PNG image since OpenGL likes to load everything
	// backwards from what is considered normal!
	//
	unsigned char *imagePtr = &image[0];
	int halfTheHeightInPixels = decoder.getHeight() / 2;
	int heightInPixels = decoder.getHeight();
	
	// Assuming RGBA for 4 components per pixel.
	int numColorComponents = 4;
	// Assuming each color component is an unsigned char.
	int widthInChars = decoder.getWidth() * numColorComponents;
	unsigned char *top = NULL;

	unsigned char *bottom = NULL;
	unsigned char temp = 0;
	for( int h = 0; h < halfTheHeightInPixels; ++h )

	{

		top = imagePtr + h * widthInChars;
		bottom = imagePtr + (heightInPixels - h - 1) * widthInChars;
		for( int w = 0; w < widthInChars; ++w )
		{

			// Swap the chars around.
			temp = *top;
			*top = *bottom;
			*bottom = temp;
			++top;
			++bottom;
		}
	}
	
	unsigned int c=0;
	for (unsigned int x = 0; x < size.x; x++)
	for (unsigned int y=0; y<size.y; y++)
		for (unsigned int co=0; co<numColorComponents; co++)
			((GLubyte*)pixmap)[c++]=image[c];

	return TRUE;
}

To be able to render transparent PNGs, you have to enable blending like this:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Sam Afshari's Notes June 30, 2011

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.

An unhandled error has occurred. Reload 🗙