Quick texture loading in Cocoa?

Oldtimer
Posts: 834
Joined: 2002.09
Post: #1
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?
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #2
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]
Quote this message in a reply
Member
Posts: 177
Joined: 2002.08
Post: #3
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.
Quote this message in a reply
Feanor
Unregistered
 
Post: #4
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:

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;
}
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #5
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.Shock

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
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #6
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).

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 this message in a reply
Feanor
Unregistered
 
Post: #7
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 ] );
   }
Again, credit to Bryan Blackburn, not to me.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #8
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
Quote this message in a reply
blb
Unregistered
 
Post: #9
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...
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #10
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.
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #11
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).

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #12
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.
Quote this message in a reply
Member
Posts: 177
Joined: 2002.08
Post: #13
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?
Quote this message in a reply
Feanor
Unregistered
 
Post: #14
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?
Quote this message in a reply
Member
Posts: 469
Joined: 2002.10
Post: #15
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.

---Kelvin--
15.4" MacBook Pro revA
1.83GHz/2GB/250GB
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  iPhone texture loading issue yotryu 0 3,370 Mar 11, 2009 03:40 AM
Last Post: yotryu
  Texture Mapping: Loading a texture from a .bmp file? ishrock 5 5,973 Dec 13, 2008 09:27 AM
Last Post: ThemsAllTook
  OpenGL Texture Loading &amp; Sprites corporatenewt 2 11,017 Jan 30, 2008 12:39 PM
Last Post: ynda20
  Texture Loading in Cocoa... dave05 3 8,024 Dec 11, 2007 03:12 AM
Last Post: DoG
  Loading and using textures with alpha in OpenGL with Cocoa corporatenewt 4 5,945 Dec 8, 2007 02:06 PM
Last Post: Malarkey