posted on: 2014-02-13 15:38:10
My goal was to create a TGA reader for c++ to use in OpenGL applications.

>I was trying to find a good image format to easily load images in c++. I use GIMP for image editing and it offers a variety of image formats to use. TGA is a pretty common file format so I set out to read TGA images.

The first thing I noticed was that java does not have TGA as one of the default image formats. I started trying to learn the file structure with java because it is a bit easier for me.

I saved a TGA file from gimp, then I read that file with java and noticed the file ends with a string.

TRUEVISION-XFILE.

I used that as an offset for the footer, I knew the size of the image, and I assumed format of the pixels is 8 bit per channel with 4 channels (red, blue, green and alpha). With these assumptions was able to find the image.

Of course after getting the image back I decided to check wikipedia for the file format. This way I was able to find out where the important values of the file are stored. The type of image is stored from byte 3 and the size of the image, contained in bytes 13 and 14 for the width, bytes 15 and 16 for the height.

Extracting these values in java looked like:

//image dimensions
int width = (stack.getUnsigned(13)<<8) + stack.getUnsigned(12);
int height = (stack.getUnsigned(15)<<8) + stack.getUnsigned(14);

At the time of writing this java doesn't have an unsigned byte so I made a utility function to get an int version of the bytes that goes from 0->255.

int getUnsigned(int dex){
    int row = dex/chunk;
    int column = dex%chunk;

    int b = stack.get(row)[column];
    return b<0?256 + b:b;
}

The row and column are used because I read the file in 2048 byte chunks and stored all of the byte[]s into an ArrayList&lt;byte[]&gt;.

Since I know the footer and the size of the image, I can extract all of the image data. Each pixel is stored as 4 bytes and the reading process ended up looking like.

for(int i = 0; i<width; i++){
  for(int j = 0; j<height; j++){
    int dex = (j*width + i)*byte_per_pixel + offset;
    int b = stack.getUnsigned(dex);
    int g = stack.getUnsigned(dex+1);
    int r = stack.getUnsigned(dex+2);
    int a = stack.getUnsigned(dex+3);
    bufferedImage.setRGB(i, y, (a<<24) + (r<<16) + (g<<8) + b);
  }
}

I know the pixel format and the file description I went on to do the exact same thing in c++. Well not quite exact. I made a structure to store my data in c++.

One of the nice things about c++ is that I just read data into a character array and use casting for the type. For example.

std::ifstream infile( file_name.c_str(), std::ifstream::binary );
char* header = new char[18];
if(infile){
    infile.read(header, 18);
}

That way I read the header and then to extract values from it I just cast the values to their appropriate type.

int width = ((unsigned char)header[13]<<8) + (unsigned char)header[12];
int height = ((unsigned char)header[15]<<8) + (unsigned char)header[14];

To store the texture I did the same thing. Read all of the bytes into a char array then just cast the texture to a GLubyte*.

GLubyte* texture = (GLubyte*)back;

To use the texture I buffered it as described in my previous post on a minimum texture example. The only difference was that I needed to specify the byte format.

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->width, img->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, img->texture);

Note that img is a struct with the elements int width, height; and GLubyte* texture.

All of this code is available in a github repository. github.com/odinsbane/tga_reader

Here is a picture of the almost full moon.

Comments

Name: