Quick texture loading in Cocoa?
Is there any quick, dirty, one-shot way to load a bundled image (e.g. tga, tiff, jpeg) in Cocoa, and use it as an OpenGL texture?
Code:
#import <OpenGL/glu.h>
#import <QuickTime/QuickTime.h>
#define quit_if(b, message) if (b) { \
NSLog(@"%s", message); \
return 0; \
}
#define quit_oserr(err, message) quit_if(err != noErr, message)
GLuint LoadTexture(NSString* textureName)
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:textureName
ofType:@"png"
inDirectory:@"Images"];
if (filePath == nil)
{
NSLog(@"Can't find image %@.png", textureName);
return 0;
}
OSStatus err;
ComponentResult cr;
FSRef fsref;
Boolean isdir;
err = FSPathMakeRef((const UInt8*)[filePath fileSystemRepresentation], &fsref, &isdir);
quit_oserr(err, "Can't make FSRef from path\n");
quit_if(isdir, "Path is a directory\n");
FSSpec fsspec;
err = FSGetCatalogInfo(&fsref, kFSCatInfoNone, NULL, NULL, &fsspec, NULL);
quit_oserr(err, "Can't convert FSRef to FSSpec\n");
GraphicsImportComponent gi;
err = GetGraphicsImporterForFile(&fsspec, &gi);
quit_oserr(err, "Can't get graphics import component for file\n");
Rect natbounds;
cr = GraphicsImportGetNaturalBounds(gi, &natbounds);
quit_oserr(cr, "Can't get bounds for image\n");
quit_if(natbounds.left != 0, "Natural bounds' left is not zero\n");
quit_if(natbounds.top != 0, "Natural bounds' top is not zero\n");
size_t buffersize = 4 * natbounds.bottom * natbounds.right;
void* buf = malloc(buffersize);
GWorldPtr gw;
err = QTNewGWorldFromPtr(&gw, k32ARGBPixelFormat, &natbounds, NULL, NULL,
0, buf, 4 * natbounds.right);
quit_oserr(err, "Can't create GWorld\n");
cr = GraphicsImportSetGWorld(gi, gw, NULL);
quit_oserr(cr, "Can't set import component's GWorld\n");
natbounds.top = natbounds.bottom;
natbounds.bottom = 0;
cr = GraphicsImportSetBoundsRect(gi, &natbounds);
quit_oserr(cr, "Can't flip image\n");
cr = GraphicsImportDraw(gi);
quit_oserr(cr, "Can't draw image\n");
err = CloseComponent(gi);
quit_oserr(err, "Can't close graphics import component\n");
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
gluBuild2DMipmaps(GL_TEXTURE_2D,
GL_RGBA,
natbounds.right,
natbounds.top,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
buf);
free(buf);
return textureID;
}[edit]Hmm, there should probably be a DisposeGWorld() in there somewhere...[/edit]
Code:
-(int)LoadTexture:(NSString *)texName
{
NSdata *foo = [NSData datawithContentsOfFile:texName];
NSBitmapImageRep *theImage = [[NSBitmapImageRep alloc] initWithData:foo];
int bar;
glGenTextures(1, &bar);
glBindTexture(GL_TEXTURE_2D, bar);
gluBuild2DMipMaps(GL_TEXTURE_2D,GL_RGBA, [theImage size].width, [theImage size].height, GL_RGBA, GL_UNSIGNED_BYTE, [theImage bitmapData]);
return bar;
}I've left out a lot of error checking and things like determining if an alpha channel is present.
That looks pretty carbon-based to me. Sure it will work in Cocoa...
Here's how bryan blackburn does it in his NeHe tutorial 6:
Here's how bryan blackburn does it in his NeHe tutorial 6:
Code:
- (BOOL) loadBitmap:(NSString *)filename intoIndex:(int)texIndex
{
BOOL success = FALSE;
NSBitmapImageRep *theImage;
int bitsPPixel, bytesPRow;
unsigned char *theImageData;
int rowNum, destRowNum;
theImage = [ NSBitmapImageRep imageRepWithContentsOfFile:filename ];
if( theImage != nil )
{
bitsPPixel = [ theImage bitsPerPixel ];
bytesPRow = [ theImage bytesPerRow ];
if( bitsPPixel == 24 ) // No alpha channel
texFormat[ texIndex ] = GL_RGB;
else if( bitsPPixel == 32 ) // There is an alpha channel
texFormat[ texIndex ] = GL_RGBA;
texSize[ texIndex ].width = [ theImage pixelsWide ];
texSize[ texIndex ].height = [ theImage pixelsHigh ];
texBytes[ texIndex ] = calloc( bytesPRow * texSize[ texIndex ].height,
1 );
if( texBytes[ texIndex ] != NULL )
{
success = TRUE;
theImageData = [ theImage bitmapData ];
destRowNum = 0;
for( rowNum = texSize[ texIndex ].height - 1; rowNum >= 0;
rowNum--, destRowNum++ )
{
// Copy the entire row in one shot
memcpy( texBytes[ texIndex ] + ( destRowNum * bytesPRow ),
theImageData + ( rowNum * bytesPRow ),
bytesPRow );
}
}
}
return success;
}
Use of the bitmapRep is definitly the way to go; using the NSImage dimensions is heinous as those dimensions are often not pixel perfect.
and yes, OSC's code is very carbon... tastes like charcoal.
Feanor's code does not account for non-8bit/sample RGB images, you're going to need to catch those instances where your graphics are greyscale/index color/CLUT/5551 RGBA/etc. Actually, Feanor's code doesn't build the texture at all... I just noticed.
I guess I'll post the code from CocoaBlitz (which as far as I can tell works without issue)
and yes, OSC's code is very carbon... tastes like charcoal.
Feanor's code does not account for non-8bit/sample RGB images, you're going to need to catch those instances where your graphics are greyscale/index color/CLUT/5551 RGBA/etc. Actually, Feanor's code doesn't build the texture at all... I just noticed.
I guess I'll post the code from CocoaBlitz (which as far as I can tell works without issue)
---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
ok, this should work. But no promises because I hacked out all the texture mosaic code from CocoaBlitz. This code should build you a texture from an image in your bundle given the image name (you don't even need the extension). Haven't tested it, so no promises, but I'm pretty confident. This should even handle non RGBA32 images (greyscale, 16bit, etc).
[edit: you might need to retain targetBitmap]
[edit: removed stupid coding errors]
cheers.
Code:
long deviceMaxTextureSize=0;
glGetIntegerv (GL_MAX_TEXTURE_SIZE, &deviceMaxTextureSize);
NSImage *anImage = [NSImage imageNamed:@"Your_filename_here_w/out_extension"];
//you need to check that [anImage TIFFRepresentation] is not nil (I don't)
NSBitmapImageRep * sourceBitmap = [[NSBitmapImageRep alloc] initWithData:[anImage TIFFRepresentation]];
if ( [sourceBitmap pixelsWide] > deviceMaxTextureSize ||
[sourceBitmap pixelsHigh] > deviceMaxTextureSize ) ;//Fail. Do your own error checking I'm lazy.
//blarg I hate rewriting code. my code mosaics multiple textures, I gotta hack this just for you.
int power = 0;
while ( (1 << power) < [sourceBitmap pixelsWide] ) power++;
int width = 1 << power;
power = 0;
while ( (1 << power) < [sourceBitmap pixelsHigh] ) power++;
int height = 1 << power;
NSImage *texImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
NSBitmapImageRep *targetBitmap =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:YES
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:4*width
bitsPerPixel:32];
[texImage addRepresentation:targetBitmap];
[texImage lockFocusOnRepresentation:targetBitmap];
[[[NSColor clearColor] colorUsingColorSpaceName: NSCalibratedRGBColorSpace] set];
NSRectFill(NSMakeRect(0,0,width,height));
[sourceBitmap drawInRect:NSMakeRect(0,0,[sourceBitmap pixelsWide],[sourceBitmap pixelsHigh])];
//this use this next line instead if you want to stretch your image over the texture anamorphically.
//[sourceBitmap drawInRect:NSMakeRect(0,0,width,height)];
[texImage unlockFocus];
//At this point our bitmap should be ready for openGL.
unsigned char * pBuffer = [targetBitmap bitmapData];
glPixelStorei(GL_UNPACK_ALIGNMENT, 1 );
glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, 1); //clientstore textures
GLuint TextureName = 0;
glGenTextures( 1, &TextureName );
glBindTexture(GL_TEXTURE_2D, TextureName);
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, //target
0, //level
GL_RGBA, //internalformat
width, //width
height, //height
0, //border
GL_RGBA, //format
GL_UNSIGNED_BYTE, //type
pBuffer); //pixels
// You're done. Your texture name is TextureName[edit: you might need to retain targetBitmap]
[edit: removed stupid coding errors]
cheers.
---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote:Originally posted by kelvin
Actually, Feanor's code doesn't build the texture at all... I just noticed.:shock:
Sorry, wasn't thinking too hard (I was just cutting and pasting). The method I posted is called from this one:
Code:
- (BOOL) loadGLTextures
{
BOOL status = FALSE;
if( [ self loadBitmap:[ NSString stringWithFormat:@"%@/%s",
[ [ NSBundle mainBundle ] resourcePath ],
"NeHe.bmp" ] intoIndex:0 ] )
{
status = TRUE;
glGenTextures( 1, &texture[ 0 ] ); // Create the texture
// Typical texture generation using data from the bitmap
glBindTexture( GL_TEXTURE_2D, texture[ 0 ] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, texSize[ 0 ].width,
texSize[ 0 ].height, 0, texFormat[ 0 ],
GL_UNSIGNED_BYTE, texBytes[ 0 ] );
// Linear filtering
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
free( texBytes[ 0 ] );
}
I'm trying not to be picky, but Feanor, you're still missing the variable declarations for the bitmap buffer as well as a few other things. the code won't run.
---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
The loadGLTextures and loadBitmap:intoIndex: are definitely not general purpose enough, as they were developed with only NeHe's tutorials in mind. Not to mention, as kelvin hints, there's other stuff in the object which would need to be copied over with those methods...
I've said it before and I'll say it again -- you can't rely on NSBitmapImageRep to have the image in a form useful for OpenGL. In particular, rowbytes will sometimes be completely unusable. To be safe, you have to copy the bits by hand from the NSBitmapImageRep into your own buffer before submitting to OpenGL, which is a right pain.
The QuickTime code doesn't suffer from this problem. It (of course) works fine in Cocoa, and it loads any image QuickTime can; I'm not sure NSBitmapImageRep does that yet or whether it still has a small fixed list of types.
The QuickTime code doesn't suffer from this problem. It (of course) works fine in Cocoa, and it loads any image QuickTime can; I'm not sure NSBitmapImageRep does that yet or whether it still has a small fixed list of types.
of course with quicktime, you have to deal with reversing the ARGB format and passing it backwards to GL. Which has a minor speed hit if I recall.
besides, the targetBitmap I created was setup correctly to be passed to GL. That's why I composite the original image into the new bitmap. As long as the targetBitmap isn't recomposited it should be good for OpenGL (and without having to reverse the sample order).
besides, the targetBitmap I created was setup correctly to be passed to GL. That's why I composite the original image into the new bitmap. As long as the targetBitmap isn't recomposited it should be good for OpenGL (and without having to reverse the sample order).
---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
ARGB is the fastest texture format, as it's the native format for both ATI and NVidia cards. You actually take a small speed hit for using RGBA.
You also don't have to copy the image with the QuickTime method as you do with the Cocoa way, which will be much faster.
You also don't have to copy the image with the QuickTime method as you do with the Cocoa way, which will be much faster.
I've actually never had rowbytes cause a problem (then again, I'm rarely loading images with unusual dimensions). You can fix it with glPixelStore() anyway.
And I never did figure out how to load ARGB images directly into GL... What's the accepted method for that?
And I never did figure out how to load ARGB images directly into GL... What's the accepted method for that?
Quote:Originally posted by kelvin
I'm trying not to be picky, but Feanor, you're still missing the variable declarations for the bitmap buffer as well as a few other things. the code won't run.
It's not meant to be a working program. Some vars are in the class. Anyone who is unhappy with it can download the sample. NeHe: Lesson 6-> Cocoa. Anyway you've got your code. What is this, a test?
yeah... a test. right. and you fail! muahaha... standard deviation was 4, the average was 12, and passing was 70. D'oh! I failed too.
Next we have to pee in a cup. This one I can pass.
Next we have to pee in a cup. This one I can pass.
---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Possibly Related Threads...
| Thread: | Author | Replies: | Views: | Last Post | |
| iPhone texture loading issue | yotryu | 0 | 3,148 |
Mar 11, 2009 03:40 AM Last Post: yotryu |
|
| Texture Mapping: Loading a texture from a .bmp file? | ishrock | 5 | 5,083 |
Dec 13, 2008 09:27 AM Last Post: ThemsAllTook |
|
| OpenGL Texture Loading & Sprites | corporatenewt | 2 | 10,640 |
Jan 30, 2008 12:39 PM Last Post: ynda20 |
|
| Texture Loading in Cocoa... | dave05 | 3 | 7,694 |
Dec 11, 2007 03:12 AM Last Post: DoG |
|
| Loading and using textures with alpha in OpenGL with Cocoa | corporatenewt | 4 | 5,117 |
Dec 8, 2007 02:06 PM Last Post: Malarkey |
|

