TGA Loading
This tutorial is far from visually stunning, but you will definitely learn a few new things by reading through it. I have had quite a few people ask me about extensions, and how to f ind out what extensions are supported on a particular brand of video card. This tutorial will teach you how to find out what OpenGL extensions are supported on any type of 3D video card.
I will also teach you how to scroll a portion of the screen without affecting any of the graphics around it using scissor testing. You will also learn how to draw line strips, and most importantly, in this tutorial we will drop the AUX library completely, along with Bitmap images. I will show you how to use Targa (TGA) images as textures. Not only are Targa files easy to work with and create, they support the ALPHA channel, which will allow you to create some pretty cool effects in future projects!
The first thing you should notice in the code below is that we no longer include the glaux header file (glaux.h). It is also important to note that the glaux.lib file can also be left out! We're not working with bitmaps anymore, so there's no need to include either of these files in our project.
Also, using glaux, I always received one warning message. Without glaux there should be zero errors, zero warnings.
#include <windows.h>// Header File For Windows
#include <stdio.h>// Header File For Standard Input / Output
#include <stdarg.h>// Header File For Variable Argument Routines
#include <string.h>// Header File For String Management
#include <gl\gl.h>// Header File For The OpenGL32 Library
#include <gl\glu.h>// Header File For The GLu32 Library HDC hDC=NULL;// Private GDI Device Context
HGLRC hRC=NULL; // Permanent Rendering Context HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance;// Holds The Instance Of The Application bool keys[256];// Array Used For The Keyboard Routine bool active=TRUE;// Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE;// Fullscreen Flag Set To Fullscreen Mode By Default
The first thing we need to do is add some variables. The first variable scroll will be used to scroll a portion of the screen up and down. The second variable maxtokens will be used t o keep track of how many tokens ( extensions) are supported by the video card.
base is used to hold the font display list.
swidth and sheight are used to grab the current window size. We use these two variable to help us calculate the scissor coordinates later in the code.
int scroll;// Used For Scrolling The Screen
int maxtokens;// Keeps Track Of The Number Of Extensions Supported int swidth;// Scissor Width
int sheight;// Scissor Height
GLuint base;// Base Display List For The Font
Lesson 24 – Tokens, Extensions, Scissor Testing And TGA Loading 141
Now we create a structure to hold the TGA information once we load it in. The first variable imageData will hold a pointer to the data that makes up the image. bpp will hold the bits per pixel used in the TGA file (this value should be 24 or 32 bits depending on whether or not there is an alpha channel). The third variable width will hold the width of the TGA image. height will hold the height of the image, and texID will be used to keep track of the textures once they are built. The structure will be called TextureImage.
The line just after the structure (TextureImage textures[1]) sets aside storage for the one texture that we will be using in this program.
typedef struct // Create A Structure {
GLubyte *imageData;// Image Data (Up To 32 Bits) GLuint bpp;// Image Color Depth In Bits Per Pixel GLuint width;// Image Width
GLuint height;// Image Height
GLuint texID;// Texture ID Used To Select A Texture } TextureImage; // Structure Name
TextureImage textures[1];// Storage For One Texture
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc
Now for the fun stuff! This section of code will load in a TGA file and convert it into a texture for use in the program. One thing to note is that this code will only load 24 or 32 bit uncompressed TGA files. I had a hard enough time making the code work with both 24 and 32 bit TGA's :) I never said I was a genious. I'd like to point out that I did not write all of this code on my own. Alot of the really good ideas I got f rom reading through random sites on the net. I just took all the good ideas and combined them i nto code that works well with OpenGL. Not easy, not extremely difficult!
We pass two parameters to this section of code. The first parameter points to memory that we can store the texture in (*texture).
The second parameter is the name of the file that we want to load (*filename).
The first variable TGAheader[ ] holds 12 bytes. We'll compare these bytes with the first 12 bytes we read from the TGA file to make sure that the file is indeed a Targa file, and not some other type of image.
TGAcompare will be used to hold the first 12 bytes we read in from the TGA file. The bytes in TGAcompare will then be compared with the bytes in TGAheader to make sure everything matches.
header[ ] will hold the first 6 IMPORTANT bytes from the header file (width, height, and bits per pixel).
The variable bytesPerPixel will store the result after we divide bits per pixel by 8, leaving us with the number of bytes used per pixel.
imageSize will store the number of bytes required to make up the image (width * height * bytes per pixel).
temp is a temporary variable that we will use to swap bytes later in the program.
The last variable type is a variable that I use to select the proper texture building params depending on whether or not the TGA is 24 or 32 bit. If the texture is 24 bit we need to use GL_RGB mode when we build the texture. If the TGA is 32 bit we need to add the Alpha component, meaning we have to use GL_RGBA ( By default I assume the i mage is 32 bit by default t hat is why type is GL_RGBA).
bool LoadTGA(TextureImage *texture, char *filename)// Loads A TGA File Into Memory {
GLubyte TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};// Uncompressed TGA Header GLubyte TGAcompare[12];// Used To Compare TGA Header
GLubyte header[6];// First 6 Useful Bytes From The Header
GLuint bytesPerPixel;// Holds Number Of Bytes Per Pixel Used In The TGA File GLuint imageSize;// Used To Store The Image Size When Setting Aside Ram GLuint temp;// Temporary Variable
GLuint type=GL_RGBA;// Set The Default GL Mode To RBGA (32 BPP)
The first line below opens the TGA file for reading. file is the handle we will use to point to the data within the file. the command fopen(filename, "rb") will open the file filename, and "rb" tells our program to open it for [r]eading in [b]inary mode!
The if statement has a few jobs. First off it checks to see if the file contains any data. If there is no data, NULL will be returned, the file will be closed with fclose(file), and we return false.
If the file contains information, we attempt to read the first 12 bytes of the file into TGAcompare. We break the line down like this:
fread will read sizeof(TGAcompare) (12 bytes) from file into TGAcompare. Then we check to see if the number of bytes read is equal to sizeof(TGAcompare) which should be 12 bytes. If we were unable to read the 12 bytes into TGAcompare the file will close and false will be returned.
If everything has gone good so far, we then compare the 12 bytes we read into TGAcompare with the 12 bytes we have stored in TGAheader. If the bytes do not match, the file will close, and false will be returned.
Lastly, if everything has gone great, we attempt to read 6 more bytes into header (the important bytes). If 6 bytes are not available, again, the file will close and the program will return false.
Lesson 24 – Tokens, Extensions, Scissor Testing And TGA Loading 142
FILE *file = fopen(filename, "rb");// Open The TGA File if( file==NULL ||// Does File Even Exist?
fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||// Are There 12 Bytes To Read?
memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 ||// Does The Header Match What We Want?
fread(header,1,sizeof(header),file)!=sizeof(header))// If So Read Next 6 Header Bytes {
if (file == NULL) // Did The File Even Exist? *Added Jim Strong*
return false;// Return False else
{
fclose(file);// If Anything Failed, Close The File return false;// Return False
} }
If everything went ok, we now have enough information to define some important variables. The first variable we want to define is width. We want width to equal the width of the TGA file. We can find out the TGA width by multiplying the value stored in header[1]
by 256. We then add the l owbyte which is stored in header[0].
The height is calculated the same way but instead of using the values stored in header[0] and header[1] we use the values store d in header[2] and header[3].
After we have calculated the width and height we check to see if either the width or height is less than or equal to 0. If either of the two variables is less than or equal to zero, the file will be closed, and false will be returned.
We also check to see if the TGA is a 24 or 32 bit image. We do this by checking the value stored at header[4]. If the value is not 24 or 32 (bit), the file will be closed, and false will be returned.
In case you have not realized. A return of false will cause the program to fail with the message "Initialization Failed". Make sure your TGA is an uncompressed 24 or 32 bi t image!
texture->width = header[1] * 256 + header[0];// Determine The TGA Width (highbyte*256+lowbyte) texture->height = header[3] * 256 + header[2];// Determine The TGA Height
(highbyte*256+lowbyte)
if( texture->width <=0 ||// Is The Width Less Than Or Equal To Zero texture->height <=0 ||// Is The Height Less Than Or Equal To Zero (header[4]!=24 && header[4]!=32))// Is The TGA 24 or 32 Bit?
{
fclose(file);// If Anything Failed, Close The File return false;// Return False
}
Now that we have calculated the image width and height we need to calculate the bits per pixel, bytes per pixel and image size.
The value in header[4] is the bits per pixel. So we set bpp to equal header[4].
If you know anything about bits and bytes, you know that 8 bits makes a byte. To figure out how many bytes per pixel the TGA uses, all we have to do is divide bits per pixel by 8. If the image is 32 bit, bytesPerPixel will equal 4. If the image is 24 bit, bytesPerPixel will equal 3.
To calculate the image size, we multiply width * height * bytesPerPixel. The result is stored in imageSize. If the image was 100x100x32 bit our image size would be 100 * 100 * 32/8 which equals 10000 * 4 or 40000 bytes!
texture->bpp = header[4];// Grab The TGA's Bits Per Pixel (24 or 32) bytesPerPixel = texture->bpp/8;// Divide By 8 To Get The Bytes Per Pixel
imageSize = texture->width*texture->height*bytesPerPixel;// Calculate The Memory Required For The TGA Data
Now that we know how many bytes our image is going to take, we need to allocate some memory. The fi rst line below does the trick. imageData will point to a section of ram big enough to hold our image. malloc(imagesize) allocates the memory (sets memory aside for us to use) based on the amount of ram we request (imageSize).
The "if" statement has a few tasks. First it checks to see if the memory was allocated properly. If not, imageData will equal NULL, the file will be closed, and false will be returned.
If the memory was allocated, we attempt to read the image data from the file into the allocated memory. The line
fread(texture->imageData, 1, imageSize, file) does the trick. fread means file read. imageData points to the memory we want to store the data in. 1 is the size of data we want to read in bytes (we want to read 1 byte at a time). imageSize is the total number of bytes we want to read. Because imageSize is equal to the total amount of ram required to hold the image, we end up reading in the entire image.
file is the handle for our open file.
After reading in the data, we check to see if the amount of data we read in is the same as the value stored in imageSize. If the amount of data read and the value of imageSize is not the same, something went wrong. If any data was loaded, we will free it.
(release the memory we allocated). The file will be closed, and false will be returned.
texture->imageData=(GLubyte *)malloc(imageSize);// Reserve Memory To Hold The TGA Data
Lesson 24 – Tokens, Extensions, Scissor Testing And TGA Loading 143
if( texture->imageData==NULL ||// Does The Storage Memory Exist?
fread(texture->imageData, 1, imageSize, file)!=imageSize)// Does The Image Size Match The Memory Reserved?
{
if(texture->imageData!=NULL) // Was Image Data Loaded free(texture->imageData); // If So, Release The Image Data fclose(file);// Close The File
return false;// Return False }
If the data was loaded properly, things are going good :) All we have to do now is swap the Red and Blue bytes. In OpenGL we use RGB (red, green, blue). The data in a TGA file is stored BGR (blue, green, red). If we didn't swap the red and blue bytes, anything in the picture that should be red would be blue and anything that should be blue would be red.
The first thing we do is create a loop (i) that goes from 0 to imageSize. By doing this, we can loop through all of the image data.
Our loop will increase by steps of 3 (0, 3, 6, 9, etc) if the TGA file is 24 bit, and 4 (0, 4, 8, 12, etc) if the image is 32 bit. The reason we increase by steps is so that the value at i is always going to be the first byte ([b]lue byte) in our group of 3 or 4 bytes.
Inside the loop, we store the [b]lue byte in our temp variable. We then grab the red byte which is stored at texture->imageData[i+2]
(Remember that TGAs store the colors as BGR[A]. B is i+0, G is i+1 and R is i+2) and store it where the [b]lue byte used to be.
Lastly we move the [b]lue byte that we stored in the temp variable to the location where the [r]ed byte used to be (i+2), and we close the file with fclose(file).
If everything went ok, the TGA should now be stored in memory as usable OpenGL texture data!
for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)// Loop Through The Image Data { // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
temp=texture->imageData[i];// Temporarily Store The Value At Image Data 'i'
texture->imageData[i] = texture->imageData[i + 2];// Set The 1st Byte To The Value Of The 3rd Byte
texture->imageData[i + 2] = temp;// Set The 3rd Byte To The Value In 'temp' (1st Byte Value) }
fclose (file);// Close The File
Now that we have usable data, it's time to make a texture from it. We start off by telling OpenGL we want to create a texture in the memory pointed to by &texture[0].texID.
It's important that you understand a few things before we go on. In the InitGL() code, when we call LoadTGA() we pass it two parameters. The first parameter is &textures[0]. In LoadTGA() we don't make reference to &textures[0]. We make reference to
&texture[0] (no 's' at the end). When we modify &texture[0] we are actually modifying textures[0]. texture[0] assumes the identity of textures[0]. I hope that makes sense.
So if we wanted to create a second texture, we would pass the parameter &textures[1]. In LoadTGA() any time we modified texture[0] we would be modifying textures[1]. If we passed &textures[2], texture[0] would assume the identity of &textures[2], etc.
Hard to explain, easy to understand. Of course I wont be happy until I make it really clear :) Last example in english using an example. Say I had a box. I called it box #10. I gave it to my friend and asked him to fill it up. My friend could care less what number it is. To him it's just a box. So he fills what he calls "just a box". He gives it back to me. To me he just filled Box #10 for me. To him he just filled a box. If I give him another box called box #11 and say hey, can you fill this. He'll again think of it as just
"box". He'll fill it and give it back to me full. To me he's just filled box #11 for me.
When I give LoadTGA &textures[1] it thinks of it as &texture[0]. It fills it with texture information, and once it's done I am left with a working textures[1]. If I give LoadTGA &textures[2] it again thinks of it as &texture[0]. It fills it with data, and I'm left with a working textures[2]. Make sense :)
Anyways... On to the code! We tell LoadTGA() to build our texture. We bind the texture, and tell OpenGL we want it to be linear filtered.
// Build A Texture From The Data
glGenTextures(1, &texture[0].texID);// Generate OpenGL texture IDs glBindTexture(GL_TEXTURE_2D, texture[0].texID);// Bind Our Texture
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// Linear Filtered glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Linear Filtered
Now we check to see if the TGA file was 24 or 32 bit. If the TGA was 24 bit, we set the type to GL_RGB. (no alpha channel). If we didn't do this, OpenGL would try to build a texture with an alpha channel. The alpha information wouldn't be t here, and the program would probably crash or give an error message.
if (texture[0].bpp==24)// Was The TGA 24 Bits {
type=GL_RGB; // If So Set The 'type' To GL_RGB }
Now we build our texture, the same way we've always done it. But instead of putting the type i n ourselves (GL_RGB or
GL_RGBA), we substitute the variable type. That way if the program detected that the TGA was 24 bit, the type will be GL_RGB. If
Lesson 24 – Tokens, Extensions, Scissor Testing And TGA Loading 144
our program detected that the TGA was 32 bit, the t ype would be GL_RGBA.
After the texture has been built, we return true. This lets the InitGL() code know that everything went ok.
glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);
return true;// Texture Building Went Ok, Return True }
The code below is our standard build a font from a t exture code. You've all seen this code before if you've gone through all the tutorials up until now. Nothing really new here, but I figured I'd include the code to make following through the program a little easier.
Only real difference is that I bind to textures[0].texID. Which points to the font texture. Only real difference is that .texID has been added.
GLvoid BuildFont(GLvoid)// Build Our Font Display List {
base=glGenLists(256);// Creating 256 Display Lists
glBindTexture(GL_TEXTURE_2D, textures[0].texID);// Select Our Font Texture for (int loop1=0; loop1<256; loop1++)// Loop Through All 256 Lists {
float cx=float(loop1%16)/16.0f;// X Position Of Current Character float cy=float(loop1/16)/16.0f;// Y Position Of Current Character glNewList(base+loop1,GL_COMPILE);// Start Building A List glBegin(GL_QUADS);// Use A Quad For Each Character
glTexCoord2f(cx,1.0f-cy-0.0625f);// Texture Coord (Bottom Left) glVertex2d(0,16); // Vertex Coord (Bottom Left)
glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);// Texture Coord (Bottom Right) glVertex2i(16,16);// Vertex Coord (Bottom Right)
glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f);// Texture Coord (Top Right) glVertex2i(16,0);// Vertex Coord (Top Right)
glTexCoord2f(cx,1.0f-cy-0.001f);// Texture Coord (Top Left) glVertex2i(0,0);// Vertex Coord (Top Left)
glEnd();// Done Building Our Quad (Character)
glTranslated(14,0,0);// Move To The Right Of The Character glEndList(); // Done Building The Display List
} // Loop Until All 256 Are Built }
KillFont is still t he same. We created 256 display lists, so we need to destroy 256 display lists when the program closes.
GLvoid KillFont(GLvoid)// Delete The Font From Memory {
glDeleteLists(base,256);// Delete All 256 Display Lists }
The glPrint() code has only changed a bit. The letters are all stretched on the y axis. Making the letters very tall. I've explained the rest of the code in other tutorials. The stretching is accomplished with the glScalef(x,y,z) command. We leave the ratio at 1.0 on the x axis, we double the size on the y axis (2.0), and we leave it at 1.0 on the z axis.
GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)// Where The Printing Happens {
char text[1024];// Holds Our String va_list ap; // Pointer To List Of Arguments if (fmt == NULL) // If There's No Text return;// Do Nothing
va_start(ap, fmt);// Parses The String For Variables
vsprintf(text, fmt, ap);// And Converts Symbols To Actual Numbers va_end(ap); // Results Are Stored In Text
if (set>1)// Did User Choose An Invalid Character Set?
{
set=1;// If So, Select Set 1 (Italic) }
glEnable(GL_TEXTURE_2D);// Enable Texture Mapping
glEnable(GL_TEXTURE_2D);// Enable Texture Mapping