Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Working with Images

Once you have an image, there are many ways you can use it. The simplest thing you can do is draw it into a view as part of your program’s user interface. You can also process the image further by modifying it in any number of ways. The following sections show you how to perform several common tasks associated with images.

In this section:

Drawing Images into a View
Creating an OpenGL Texture
Applying Core Image Filters
Converting a Bitmap to a Different Format
Associating a ColorSync Profile With an Image
Converting Between Color Spaces
Premultiplying Alpha Values for Bitmaps


Drawing Images into a View

The NSImage class defines several methods for drawing an image into the current context. The two most commonly used methods are:

These methods offer a simple interface for rendering your images, but more importantly, they ensure that only your image content is drawn. Other methods, such as the compositeToPoint:operation: method and its variants, are fast at drawing images but they do not check the image’s boundaries before drawing. If the drawing rectangle strays outside of the image bounds, it is possible to draw content not belonging to your image. If the image resides on a shared offscreen window, which many do, it is even possible to draw portions of other images. For more information about the differences between these methods, see “Drawing Versus Compositing.”

With one exception, all of the drawing and compositing methods choose the image representation that is best suited for the target canvas—see “How an Image Representation Is Chosen.” The one exception is the drawRepresentation:inRect: method, which uses the image representation object you specify. For more information about the use of these methods, see the NSImage reference.

Images support the same set of compositing options as other Cocoa content, with the same results. For a complete list of compositing options and an illustration of their effects, see “Setting Compositing Options.”

Creating an OpenGL Texture

In OpenGL, a texture is one way to paint the surface of an object. For complex or photorealistic surfaces, it may be easier to apply a texture than render the same content using primitive shapes. Almost any view or image in Cocoa can be used to create an OpenGL texture. From a view or image, you create a bitmap image representation object (as described in “Capturing the Contents of a View or Image”) and then use that object to create your texture.

Listing 6-6 shows a self-contained method for creating a texture from an NSImage object. After creating the NSBitmapImageRep object, this method configures some texture-related parameters, creates a new texture object, and then associates the bitmap data with that object. This method handles 24-bit RGB and 32-bit RGBA images, but you could readily modify it to support other image formats. You must pass in a pointer to a valid GLuint variable for texName but the value stored in that parameter can be 0. If you specify a nonzero value, your identifier is associated with the texture object and can be used to identify the texture later; otherwise, an identifier is returned to you in the texName parameter.

Listing 6-6  Creating an OpenGL texture from an image

- (void)textureFromImage:(NSImage*)theImg textureName:(GLuint*)texName
{
    NSBitmapImageRep* bitmap = [NSBitmapImageRep alloc];
    int samplesPerPixel = 0;
    NSSize imgSize = [theImg size];
 
    [theImg lockFocus];
    [bitmap initWithFocusedViewRect:
                    NSMakeRect(0.0, 0.0, imgSize.width, imgSize.height)];
    [theImg unlockFocus];
 
    // Set proper unpacking row length for bitmap.
    glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap pixelsWide]);
 
    // Set byte aligned unpacking (needed for 3 byte per pixel bitmaps).
    glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
 
    // Generate a new texture name if one was not provided.
    if (*texName == 0)
        glGenTextures (1, texName);
    glBindTexture (GL_TEXTURE_RECTANGLE_EXT, *texName);
 
    // Non-mipmap filtering (redundant for texture_rectangle).
    glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER,  GL_LINEAR);
    samplesPerPixel = [bitmap samplesPerPixel];
 
    // Nonplanar, RGB 24 bit bitmap, or RGBA 32 bit bitmap.
    if(![bitmap isPlanar] &&
        (samplesPerPixel == 3 || samplesPerPixel == 4))
    {
        glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0,
            samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
            [bitmap pixelsWide],
            [bitmap pixelsHigh],
            0,
            samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
            GL_UNSIGNED_BYTE,
            [bitmap bitmapData]);
    }
    else
    {
        // Handle other bitmap formats.
    }
 
    // Clean up.
    [bitmap release];
}

