Tuesday, April 7, 2009

Creating UIImages from TGA Data

UIImage doesn't support as wide a range of file types as NSImage does in Cocoa . It's understandable, given the different natures of the devices they run on, however there are times when you will want to load data in a format that's not directly supported by UIImage. This happened recently while experimenting with creating a Milkshape 3D model loader for use in iPhone game development. Milkshape uses Targa and PCX files for the texture, and neither of these formats is supported by UIImage.

I haven't tackled PCX files, but I wrote a category on UIImage to let you instantiate a UIImage object from Targa data stored in a TGA file. This version doesn't support RLE encoded files, but generally for game models, RLE isn't encoded. If you need to save size in your image executable, zipping the uncompressed TGA data will save about the same amount of room. If you're looking at doing a game, I probably wouldn't recommend compressing the images at all unless you're very close to that ten meg limit that prevents people from downloading your game over 3G & Edge, since you'll have to expend processing cycles to decompress at launch or load time.

You can find the category and an Xcode project that shows how it can be used in the iPhone Bits repository over at Google Code.

This is a first draft and there is likely room for improvement. There are some known inefficiencies in this class. For example, the iPhone's native byte ordering is BGRA, and so is Targa's, This code, however, converts the Targa data to RGB, and likely under the hood, UIImage is converting back to BGRA. I'm pretty sure Core Graphics provides a way to avoid this conversion, but don't have time to research it right now. Also, if your only use of the Targa image is going to be in OpenGL, you probably want to skip this category altogether and just load the bitmap data into OpenGL directly. This category combined with the Texture2D from Apple's sample code certainly gives an easy way to setup a scaffold environment for early development.

UIImage-Targa.h
#import <UIKit/UIKit.h>

@interface UIImage(Targa)
+(id)imageFromTGAFile:(NSString *)filename;
+(id)imageFromTGAData:(NSData *)data;
+(id)imageWithRawRGBAData:(NSData *)data width:(int)width height:(int)height;
@end



UIImage-Targa.m
#import "UIImage-Targa.h"

void releaseImageData(void *info, const void *data, size_t size)
{
free((void *)data);
}


@implementation UIImage(Targa)
+(id)imageFromTGAFile:(NSString *)filename
{
NSData *data = [[NSData alloc] initWithContentsOfFile:filename];
id ret = [self imageFromTGAData:data];
[data release];
return ret;
}

+(id)imageFromTGAData:(NSData *)data
{

short imageHeight;
short imageWidth;
unsigned char bitDepth;
unsigned char imageType;
int colorMode;
long imageSize;
unsigned char *imageData;

unsigned char *bytes = (unsigned char *)[data bytes]+2; // skip first two bytes

memcpy(&imageType, bytes, sizeof(unsigned char));

if (imageType != 2 && imageType != 3)
{
NSException *exception = [NSException exceptionWithName:@"Unsupported File Format"
reason:@"Compressed TGA files are not supported yet"
userInfo:nil
]
;
[exception raise];
}

bytes += ( (sizeof(unsigned char) * 2) + (sizeof(short) * 4));
memcpy(&imageWidth, bytes, sizeof(short));
bytes += 2;
memcpy(&imageHeight, bytes, sizeof(short));
bytes += 2;
memcpy(&bitDepth, bytes, sizeof(char));
bytes+=2;

colorMode = bitDepth / 8;
imageSize = imageWidth * imageHeight * colorMode;

long newDataLength = imageWidth * imageHeight * 4;
imageData = (unsigned char *)malloc(newDataLength);
memcpy(imageData, bytes, imageSize * sizeof(unsigned char));

// Targa is BGR, swap to RGB
long byteCounter = 0;
for (long i = 0; i < imageSize; i += colorMode)
{
long start = (byteCounter++ * 4);
imageData[start] = bytes[i+2];
imageData[start+1] = bytes[i+1];
imageData[start+2] = bytes[i];
imageData[start+3] = (colorMode == 4) ? bytes[i+3] : 1.0;
}


// Targa uses more standard Y axis, so need to swap top & bottom bytes
// We can swap 32 bits at a time rather than going byte by byte
uint32_t *imageDataAsInts = (uint32_t *)imageData;
for (int y = 0; y < imageHeight / 2; y++)
{
for (int x = 0; x < imageWidth; x++)
{
uint32_t top = imageDataAsInts[y * imageWidth + x];
uint32_t bottom = imageDataAsInts[(imageHeight - 1 - y) * imageWidth + x];
imageDataAsInts[(imageHeight - 1 - y) * imageWidth + x] = top;
imageDataAsInts[y * imageWidth + x] = bottom;
}

}


NSData *swappedData = [[NSData alloc] initWithBytes:imageData length:newDataLength];
return [self imageWithRawRGBAData:swappedData width:imageWidth height:imageHeight];
}

