OpenGL on the Macintosh provides several options for creating high-quality textures from image data. Mac OS X supports floating-point pixel values, multiple image file formats, and a variety of color spaces. You can import a floating-point image into a floating-point texture. Figure 9-9 shows an image used to texture a cube.
For Cocoa, you need to provide a bitmap representation. You can create an NSBitmapImageRep object from the contents of an NSView object. For either Cocoa or Carbon, you can use the Image I/O framework (see CGImageSource Reference). This framework has support for many different file formats, floating-point data, and a variety of color spaces. Furthermore, it is easy to use. You can import image data as a texture simply by supplying a CFURL object that specifies the location of the texture. There is no need for you to convert the image to an intermediate integer RGB format.
Creating a Texture from a Cocoa View
Creating a Texture from a Quartz Image Source
Getting Decompressed Raw Pixel Data from a Source Image
You can use the NSView class or a subclass of it for texturing in OpenGL. The process is to first store the image data from an NSView object in an NSBitmapImageRep object so that the image data is in a format that can be readily used as texture data by OpenGL. Then, after setting up the texture target, you supply the bitmap data to the OpenGL function glTexImage2D. Note that you must have a valid, current OpenGL context set up.
Note: You can't create an OpenGL texture from image data that's provided by a view created from the following classes: NSProgressIndicator, NSMovieView, and NSOpenGLView. This is because these views do not use the window backing store, which is what the method initWithFocusedViewRect: reads from.
Listing 9-3 shows a routine that uses this process to create a texture from the contents of an NSView object. A detailed explanation for each numbered line of code appears following the listing.
Listing 9-3 Building an OpenGL texture from an NSView object
-(void)myTextureFromView:(NSView*)theView |
textureName:(GLuint*)texName |
{ |
NSBitmapImageRep * bitmap = [NSBitmapImageRep alloc]; // 1 |
int samplesPerPixel = 0; |
[theView lockFocus]; // 2 |
[bitmap initWithFocusedViewRect:[theView bounds]]; // 3 |
[theView unlockFocus]; |
glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap pixelsWide]); // 4 |
glPixelStorei (GL_UNPACK_ALIGNMENT, 1); // 5 |
if (*texName == 0) // 6 |
glGenTextures (1, texName); |
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, *texName); // 7 |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, |
GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 8 |
samplesPerPixel = [bitmap samplesPerPixel]; // 9 |
if(![bitmap isPlanar] && |
(samplesPerPixel == 3 || samplesPerPixel == 4)) { // 10 |
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, |
0, |
samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8, |
[bitmap pixelsWide], |
[bitmap pixelsHigh], |
0, |
samplesPerPixel == 4 ? GL_RGBA : GL_RGB, |
GL_UNSIGNED_BYTE, |
[bitmap bitmapData]); |
} else { |
// Your code to report unsupported bitmap data |
} |
[bitmap release]; // 11 |
} |
Here's what the code does:
Allocates an NSBitmapImageRep object.
Locks the focus on the the NSView object so that subsequent commands take effect in coordinate system of the NSView object. You must invoke lockFocus before invoking methods that send commands to the window server, which is the case with the next line of code. Later, you must balance a lockFocus message with an unlockFocus message.
Initializes the NSBitmapImageRep object with bitmap data from the current view using the bounds returned by the NSView object passed to the myTextureFromView:textureName routine.
Sets the appropriate unpacking row length for the bitmap.
Sets the byte-aligned unpacking that's needed for bitmaps that are 3 bytes per pixel.
If a texture object is not passed in, generates a new texture object.
Binds the texture name to the texture target.
Sets filtering so that it does not use a mipmap, which would be redundant for the texture rectangle extension.
Gets the number of samples per pixel.
Checks to see if the bitmap is nonplanar and is either a 24-bit RGB bitmap or a 32-bit RGBA bitmap. If so, retrieves the pixel data using the bitmapData method, passing it along with other appropriate parameters to the OpenGL function for specifying a 2D texture image.
Releases the NSBitmapImageRep object when it is no longer needed.
Quartz images (CGImageRef data type) are defined in the Core Graphics framework (ApplicationServices/CoreGraphics.framework/CGImage.h) while the image source data type for reading image data and creating Quartz images from an image source is declared in the Image I/O framework (ApplicationServices/ImageIO.framework/CGImageSource.h). Quartz provides routines that read a wide variety of image data.
To use a Quartz image as a texture source, follow these steps:
Create a Quartz image source by supplying a CFURL object to the function CGImageSourceCreateWithURL.
Create a Quartz image by extracting an image from the image source, using the function CGImageSourceCreateImageAtIndex.
Extract the image dimensions using the function CGImageGetWidth and CGImageGetHeight. You'll need these to calculate the storage required for the texture.
Allocate storage for the texture.
Create a color space for the image data.
Create a Quartz bitmap graphics context for drawing. Make sure to set up the context for pre-multiplied alpha.
Draw the image to the bitmap context.
Release the bitmap context.
Set the pixel storage mode by calling the function glPixelStorei.
Create and bind the texture.
Set up the appropriate texture parameters.
Call glTexImage2D, supplying the image data.
Free the image data.
Listing 9-4 shows a code fragment that performs these steps. Note that you must have a valid, current OpenGL context.
Listing 9-4 Using a Quartz image as a texture source
CGImageSourceRef myImageSourceRef = CGImageSourceCreateWithURL(url, NULL); |
CGImageRef myImageRef = CGImageSourceCreateImageAtIndex (myImageSourceRef, 0, NULL); |
GLint myTextureName; |
size_t width = CGImageGetWidth(myImageRef); |
size_t height = CGImageGetHeight(myImageRef); |
CGRect rect = {{0, 0}, {width, height}}; |
void * myData = calloc(width * 4, height); |
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); |
CGContextRef myBitmapContext = CGBitmapContextCreate (myData, |
width, height, 8, |
width*4, space, |
kCGBitmapByteOrder32Host | |
kCGImageAlphaPremultipliedFirst); |
CGContextDrawImage(myBitmapContext, rect, myImageRef); |
CGContextRelease(myBitmapContext); |
glPixelStorei(GL_UNPACK_ROW_LENGTH, width); |
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
glGenTextures(1, &myTextureName); |
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, myTextureName); |
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, |
GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height, |
0, GL_BGRA_EXT, GL_UNSIGNED_INT_8_8_8_8_REV, myData); |
free(myData); |
For more information on using Quartz, see Quartz 2D Programming Guide, CGImage Reference, and CGImageSource Reference.
You can use the Image I/O framework together with a Quartz data provider to obtain decompressed raw pixel data from a source image, as shown in Listing 9-5. You can then use the pixel data for your OpenGL texture. The data has the same format as the source image, so you need to make sure that you use a source image that has the layout you need.
Alpha is not premultiplied for the pixel data obtained in Listing 9-5, but alpha is premultiplied for the pixel data you get when using the code described in “Creating a Texture from a Cocoa View” and “Creating a Texture from a Quartz Image Source.”
Listing 9-5 Getting pixel data from a source image
CGImageSourceRef myImageSourceRef = CGImageSourceCreateWithURL(url, NULL); |
CGImageRef myImageRef = CGImageSourceCreateImageAtIndex (myImageSourceRef, 0, NULL); |
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(myImageRef)); |
void *pixelData = CFDataGetBytePtr(data); |
Last updated: 2008-06-09