In the preceding code, there are some additional points worth mentioning:

For more information on Apple's support for OpenGL, see OpenGL Programming Guide for Mac OS X.

Applying Core Image Filters

In Mac OS X v10.4 and later, Core Image filters are a fast and efficient way to modify an image without changing the image itself. Core Image filters use graphics acceleration to apply real-time effects such as Gaussian blurs, distortions, and color corrections to an image. Because the filters are applied when content is composited to the screen, they do not modify the actual image data.

Core Image filters operate on CIImage objects. If you have an existing CIImage object, you can simply apply any desired filters to it and use it to create an NSCIImageRep object. You can then add this image representation object to an NSImage object and draw the results in your view. In Mac OS X v10.5 and later, you can also use the initWithCIImage: method of NSBitmapImageRep to render already-processed images directly to a bitmap representation.

If you do not already have a CIImage object, you need to create one using your program’s existing content. The first step is to create a bitmap image representation for the content you want to modify, the process for which is described in “Creating a Bitmap.” Once you have an NSBitmapImageRep object, use the initWithBitmapImageRep: method of CIImage to create a Core Image image object.

For information on how to apply Core Image filters to a CIImage object, see Using Core Image Filters in Core Image Programming Guide.

Converting a Bitmap to a Different Format

The NSBitmapImageRep class provides built-in support for converting bitmap data to several standard formats. To convert bitmap images to other formats, you can use any of the following methods:

The first set of methods generate TIFF data for the bitmap. For all other supported formats, you use the representationOfImageRepsInArray:usingType:properties: and representationUsingType:properties: methods. These methods support the conversion of bitmap data to BMP, GIF, JPEG, PNG, and TIFF file formats.

All of the preceding methods return an NSData object with the formatted image data. You can write this data out to a file or use it to create a new NSBitmapImageRep object.

Associating a ColorSync Profile With an Image

You can associate a ColorSync profile with a NSBitmapImageRep object containing pixel data produced by decoding a TIFF, JPEG, GIF or PNG file. To associate the data with the bitmap image representation, you use the setProperty:withValue: method of NSBitmapImageRep and the NSImageColorSyncProfileData property. Listing 6-7 shows an example of how to load the ColorSync data and associate it with a bitmap image representation.

Listing 6-7  Adding a ColorSync profile to an image

@implementation NSBitmapImageRep (MoreColorMethods)
- (NSBitmapImageRep *) imageRepWithProfileAtPath:(NSString *) pathToProfile
{
    id result = [self copy];
 
    // Build an NSData object using the specified ColorSync profile
    id profile = [NSData dataWithContentsOfFile: pathToProfile];
 
    // Set the ColorSync profile for the object
    [result setProperty:NSImageColorSyncProfileData withValue:profile];
 
    return [result autorelease];
}
@end

Converting Between Color Spaces

Cocoa does not provide any direct ways to convert images from one color space to another. Although Cocoa fully supports color spaces included with existing image files, there is no way to convert image data directly using NSImage. Instead, you must use a combination of Quartz and Cocoa to convert the image data.

Creating the Target Image

Converting the color space of an existing image requires the use of Quartz to establish a drawing context that uses the target color space. Once you have a CGContextRef object with the desired color space, you can use it to configure the Cocoa drawing environment and draw your image.

Listing 6-8 shows you how to create a Quartz bitmap context using a custom color space. This function receives a CMProfileRef object, which you can get from the ColorSync Manager or from the colorSyncProfile method of NSColorSpace. It uses the color profile to determine the number of channels in the color space. Once it knows the total number of channels (including alpha) needed for the bitmap, it creates and returns a matching bitmap context.

Listing 6-8  Creating a bitmap with a custom color profile

