Loading Compressed And Uncompressed TGA'sLoading Uncompressed and RunLength Encoded TGA images By Evan "terminate" Pipho. I've seen lots of people ask around in #gamdev, the gamedev forums, and other places about TGA loading. The following code and explanation will show you how to load both uncompressed TGA files and RLE compressed files. This particular tutorial is geared toward OpenGL, but I plan to make it more universal in the future. We will begin with the two header files. The first file will hold our texture structure, the second, structures and variables used by the loading code. Like every header file we need some inclusion guards to prevent the file from being included multiple times. At the top of the file add these lines: #ifndef __TEXTURE_H__ // See If The Header Has Been Defined Yet #define __TEXTURE_H__ // If Not, Define It. Then scroll all the way down to the bottom and add: #endif // __TEXTURE_H__ End Inclusion Guard These three lines prevent the file from being included more than once into a file. The rest of the code in the file will be between the first two, and the last line. Into this header file we we will insert the standard headers we will need for everything we do. Add the following lines after the #define __TGA_H__ command. #pragma comment(lib, "OpenGL32.lib") // Link To Opengl32.lib #include <windows.h> // Standard Windows header #include <stdio.h> // Standard Header For File I/O #include <gl\gl.h> // Standard Header For OpenGL The first header is the standard windows header, the second is for the file I/O functions we will be using later, and the 3rd is the standard OpenGL header file for OpenGL32.lib. We will need a place to store image data and specifications for generating a texture usable by OpenGL. We will use the following structure. typedef struct { GLubyte* imageData; // Hold All The Color Values For The Image. GLuint bpp; // Hold The Number Of Bits Per Pixel. GLuint width; // The Width Of The Entire Image. GLuint height; // The Height Of The Entire Image. GLuint texID; // Texture ID For Use With glBindTexture. GLuint type; // Data Stored In * ImageData (GL_RGB Or GL_RGBA) } Texture; Next come two more structures used during processing of the TGA file. typedef struct { GLubyte Header[12]; // File Header To Determine File Type } TGAHeader; typedef struct { GLubyte header[6]; // Holds The First 6 Useful Bytes Of The File GLuint bytesPerPixel; // Number Of BYTES Per Pixel (3 Or 4) GLuint imageSize; // Amount Of Memory Needed To Hold The Image GLuint type; // The Type Of Image, GL_RGB Or GL_RGBA GLuint Height; // Height Of Image GLuint Width; // Width Of Image GLuint Bpp; // Number Of BITS Per Pixel (24 Or 32) } TGA; Now we declare some instances of our two structures so we can use them within our code. TGAHeader tgaheader; // Used To Store Our File Header TGA tga; // Used To Store File Information We need to define a couple file headers so we can tell the program what kinds of file headers are on valid images. The first 12 bytes will be 0 0 2 0 0 0 0 0 0 0 0 0 if it is uncompressed TGA image and 0 0 10 0 0 0 0 0 0 0 0 0 if it an RLE compressed one. These two values allow us to check to see if the file we are reading is valid. // Uncompressed TGA Header GLubyte uTGAcompare[12] = {0,0, 2,0,0,0,0,0,0,0,0,0}; // Compressed TGA Header GLubyte cTGAcompare[12] = {0,0,10,0,0,0,0,0,0,0,0,0}; Finally we declare a pair of functions to use in the loading process. // Load An Uncompressed File bool LoadUncompressedTGA(Texture *, char *, FILE *); // Load A Compressed File bool LoadCompressedTGA(Texture *, char *, FILE *); Now, on to the cpp file, and the real brunt of the code. I will leave out some of the error message code and the like to make the tutorial shorter and more readable. You may look in the included file (link at the bottom of the article) Right off the bat we have to include the file we just made so at the top of the file put. #include "tga.h" // Include The Header We Just Made We don't have to include any other files, because we included them inside our header we just completed. The next thing we see is the first function, which is called LoadTGA(...). // Load A TGA File! bool LoadTGA(Texture * texture, char * filename) { It takes two parameters. The first is a pointer to a Texture structure, which you must have declared in your code some where (see included example). The second parameter is a string that tells the computer where to find your texture file. The first two lines of the function declare a file pointer and then open the file specified by "filename" which was passed to the function in the second parameter for reading. FILE * fTGA; // Declare File Pointer fTGA = fopen(filename, "rb"); // Open File For Reading The next few lines check to make sure that the file opened correctly. if(fTGA == NULL) // If Here Was An Error { ...Error code... return false; // Return False } Next we try to read the first twelve bytes of the file and store them in our TGAHeader structure so we can check on the file type. If fread fails, the file is closed, an error displayed, and the function returns false. // Attempt To Read The File Header if(fread(&tgaheader, sizeof(TGAHeader), 1, fTGA) == 0) { ...Error code here... return false; // Return False If It Fails } Next we try to determine what type of file it is by comparing our newly aquired header with our two hard coded ones. This will tell us if its compressed, uncompressed, or even if its the wrong file type. For this purpose we will use the memcmp(...) function. // If The File Header Matches The Uncompressed Header if(memcmp(uTGAcompare, &tgaheader, sizeof(tgaheader)) == 0) { // Load An Uncompressed TGA LoadUncompressedTGA(texture, filename, fTGA); } // If The File Header Matches The Compressed Header else if(memcmp(cTGAcompare, &tgaheader, sizeof(tgaheader)) == 0) { // Load A Compressed TGA LoadCompressedTGA(texture, filename, fTGA); } else // If It Doesn't Match Either One { ...Error code here... return false; // Return False } We will begin this section with the loading of an UNCOMPRESSED file. This function is heavily based on NeHe's code in lesson 25. First thing we come to, as always, is the function header. // Load An Uncompressed TGA! bool LoadUncompressedTGA(Texture * texture, char * filename, FILE * fTGA) { This function takes three parameters. The first two are the same as from LoadTGA, and are simply passed on. The third is the file pointer from the previous function so that we don't lose our place. Next we try to read the next 6 bytes of the file, and store them in tga.header. If it fails, we run some error code, and return false. // Attempt To Read Next 6 Bytes if(fread(tga.header, sizeof(tga.header), 1, fTGA) == 0) { ...Error code here... return false; // Return False } Now we have all the information we need to calculate the height, width and bpp of our image. We store it in both the texture and local structure. texture->width = tga.header[1] * 256 + tga.header[0]; // Calculate Height texture->height = tga.header[3] * 256 + tga.header[2]; // Calculate The Width texture->bpp = tga.header[4]; // Calculate Bits Per Pixel tga.Width = texture->width; // Copy Width Into Local Structure tga.Height = texture->height; // Copy Height Into Local Structure tga.Bpp = texture->bpp; // Copy Bpp Into Local Structure Now we check to make sure that the height and width are at least one pixel, and that the bpp is either 24 or 32. If any of the values are outside their boundaries we again display an error, close the file, and leave the function. // Make Sure All Information Is Valid if((texture->width <= 0) || (texture->height <= 0) || ((texture->bpp != 24) && (texture->bpp !=32))) { ...Error code here... return false; // Return False } Next we set the type of the image. 24 bit images are type GL_RGB and 32 bit images are type GL_RGBA if(texture->bpp == 24) // Is It A 24bpp Image? texture->type = GL_RGB; // If So, Set Type To GL_RGB else // If It's Not 24, It Must Be 32 texture->type = GL_RGBA; // So Set The Type To GL_RGBA Now we calculate the BYTES per pixel and the total size of the image data. tga.bytesPerPixel = (tga.Bpp / 8); // Calculate The BYTES Per Pixel // Calculate Memory Needed To Store Image tga.imageSize = (tga.bytesPerPixel * tga.Width * tga.Height); We need some place to store all that image data so we will use malloc to allocate the right amount of memory. Then we check to make sure memory was allocated, and is not NULL. If there was an error, run error handling code. // Allocate Memory texture->imageData = (GLubyte *)malloc(tga.imageSize); if(texture->imageData == NULL) // Make Sure It Was Allocated Ok { ...Error code here... return false; // If Not, Return False } Here we try to read all the image data. If we can't, we trigger error code again. // Attempt To Read All The Image Data if(fread(texture->imageData, 1, tga.imageSize, fTGA) != tga.imageSize) { ...Error code here... return false; // If We Cant, Return False } TGA files store their image in reverse order than what OpenGL wants so we must change the format from BGR to RGB. To do this we swap the first and third bytes in every pixel. Steve Thomas Adds: I've got a little speedup in TGA loading code. It concerns switching BGR into RGB using only 3 binary operations. Instead of using a temp variable you XOR the two bytes 3 times. Then we close the file, and exit the function successfully. // Start The Loop for(GLuint cswap = 0; cswap < (int)tga.imageSize; cswap += tga.bytesPerPixel) { // 1st Byte XOR 3rd Byte XOR 1st Byte XOR 3rd Byte texture->imageData[cswap] ^= texture->imageData[cswap+2] ^= texture->imageData[cswap] ^= texture->imageData[cswap+2]; } fclose(fTGA); // Close The File return true; // Return Success } Thats all there is to loading an uncompressed TGA file. Loading a RLE compressed one is only slightly harder. We read the header and collect height/width/bpp as usual, same as the uncompressed version, so i will just post the code, you can look in the previous pages for a complete explanation. bool LoadCompressedTGA(Texture * texture, char * filename, FILE * fTGA) { if(fread(tga.header, sizeof(tga.header), 1, fTGA) == 0) { ...Error code here... } texture->width = tga.header[1] * 256 + tga.header[0]; texture->height = tga.header[3] * 256 + tga.header[2]; texture->bpp = tga.header[4]; tga.Width = texture->width; tga.Height = texture->height; tga.Bpp = texture->bpp; if((texture->width <= 0) || (texture->height <= 0) || ((texture->bpp != 24) && (texture->bpp !=32))) { ...Error code here... } } if(texture->bpp == 24) // Is It A 24bpp Image? texture->type = GL_RGB; // If So, Set Type To GL_RGB else // If It's Not 24, It Must Be 32 texture->type = GL_RGBA; // So Set The Type To GL_RGBA tga.bytesPerPixel = (tga.Bpp / 8); tga.imageSize = (tga.bytesPerPixel * tga.Width * tga.Height); Now we need to allocate the amount of storage for the image to use AFTER we uncompress it, we will use malloc. If memory fails to be allocated, run error code, and return false. // Allocate Memory To Store Image Data texture->imageData = (GLubyte *)malloc(tga.imageSize); if(texture->imageData == NULL) // If Memory Can Not Be Allocated... { ...Error code here... return false; // Return False } Next we need to determine how many pixels make up the image. We will store it in the variable "pixelcount" We also need to store which pixel we are currently on, and what byte of the imageData we are writing to, to avoid overflows and overwriting old data. We will allocate enough memory to store one pixel. GLuint pixelcount = tga.Height * tga.Width; // Number Of Pixels In The Image GLuint currentpixel = 0; // Current Pixel We Are Reading From Data GLuint currentbyte = 0; // Current Byte We Are Writing Into Imagedata // Storage For 1 Pixel GLubyte * colorbuffer = (GLubyte *)malloc(tga.bytesPerPixel); Next we have a big loop. Lets break it down into more manageable chunks. First we declare a variable in order to store the chunk header. A chunk header dictates whether the following section is RLE, or RAW, and how long it is. If the one byte header is less than or equal to 127, then it is a RAW header. The value of the header is the number of colors, minus one, that we read ahead and copy into memory, before we hit another header byte. So we add one to the value we get, and then read that many pixels and copy them into the ImageData, just like we did with the uncompressed ones. If the header is ABOVE 127, then it is the number of times that the next pixel value is repeated consequtively. To get the actual number of repetitions we take the value returned and subtract 127 to get rid of the one bit header identifier. Then we read the next one pixel and copy it the said number of times consecutively into the memory. On to the code. First we read the one byte header. do // Start Loop { GLubyte chunkheader = 0; // Variable To Store The Value Of The Id Chunk if(fread(&chunkheader, sizeof(GLubyte), 1, fTGA) == 0) // Attempt To Read The Chunk's Header { ...Error code... return false; // If It Fails, Return False } Next we will check to see if it a RAW header. If it is, we need to add one to the value to get the total number of pixels following the header. if(chunkheader < 128) // If The Chunk Is A 'RAW' Chunk { chunkheader++; // Add 1 To The Value To Get Total Number Of Raw Pixels We then start another loop to read all the color information. It will loop the amout of times specified in the chunk header, and will read and store one pixel each loop. First we read and verify the pixel data. The data for one pixel will be stored in the colorbuffer variable. Next we will check to see if it a RAW header. If it is, we need to add one to the value to get the total number of pixels following the header. // Start Pixel Reading Loop for(short counter = 0; counter < chunkheader; counter++) { // Try To Read 1 Pixel if(fread(colorbuffer, 1, tga.bytesPerPixel, fTGA) != tga.bytesPerPixel) { ...Error code... return false; // If It Fails, Return False } The next part in our loop will take the color values stored in colorbuffer and writing them to the imageData variable to be used later. In the process it will flip the data from BGR format to RGB or from BGRA to RGBA depending on the number of bits per pixel. When we are done we increment the current byte, and current pixel counters. texture->imageData[currentbyte] = colorbuffer[2]; // Write The 'R' Byte texture->imageData[currentbyte + 1 ] = colorbuffer[1]; // Write The 'G' Byte texture->imageData[currentbyte + 2 ] = colorbuffer[0]; // Write The 'B' Byte if(tga.bytesPerPixel == 4) // If It's A 32bpp Image... { texture->imageData[currentbyte + 3] = colorbuffer[3]; // Write The 'A' Byte } // Increment The Byte Counter By The Number Of Bytes In A Pixel currentbyte += tga.bytesPerPixel; currentpixel++; // Increment The Number Of Pixels By 1 The next section deals with the chunk headers that represent the RLE sections. First thing we do is subtract 127 from the chunkheader to get the amount of times the next color is repeated. else // If It's An RLE Header { chunkheader -= 127; // Subtract 127 To Get Rid Of The ID Bit Then we attempt to read the next color value. // Read The Next Pixel if(fread(colorbuffer, 1, tga.bytesPerPixel, fTGA) != tga.bytesPerPixel) { ...Error code... return false; // If It Fails, Return False } Next we begin a loop to copy the pixel we just read into memory multiple times, as dictated by the value from the RLE header. Then we copy the color values into the image data, performing the R and B value switch. Then we increment the current bytes, and current pixel, so we are in the right spot when we write the values again. // Start The Loop for(short counter = 0; counter < chunkheader; counter++) { // Copy The 'R' Byte texture->imageData[currentbyte] = colorbuffer[2]; // Copy The 'G' Byte texture->imageData[currentbyte + 1 ] = colorbuffer[1]; // Copy The 'B' Byte texture->imageData[currentbyte + 2 ] = colorbuffer[0]; if(tga.bytesPerPixel == 4) // If It's A 32bpp Image { // Copy The 'A' Byte texture->imageData[currentbyte + 3] = colorbuffer[3]; } currentbyte += tga.bytesPerPixel; // Increment The Byte Counter currentpixel++; // Increment The Pixel Counter Then we contiune the main loop, as long as we still have pixels left to read. Last of all we close up the file and return success. while(currentpixel < pixelcount); // More Pixels To Read? ... Start Loop Over fclose(fTGA); // Close File return true; // Return Success } Now you have some image data ready for glGenTextures and glBindTexture. I suggest you check out NeHe's tutorial #6 and #24 for info on these commands. That concludes my first ever tutorial. I do not guarantee my code is error free, though i made an effort to see that it was. Special thanks to Jeff "NeHe" Molofee for his great tutorials and to Trent "ShiningKnight" Polack for helping me revise this tutorial. If you find errors, have suggestions, or comments please feel free to email me (terminate@gdnmail.net), or ICQ me at UIN# 38601160. Enjoy! Evan Pipho (Terminate) Jeff Molofee (NeHe) * DOWNLOAD Visual C++ Code For This Lesson. * DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl )
|