Lesson 8: Parsing and loading custom image formats, TGA
In this tutorial we’ll look into custom image formats. Al though you can get away with most of the image formats provided by an additional library, at some point you may want to load a custom format. Proprietary video games often have their own custom image formats. In our case we’ll load an image stored in the uncompressed Truevision TGA format. The file contains a description header and raw image data. The header looks like this:
Field no. | Length | Field name | Description |
---|---|---|---|
1 | 1 byte | ID length | Length of the image ID field |
2 | 1 byte | Color map type | Whether a color map is included |
3 | 1 byte | Image type | Compression and color types |
4 | 5 bytes | Color map specification | Describes the color map |
5 | 10 bytes | Image specification | Image dimensions and format |
We could define a struct defining how to load this header, and this is exactly what we will do:
struct tga_header { unsigned char idLength; unsigned char colorMapType; unsigned char imageTypeCode; unsigned char colorMapSpec[5]; unsigned short xOrigin; unsigned short yOrigin; unsigned short width; unsigned short height; unsigned char bpp; unsigned char imageDesc; }; tga_header tgaheader;
We would also need to store the raw image data, thus we define:
char *imageData;
Finally we would need methods to load, parse and close the image. We end up with this class:
class TGA { public: bool Load(const char *filename); void Release(); void Draw(); private: char *imageData; struct tga_header { unsigned char idLength; unsigned char colorMapType; unsigned char imageTypeCode; unsigned char colorMapSpec[5]; unsigned short xOrigin; unsigned short yOrigin; unsigned short width; unsigned short height; unsigned char bpp; unsigned char imageDesc; }; tga_header tgaheader; };
Thus, we have defined the general data structure to hold the image data. However, we do not have the functionality yet.
We can load the entire hire as memory block into our data stucture using this function call :
filestr.read((char*) &tgaheader , sizeof(struct tga_header));
Finally, we can use these methods to load, parse and draw the image:
void TGA::Draw() { glPixelStorei (GL_UNPACK_ROW_LENGTH, this->tgaheader.width); if (this->tgaheader.bpp == 32) { glPixelStorei(GL_UNPACK_ALIGNMENT, 2); glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGBA, GL_UNSIGNED_BYTE, this->imageData); } if (this->tgaheader.bpp == 24) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGB, GL_UNSIGNED_BYTE, this->imageData); } } void TGA::Release() { free(imageData); // Free image data from memory } bool TGA::Load(const char* filename) { fstream filestr; filestr.open (filename, ios::in | ios::binary); // Open file if (filestr.is_open()) // Do the following actions, if file is opened { // read TGA header filestr.read((char*) &tgaheader , sizeof(struct tga_header)); // Read tga header. For more information: see tga.h and link above printf("image type: %i \n", tgaheader.imageTypeCode); // read pixel data int imageSize = tgaheader.width * tgaheader.height * tgaheader.bpp; // Calculate image size this->imageData = (char*) malloc(imageSize); // Reserve space in the memory to store our image data filestr.read((char*) this->imageData, imageSize); // Read image data from file, into the reserved memory place /* * TGA is stored in BGR (Blue-Green-Red) format, * we need to convert this to Red-Green-Blue (RGB). * The following section does BGR to RGB conversion */ if (tgaheader.bpp == 24) { for (int i = 0; i imageData[i]; this->imageData[i] = this->imageData[i+2]; this->imageData[i+2] = c; } } else if (tgaheader.bpp == 32) { for (int i = 0; i imageData[i]; this->imageData[i] = this->imageData[i+2]; this->imageData[i+2] = c; } } filestr.close(); } else { coutNow that we have the functionality to load, display and parse the data we are not done yet. We still need the OpenGL scene where we define the entire scene, viewport etc.
We end up with this code:
/* * OpenGL Tutorials (https://talkera.org/opengl) * VC++ users should create a Win32 Console project and link * the program with glut32.lib, glu32.lib, opengl32.lib * * GLUT can be downloaded from http://www.xmission.com/~nate/glut.html * OpenGL is by default installed on your system. * */ /* * TGA Loader * * 24BPP or 32BPP TGA Images. * This loader does NOT support RLE Compression * * For more info about TGA: * http://en.wikipedia.org/wiki/Truevision_TGA */ #include#include #include #include // Standard header for MS Windows applications #include // Open Graphics Library (OpenGL) header #include // The GL Utility Toolkit (GLUT) Header #define KEY_ESCAPE 27 #define g_rotation_speed 0.2 float g_rotation; using namespace std; /************************************************************************************ * TGA CLASS ************************************************************************************/ class TGA { public: bool Load(const char *filename); void Release(); void Draw(); private: char *imageData; struct tga_header { unsigned char idLength; unsigned char colorMapType; unsigned char imageTypeCode; unsigned char colorMapSpec[5]; unsigned short xOrigin; unsigned short yOrigin; unsigned short width; unsigned short height; unsigned char bpp; unsigned char imageDesc; }; tga_header tgaheader; }; void TGA::Draw() { glPixelStorei (GL_UNPACK_ROW_LENGTH, this->tgaheader.width); if (this->tgaheader.bpp == 32) { glPixelStorei(GL_UNPACK_ALIGNMENT, 2); glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGBA, GL_UNSIGNED_BYTE, this->imageData); } if (this->tgaheader.bpp == 24) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGB, GL_UNSIGNED_BYTE, this->imageData); } } void TGA::Release() { free(imageData); // Free image data from memory } bool TGA::Load(const char* filename) { fstream filestr; filestr.open (filename, ios::in | ios::binary); // Open file if (filestr.is_open()) // Do the following actions, if file is opened { // read TGA header filestr.read((char*) &tgaheader , sizeof(struct tga_header)); // Read tga header. For more information: see tga.h and link above printf("image type: %i \n", tgaheader.imageTypeCode); // read pixel data int imageSize = tgaheader.width * tgaheader.height * tgaheader.bpp; // Calculate image size this->imageData = (char*) malloc(imageSize); // Reserve space in the memory to store our image data filestr.read((char*) this->imageData, imageSize); // Read image data from file, into the reserved memory place /* * TGA is stored in BGR (Blue-Green-Red) format, * we need to convert this to Red-Green-Blue (RGB). * The following section does BGR to RGB conversion */ if (tgaheader.bpp == 24) { for (int i = 0; i imageData[i]; this->imageData[i] = this->imageData[i+2]; this->imageData[i+2] = c; } } else if (tgaheader.bpp == 32) { for (int i = 0; i imageData[i]; this->imageData[i] = this->imageData[i+2]; this->imageData[i+2] = c; } } filestr.close(); } else { cout Note if you are on a unix machine you may need to change some code. In addition, this image parser does not deal with compressed TGA images. Finally, OpenGL likes images in a specific format, e.g. 256×256, 512×512, 1024×1024 and so on. In case the image is not correctly displayed, that might be the reason.