CGContextRef CreateCGBitmapContextWithColorProfile(size_t width,
                size_t height,
                CMProfileRef profile,
                CGImageAlphaInfo alphaInfo)
{
    size_t bytesPerRow = 0;
    size_t alphaComponent = 0;
 
    // Get the type of the color space.
    CMAppleProfileHeader header;
    if (noErr != CMGetProfileHeader(profile, &header))
        return nil;
 
    // Get the color space info from the profile.
    CGColorSpaceRef csRef = CGColorSpaceCreateWithPlatformColorSpace(profile);
    if (csRef == NULL)
        return NULL;
 
    // Add 1 channel if there is an alpha component.
    if (alphaInfo != kCGImageAlphaNone)
        alphaComponent = 1;
 
    // Check the major color spaces.
    OSType space = header.cm2.dataColorSpace;
    switch (space)
    {
        case cmGrayData:
            bytesPerRow = width;
 
            // Quartz doesn’t support alpha for grayscale bitmaps.
            alphaInfo = kCGImageAlphaNone;
            break;
 
        case cmRGBData:
            bytesPerRow = width * (3 + alphaComponent);
            break;
 
        case cmCMYKData:
            bytesPerRow = width * 4;
 
            // Quartz doesn’t support alpha for CMYK bitmaps.
            alphaInfo = kCGImageAlphaNone;
            break;
 
        default:
            return NULL;
    }
 
    // Allocate the memory for the bitmap.
    void*   bitmapData = malloc(bytesPerRow * height);
 
    CGContextRef    theRef = CGBitmapContextCreate(bitmapData, width,
                                    height, 8,  bytesPerRow,
                                    csRef, alphaInfo);
 
    // Cleanup if an error occurs; otherwise, the caller is responsible
    // for releasing the bitmap data.
    if ((!theRef) && bitmapData)
        free(bitmapData);
 
    CGColorSpaceRelease(csRef);
    return theRef;
}

Once you have a Quartz bitmap context, you can create a new Cocoa graphics context object and use it for drawing. To create the NSGraphicsContext object, you use the graphicsContextWithGraphicsPort: method, which takes a CGContextRef object as a parameter. You then use the setCurrentContext: method to make it current and begin drawing. When you are done drawing, you use Quartz to create a CGImageRef object containing the results. Listing 6-9 shows this process.

Listing 6-9  Converting a bitmap to a different color space

- (CGImageRef) convertBitmapImageRep:(NSBitmapImageRep*)theRep toColorSpace:(NSColorSpace*)colorspace
{
    if (!theRep)
        return nil;
 
    // Map the Cocoa constants returned by -bitmapFormat to their
    // Quartz equivalents.
    CGImageAlphaInfo alphaInfo = GetAlphaInfoFromBitmapImageRep(theRep);
 
    // Get the rest of the image info.
    NSSize imageSize = [theRep size];
    size_t width = imageSize.width;
    size_t height = imageSize.height;
    CMProfileRef profile = (CMProfileRef)[colorspace colorSyncProfile];
 
    // Create a new 8-bit bitmap context based on the image info.
    CGContextRef cgContext = CreateCGBitmapContextWithColorProfile(width,
                                     height, profile, alphaInfo);
 
    if (cgContext == NULL)
        return NULL;
 
    // Create an NSGraphicsContext that draws into the CGContext.
    NSGraphicsContext *graphicsContext = [NSGraphicsContext
             graphicsContextWithGraphicsPort:cgContext flipped:NO];
 
    // Make the NSGraphicsContext current and draw into it.
    [NSGraphicsContext saveGraphicsState];
    [NSGraphicsContext setCurrentContext:graphicsContext];
 
    // Create a new image for rendering the original bitmap.
    NSImage*  theImage = [[[NSImage alloc] initWithSize:imageSize] autorelease];
    [theImage addRepresentation:theRep];
 
    // Draw the original image in the Quartz bitmap context.
    NSRect imageRect = NSMakeRect(0.0, 0.0, imageSize.width, imageSize.height);
    [theImage drawAtPoint:NSMakePoint(0.0, 0.0)
                fromRect:imageRect
                operation: NSCompositeSourceOver
                fraction: 1.0];
    [NSGraphicsContext restoreGraphicsState];
 
    // Create a CGImage from the CGContext's contents.
    CGImageRef cgImage = CGBitmapContextCreateImage(cgContext);
 
    // Release the context. Note that this does not release the bitmap data.
    CGContextRelease(cgContext);
 
    return cgImage;
}

