Loading TGA Files

Chapter 7 covers how to deal with bit-mapped images, used as textures that cover images. The first program, Bitmap, uses a small data array to create a 16 by 16 black and white image of a campfire, only one bit deep (each pixel is either on or off).

Next up was ImageLoad, which loads a TARGA or tga file. The TARGA format is fairly old, but it is simple to use, so the OpenGL SuperBible makes use of it.

I started off having a hard time getting my Cocoa programs to load the TGA files. I tried integrating the GLEE tools, but kept having compiling and linking problems, and eventually I gave up and found some code online to load PNG files. I had been doing this on my trip home to Michigan using my soft copy of the fifth edition. When I got home and looked in my hard-copy fourth edition, I found that it had the code to load the TGA files. It was almost complete—I simply had to find the definition for the TGAHEADER in the sample code files.

I started out just adding a loadTGA method to my view class, and when I got that working, I decided to design an Objective-C TGAImage class, capable of loading and saving TGA files. Wikipedia has a description of the file format, here, which proved useful in designing the class.

Rather than loading files using C FILE pointers, I used NSData’s dataWithContentsOfURL:options:error: method to load the file and writeToURL:atomically:, like a proper Cocoa class.

My class allows setting the image ID field, which is not available in the book’s code. The TGA standard does not require any information, but the Wikipedia article suggests that it’s common for it to hold the date and time the image was created, or a serial number. I plan on using it to store the name of the app that created the image, plus the date and time. Unfortunately, the book’s code can’t load a file with an ID field, so storing this commits myself to using my own code. Of course, “eating your own dogfood” is generally good advice when creating tools, so if my class has any bugs, I will find out about them.

Here’s the header file, TGAImage.h:

//
//  TGAImage.h
//  ImageLoad
//
//  Created by Norm Hecht on 10/2/10.
//  Copyright 2010 Scamp Dog Software. All rights reserved.
//

#import <Cocoa/Cocoa.h>

@interface TGAImage : NSObject {
    NSURL *fileURL;
    NSString *IDInformation;
    GLbyte *pixelData;
    GLint xOrigin;
    GLint yOrigin;
    GLint width;
    GLint height;
    GLint components;
    GLenum format;
}

-(id)initWithURL:(NSURL *)name;
-(id)initFromOpenGLContext;
-(BOOL)saveToURL:(NSURL *)name;

-(GLvoid *)bitmapData;
-(NSString *)IDInformation;
-(void)setIDInformation:(NSString *)theInfo;
-(GLint)xOrigin;
-(GLint)yOrigin;
-(GLint)width;
-(GLint)height;
-(GLint)components;
-(GLenum)format;
-(GLbyte *)pixelData;

@end

Here’s the implementation file, TGAImage.m:

//
//  TGAImage.m
//  ImageLoad
//
//  Created by Norm Hecht on 10/2/10.
//  Copyright 2010 Scamp Dog Software. All rights reserved.
//

#import "TGAImage.h"
#import <OpenGL/OpenGL.h>


@implementation TGAImage

-(id)initWithURL:(NSURL *)name {
    if (!self) {
        return self;
    }
    fileURL = name;
    [fileURL retain];
    unsigned char header[18];
    NSData *tgaData = [NSData dataWithContentsOfURL:fileURL];
    NSRange headerRange = NSMakeRange(0, 18);
    if (!tgaData) {
        NSLog(@"TGA file %@ not found", fileURL);
        return NULL;
    }
    [tgaData getBytes:header range:headerRange];
    int iCount;
    for (iCount = 0; iCount < 18; iCount++) {
        NSLog(@"Header byte %2i = %i", iCount, header[iCount]);
    }
    if (header[1] != 0) {
        NSLog(@"TGA file uses a color map, which this method can't handle");
        if (fileURL) {
            [fileURL release];
        }
        return NULL;
    }
    char imageType = header[2];
    if (imageType >= 8 ) {
        NSLog(@"TGA file uses run-length encoding, which this method can't handle");
        if (fileURL) {
            [fileURL release];
        }
        return NULL;
    }
    switch (imageType) {
        case 0:
            NSLog(@"TGA file has no image data, what's up with that?");
            if (fileURL) {
                [fileURL release];
            }
            return NULL;
            break;
        case 1:
            NSLog(@"TGA file has color-mapped image data, aborting");
            if (fileURL) {
                [fileURL release];
            }
            return NULL;
            break;
        case 3:
            NSLog(@"TGA file has black and white image data");
            break;
        default:
            break;
    }
    int IDLength = header[0];
    if (IDLength > 0) { // Then read the ID information
        NSLog(@"TGA file has an ID area");
        NSRange IDRange = NSMakeRange(18, IDLength);
        char *IDInfoCString = (char *)malloc(IDLength+1);
        if (IDInfoCString == NULL) {
            NSLog(@"Couldn't allocate memory for TGA file's ID information");
            if (fileURL) {
                [fileURL release];
            }
            return NULL;
        }
        [tgaData getBytes:IDInfoCString range:IDRange];
        IDInfoCString[IDLength] = 0; // make last byte zero so it's a string
        IDInformation = [NSString stringWithCString:IDInfoCString 
                                           encoding:NSUTF8StringEncoding];
        free(IDInfoCString);
    } else {
        NSLog(@"TGA file has no ID area");
        IDInformation = NULL;
    }
    xOrigin = header[9]*256 + header[8];
    yOrigin = header[11]*256 + header[10];
    width = header[13]*256 + header[12];
    height = header[15]*256 + header[14];
    NSLog(@"TGA image's origin, width and height are (%i, %i), %i and %i", 
          xOrigin, yOrigin, width, height);
    int pixelDepth = header[16];
    switch (pixelDepth) {
        case 24:
            format = GL_BGR_EXT;
            components = GL_RGB8;
            break;
        case 32:
            format = GL_BGRA_EXT;
            components = GL_RGBA8;
            break;
        case 8:
            format = GL_LUMINANCE;
            components = GL_LUMINANCE8;
            break;
        default:
            NSLog(@"TGA pixel depth invalid for OpenGL");
            return NULL;
            break;
    }
    int imageDescriptor = header[17];
    NSLog(@"TGA image's pixel depth is %i, with descriptor of %i",
          pixelDepth, imageDescriptor);
    unsigned long lImageSize = width*height*pixelDepth/8;
    NSLog(@"Image size in bytes should be %i", lImageSize);
    pixelData = (GLbyte *)malloc(lImageSize*sizeof(GLbyte));
    if (pixelData == NULL) {
        NSLog(@"Failed to allocate memory for image data");
        if (fileURL) {
            [fileURL release];
        }
        return NULL;
    }
    NSRange pixelRange = NSMakeRange(18+IDLength, lImageSize);
    [tgaData getBytes:pixelData range:pixelRange];
    NSLog(@"Successfully read TGA file!");
    return self;
}

