Loading compressed TGA into OpenGL texture

Started by
9 comments, last by mixmaster 12 years, 2 months ago
I'm in need of some assistance on a pretty specific problem. I'm trying to read a compressed 24-bit TGA file and pass it into OpenGL as a texture. I've been almost successful, except that the resulting rendered texture is somewhat... skewed, and off-color. I'm skipping pixels when reading the actual image data or something, but I swear I have the right idea since everything looks fine when I step through the code while loading a small image file. So maybe my OpenGL/texture settings are wrong? Ugh, I just can't figure out where the problem is.

Here's my TGA reading code. Note that the code is actually supposed to be designed to take either a 24-bit truecolor TGA or an 8-bit indexed TGA, but right now I'm just testing the reading of a 24-bit truecolor TGA.

int loadTGAToTexture(string filePath, HSTexture * hsTex)
{
//the data that OpenGL ultimately needs
GLushort imageWidth = 0;
GLushort imageHeight = 0;
GLenum format = GL_RGB;
GLubyte * imageData;
GLuint textureID;
GLuint bufferID;

//variables to help us get the above data
FILE * file;
GLubyte imageIDLength;
GLubyte colorMapType;
GLubyte imageType;
GLushort colorMapLength;
GLubyte colorMapEntrySize;
GLubyte pixelDepth;
GLubyte bytesPerPixel;
GLubyte imageDescriptor;
bool topAlign = true;
bool rightAlign = true;

//open the file
if(GLuint error = fopen_s(&file, filePath.data(), "rb") != 0)
{
return error; //couldn't open the file
}

//gather all the general info about the TGA file
fseek(file, 0L, SEEK_SET); //move to the beginning of the file
fread(&imageIDLength, 1, 1, file); //get the length of the image id field
fread(&colorMapType, 1, 1, file); //see if the file has a color map in it or not
fread(&imageType, 1, 1, file); //find out what kind of image file this is
if(imageType == 9)
{
format = GL_COLOR_INDEX; //its an indexed RLE format
}
else if(imageType != 10)
{
return -1; //this needs to be RLE, either indexed or truecolor
}
fseek(file, 2L, SEEK_CUR); //skip the first two bytes of the color map specification
fread(&colorMapLength, 2, 1, file); //get the length of the color map
fread(&colorMapEntrySize, 1, 1, file); //get the size of each color map entry
if(colorMapEntrySize == 15)
{
colorMapEntrySize = 16; //make sure it's a multiple of 8
}
fseek(file, 4L, SEEK_CUR); //skip the first four bytes of the image specification
fread((void *)&imageWidth, 2, 1, file); //get the image width
fread((void *)&imageHeight, 2, 1, file); //get the image height
fread(&pixelDepth, 1, 1, file); //get the size of each pixel in bits
bytesPerPixel = pixelDepth/8; //get the size of each pixel in bytes
if((format == GL_COLOR_INDEX && pixelDepth != 8) || (format == GL_RGB && pixelDepth != 24))
{
return -1; //only allowed configurations are: indexed with 8-bit, or truecolor with 24 bit
}
fread(&imageDescriptor, 1, 1, file); //get the image descriptor, and pull some data from it
if((imageDescriptor & ATTRIBUTE_BITS) > 0)
{
return -1; //no attributes should be defined
}
if((imageDescriptor & TOP_ALIGN) == 0)
{
topAlign = false; //the pixels are bottom-aligned
}
if((imageDescriptor & RIGHT_ALIGN) == 0)
{
rightAlign = false; //the pixels are left-aligned
}
fseek(file, (long)imageIDLength, SEEK_CUR); //skip the imageID
if(colorMapType != 0)
{
fseek(file, (long)(colorMapLength * colorMapEntrySize), SEEK_CUR); //skip the color map data
}

//okay, time for the fun part: picking through the image data.
//it's ALWAYS going to be in RLE format so we can't just grab the raw data.
GLuint maxPixels = imageWidth * imageHeight; //we need this so we know when to stop
GLuint curPixels = 0; //keeps track of how far we've gone through the actual pixels
imageData = (GLubyte*)malloc(maxPixels * bytesPerPixel); //set up our image data buffer
GLubyte repCount; //this'll hold each repetition count field
GLubyte * runColor = (GLubyte*)malloc(bytesPerPixel); //create a buffer to hold the color data of a run
while(curPixels < maxPixels)
{
if(fread(&repCount, 1, 1, file) != 1) { return -1;} //get the repetition count field
if((repCount & RL_PACKET) == 0)
{
//this is a raw packet.
//Just shove the pixel data into the image data buffer
repCount = repCount & REP_COUNT_BITS; //get the actual number of following pixels

//put the pixels into the image data buffer
for(int i = 0; i <= repCount; i++)
{
//fread(imageData + (curPixels * bytesPerPixel) + (i * bytesPerPixel), bytesPerPixel, 1, file);
for(int j = bytesPerPixel - 1; j >= 0; j--)
{
//gotta read each individual pixel in backwards since TGA stores color backwards
if(fread(imageData + (curPixels * bytesPerPixel) + (i * bytesPerPixel) + j, 1, 1, file) != 1) { return -1; }
}
}
}
else
{
//this is a run-length packet.
//Put the specificed number of pixels of the specified color into the image data buffer
repCount = repCount & REP_COUNT_BITS; //get the actual number of following pixels

//get the color if this run
//fread(runColor, bytesPerPixel, 1, file);
for(int i = bytesPerPixel - 1; i >= 0; i--)
{
//since TGA stores colors as BGR, we need to read it backwards to get RGB
if(fread(runColor + i, 1, 1, file) != 1) { return -1; }
}

for(int i = 0; i <= repCount; i++)
{
//put a pixel of the specified packet color into the image data buffer
memcpy(imageData + (curPixels * bytesPerPixel) + (i * bytesPerPixel), runColor, bytesPerPixel);
}
}
curPixels += repCount + 1; //update how far we've moved through the actual pixels
}

fclose(file);

delete(runColor);

//create and bind the texture
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);

