Sam Afshari's Notes
  • GitHub
  • Twitter/X
  • All posts
  • Ed
  • NuGets
  • POIWorld
  • RedCorners

Loading Transparent PNGs in OpenGL for Dummies! - Thu, Jun 30, 2011

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 LodePNG::LoadFile( vector, std::string ) 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);

Back to Home


© Sam Afshari 2024