-(id)initFromOpenGLContext {
    GLint iViewPort[4]; // viewport in pixels
    glGetIntegerv(GL_VIEWPORT, iViewPort);
    unsigned long lImageSize = iViewPort[2]*iViewPort[3]*3;
    pixelData = (GLbyte *)malloc(lImageSize);
    if (!pixelData) { // couldn't allocate memory, so quit
        return NULL;
    }
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_SKIP_ROWS, 0);
    glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
    
    // Get the current read buffer setting and save it.  Switch to
    // the front buffer and do the read operation.  Finally, restore 
    // the read buffer state.
    GLint lastBuffer; // storage for the current read buffer setting
    glGetIntegerv(GL_READ_BUFFER, &lastBuffer);
    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, iViewPort[2], iViewPort[3], GL_BGR,
                 GL_UNSIGNED_BYTE, pixelData);
    glReadBuffer(lastBuffer);
    
    xOrigin = 0;
    yOrigin = 0;
    width = iViewPort[2];
    height = iViewPort[3];
    format = GL_BGR_EXT;
    components = GL_RGB8;
    return self;
}

-(BOOL)saveToURL:(NSURL *)name {
    NSMutableData *tgaData = [[[NSMutableData alloc] init] autorelease];
    unsigned char header[18];
    header[1] = 0; // No color map
    header[2] = 2; // Uncompressed, true-color image
    header[3] = header[4] = 0; // offset into color map table, not used
    header[5] = header[6] = 0; // number of color map entries, not used
    header[7] = 0; // number of bits in (non-existant) color map entries
    header[8]  = xOrigin % 256;
    header[9]  = xOrigin / 256;
    header[10] = yOrigin % 256;
    header[11] = yOrigin / 256;
    header[12] = width % 256;
    header[13] = width / 256;
    header[14] = height % 256;
    header[15] = height / 256;
    header[16] = 24; // bits per pixel
    header[17] = 0; // image descriptor:
    // bits 0-3 alpha depth, bits 5-4 direction
    
    if (IDInformation) {
        int IDLength = [IDInformation length];
        if (IDLength < 256) {
            header[0] = IDLength;
            [tgaData appendBytes:header length:18];
            NSData *IDData = [IDInformation 
                              dataUsingEncoding:NSUTF8StringEncoding];
            [tgaData appendData:IDData];
        } else {
            NSLog(@"Truncating IDInformation");
            header[0] = 255;
            [tgaData appendBytes:header length:18];
            NSRange r = NSMakeRange(0, 255);
            NSData *IDData = [[IDInformation substringWithRange:r] 
                              dataUsingEncoding:NSUTF8StringEncoding];
            [tgaData appendData:IDData];
         }
    } else {
        header[0] = 0;
        [tgaData appendBytes:header length:18];
    }
    NSUInteger imageSize = width*height*3;
    [tgaData appendBytes:pixelData length:imageSize];
    return [tgaData writeToURL:name atomically:NO];
}

- (void)dealloc {
    [super dealloc];
    if (pixelData) {
        free(pixelData);
    }
    if (fileURL) {
        [fileURL release];
    }
    if (IDInformation) {
        [IDInformation release];
    }
}

-(NSString *)IDInformation {
    return IDInformation;
}

-(void)setIDInformation:(NSString *)theInfo {
    if (IDInformation) {
        [IDInformation release];
    }
    IDInformation = theInfo;
    [IDInformation retain];
}

-(GLvoid *)bitmapData {
    return pixelData;
}

-(GLint)xOrigin {
    return xOrigin;
}

-(GLint)yOrigin {
    return yOrigin;
}

-(GLint)width {
    return width;
}

-(GLint)height {
    return height;
}

-(GLint)components {
    return components;
}

-(GLenum)format {
    return format;
}

-(GLbyte *)pixelData {
    return pixelData;
}
@end
Advertisements
This entry was posted in OpenGL and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s