//set some options
//glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
//glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

//put everything in the texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageWidth, imageHeight, 0, format, GL_UNSIGNED_BYTE, imageData);

delete(imageData);
glBindTexture(GL_TEXTURE_2D, 0);

//make an array of coordinates based on the texture dimensions
GLshort * dim = new GLshort[12];
dim[0] = 0; dim[1] = 0; dim[2] = 0;
dim[3] = imageWidth; dim[4] = 0; dim[5] = 0;
dim[6] = imageWidth; dim[7] = imageHeight; dim[8] = 0;
dim[9] = 0; dim[10] = imageHeight; dim[11] = 0;

//make a buffer object
glGenBuffers(1, &bufferID);
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glBufferData(GL_ARRAY_BUFFER, 12*sizeof(GLushort), dim, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

delete[] dim;

//now, put all the results in the texture struct
hsTex->defined = true;
hsTex->textureID = textureID;
hsTex->bufferID = bufferID;
hsTex->rightAlign = rightAlign;
hsTex->topAlign = topAlign;

return 0;
}


Attached are four files. test.bmp and john.bmp is what the files look like before being turned into a 24 bit compressed truecolor TGA (I know that part of the process works fine because I can open them back up in a TGA viewer and they look correct) and then run through my code. testResult.bmp and johnResult.bmp is what I get back out as an on-screen texture. I'm rendering in orthographic mode and the surface I'm drawing on has the same dimensions as the texture that belongs to it.

Any ideas? Any help would be appreciated.

[attachment=7142:test.bmp]
[attachment=7143:testResult.bmp]
[attachment=7140:john.bmp]
[attachment=7141:johnResult.bmp]
Advertisement
you may want to add some UVs to your Quad, I'm not even sure how this is working at all. Is this all the code?

Your TGA loading is probably fine, look into building a correct Quad to apply the texture to.

Cheers

Lee

Code is Life
C/C++, ObjC Programmer, 3D Artist, Media Production

Website : www.leestripp.com
Skype : lee.stripp@bigpond.com
Gmail : leestripp@gmail.com

Crap I should have posted my rendering code. I'm at work and can't do it at the moment.

What's a UV?
Depending on the values of imageWidth and imageHeight you may need to change your unpack alignment: http://www.opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

"However, the program crashes on upload, or there seems to be diagonal lines going through the resulting image."

Okay, that might actually be the problem, since the image looks "torn" across a diagonal. I'll look into it. Thanks.

Assuming the solutions suggested in this section fixes it, then I have to ask: which of the two solutions is more efficient performance-wise? Should I change the unpack alignment, or should I just slap an extra byte on my pixels and make the input data RGBA?
Most efficient would probably be to add the extra byte but use BGRA, not RGBA. If you use RGBA your texture upload will need to go through an additional software stage to convert it to BGRA (which is what your GPU is more than likely using internally, despite an internalFormat of GL_RGBA8 in this case - legacy OpenGL is weird like that). Unpack alignment 1 would likely incur additional penalties as it may only transfer in chunks of 1 byte at a time (rather than 4). You'd really need to profile to get the full picture, but my gut says extra byte + BGRA.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Alright. That's what I kinda figured myself, so I'll go with that.

Everything seems to be working now. Thanks!
Oh woops :-)... Hey is this really correct : BGRA is fastest? If so where is your reference? In a openGL book.

Cheers

Lee

Code is Life
C/C++, ObjC Programmer, 3D Artist, Media Production

Website : www.leestripp.com
Skype : lee.stripp@bigpond.com
Gmail : leestripp@gmail.com

http://www.opengl.org/wiki/Common_Mistakes#Paletted_textures
http://www.opengl.org/wiki/Common_Mistakes#Image_precision
http://www.opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads

But you can write your own benchmark code if you wish.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Well at any rate, if I have some kind of performance issue during image loading in the future, I'll come back and look at this subject and try different things. Until then I'll just go ahead and assume using BGRA is my best bet, since all info sources (and some common sense) seems to point towards it.

This topic is closed to new replies.

Advertisement