+(id)imageWithRawRGBAData:(NSData *)data width:(int)width height:(int)height
{
const void * buffer = [data bytes];
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, [data length], releaseImageData);

const int bitsPerComponent = 8;
const int bitsPerPixel = 4 * bitsPerComponent;
const int bytesPerRow = 4 * width;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGDataProviderRelease(provider);

UIImage *myImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

return myImage;
}

@end





11 comments:

Michael said...

You are missing a library if this example is expected to work. I'm compiling against the standard set of libraries and frameworks an OpenGL application would provide.






Building target “MyProject” of project “MyProject” with configuration “Debug” — (5 errors)
cd /Users/todd/Desktop/MyProject
setenv MACOSX_DEPLOYMENT_TARGET 10.5
setenv PATH "/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
/Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/gcc-4.2 -arch i386 -isysroot /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.0.sdk -L/Users/todd/Desktop/MyProject/build/Debug-iphonesimulator -F/Users/todd/Desktop/MyProject/build/Debug-iphonesimulator -filelist /Users/todd/Desktop/MyProject/build/MyProject.build/Debug-iphonesimulator/MyProject.build/Objects-normal/i386/MyProject.LinkFileList -mmacosx-version-min=10.5 -framework UIKit -framework OpenGLES -framework QuartzCore -framework Foundation -o /Users/todd/Desktop/MyProject/build/Debug-iphonesimulator/MyProject.app/MyProject
Undefined symbols:
"_CGImageRelease", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGImageCreate", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGColorSpaceCreateDeviceRGB", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGDataProviderRelease", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGDataProviderCreateWithData", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
"_CGImageRelease", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGImageCreate", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGColorSpaceCreateDeviceRGB", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGDataProviderRelease", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
"_CGDataProviderCreateWithData", referenced from:
+[UIImage(Targa) imageWithRawRGBAData:width:height:] in UIImage_Targa.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
Build failed (5 errors)

Jeff LaMarche said...

You must be compiling under an older version of the SDK that doesn't automatically link the CoreGraphics.framework into your project.

Daniel Yankowsky said...

You mention that you think that UIImage converts the data back to BGRA. I don't know for sure, but I think that CGImage leaves the data in the byte order you specify. However, it's not obvious how to specify a byte order.

The trick is the CGBitmapInfo passed to CGImageCreate. The combination of byte order and alpha info determines the specific byte ordering. kCGBitmapByteOrderDefault is RGB (I think). kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast is RGBA.

kCGBitmapByteOrder32Little is BGR. kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst is BGRA.

Armed with that knowledge, you can probably skip the byte flipping, create the bitmap with the appropriate flags, jam the data into the bitmap, and draw the image at full speed.

This post on the Apple mailing list was useful to me.

ovidiu said...
This comment has been removed by the author.
ovidiu said...

What type of .tga files you've used?

I've tried your code with 16 bit, 24 bit and 32 bit uncompressed tgas exported from Photoshop cs2, and none worked (it would display a gray image, nothing in it). I've tried your logo.tga from google code but it crashes telling it will not support compressed tgas (your error in console with some numbers).

gg group said...

Hi Good day to all.....

This is Gopal. I'm new to iphone application development. In one application i need to access a image when i touch on the iphone, that perticular x,y co-ordination values and background image pixel(co-ordinate)colur of an image with respect the co-ordinates.

those co-ordinate need to place in a buffer.

from that buffer create i need to create provider.

using these i want to generate CGImage.
from CGImage i need to convert it in to UIImage format.

so i can utilize this is in my application. please any body can help me out from this......????

Thanq.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

Estelle said...

Above you say :
"you probably want to skip this category altogether and just load the bitmap data into OpenGL directly."

Do you know how to do that? Has this been covered before?

Thanks.

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

Owen said...

Hi!

Great work on the targa loader, I am using it in an upcoming game to allow me to have large animations stored as targas (which when zipped are tiny compared to pbgs, yay uncompressed image formats).

Anyway,

I ran into quite a large memory leak, which I have fixed:

In the method imageFromTGAData, you are calling imageData = (unsigned char *)malloc(newDataLength);

With no corresponding call to free.

I added free(imageData) just above the return, and it solved the leak. (Before the fix, my app's memory footprint was growing 500kb every 12th of a second due to on the fly tga loading).

Also:

For TGA alpha, you can change:
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
To
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault|kCGImageAlphaLast;


Hope this helps.

Regards,

Owen

Hire iphone developer said...

Hello,
Before you make any hirings, you should be well aware of all the terms and conditions laid down by the iPhone development company, along with the payment details, so that you do not have to face any problems at the later stage of development.

Hire iphone Developer