To create an NSImage object from a CGImageRef object, simply lock focus on a new NSImage object and call the CGContextDrawImage function to render the Quartz image. You can obtain a CGContextRef object from the current context after you lock focus, as shown in Listing 6-10.

Listing 6-10  Using a CGImageRef object to create an NSImage object

- (NSImage*) imageFromCGImageRef:(CGImageRef)image
{
    NSRect imageRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
    CGContextRef imageContext = nil;
    NSImage* newImage = nil;
 
    // Get the image dimensions.
    imageRect.size.height = CGImageGetHeight(image);
    imageRect.size.width = CGImageGetWidth(image);
 
    // Create a new image to receive the Quartz image data.
    newImage = [[[NSImage alloc] initWithSize:imageRect.size] autorelease];
    [newImage lockFocus];
 
    // Get the Quartz context and draw.
    imageContext = (CGContextRef)[[NSGraphicsContext currentContext]
                                         graphicsPort];
    CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image);
    [newImage unlockFocus];
 
    return newImage;
}

Using a Custom Color Profile

If you have an existing ICC profile and want to associate that profile with an image, you must do so using the ColorSync Manager. If you are working with Quartz graphic contexts, you use the ICC profile to obtain the color space information needed to create a CGImageRef object. You can then use that color space information to create an appropriate context for rendering your image.

Listing 6-11 shows you how to create a CGColorSpaceRef object from an ICC profile. This code uses several ColorSync Manager functions to create a CMProfileRef object, from which you can then extract the color space object. Mac OS X includes several standard ICC profiles in the /System/Library/ColorSync/Profiles/ directory.

Listing 6-11  Creating a color space from a custom color profile

CGColorSpaceRef CreateColorSpaceForProfileAtPath(NSString* path)
{
    CMProfileLocation profileLoc;
    CMProfileRef profileRef;
    CGColorSpaceRef csRef = NULL;
 
    // Specify where the ICC profile data file is located.
    profileLoc.locType = cmPathBasedProfile;
    strncpy(profileLoc.u.pathLoc.path, [path fileSystemRepresentation],  255);
 
    // Get the ColorSync profile information from the data file.
    CMOpenProfile(&profileRef, &profileLoc);
 
    // Use the profile to create the color space object.
    csRef = CGColorSpaceCreateWithPlatformColorSpace(profileRef);
    CMCloseProfile(profileRef);
 
    return csRef;
}

For more information on ColorSync and its functions, see ColorSync Manager Reference.

Premultiplying Alpha Values for Bitmaps

Although premultiplying alpha values used to be a common way to improve performance when rendering bitmaps, the technique is not recommended for programs running in Mac OS X. Premultiplication involves multiplying values in the bitmap’s alpha channel with the corresponding pixel values and storing the results back to the bitmap’s source file. The goal of premultiplicaton is to reduce the number of calculations performed when the bitmap is composited with other content. In Mac OS X, premultiplication can actually result in more calculations.

In Mac OS X, color correction is integral to the operating system. In order to process colors correctly, ColorSync needs the original pixel color values. If a bitmap contains premultiplied color values, ColorSync must undo the premultiplication before it can check the colors. This extra step adds a significant amount of work to the system because it must be performed every time the colors are checked.

The only reason to consider premultiplication of alpha values for your bitmaps is if your data is already premultiplied and leaving it that way is beneficial to your program’s data model. Even so, you should do some performance tests to see if using premultiplied bitmaps hurts your overall application performance. Cocoa incorporates color management into many parts of the framework. If your code paths use these parts of the framework, you might find it beneficial to change your model.



< Previous PageNext Page > Hide TOC


Last updated: 2007-10-31




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice