Before you can draw an image, you have to create or load the image data. Cocoa supports several techniques for creating new images and loading existing images.
Loading an Existing Image
Loading a Named Image
Drawing to an Image
Creating a Bitmap
Creating a PDF or EPS Image Representation
Using a Quartz Image to Create an NSImage
For existing images, you can load the image directly from a file or URL using the methods of NSImage. When you open an image file, NSImage automatically creates an image representation that best matches the type of data in that file. Cocoa supports numerous file formats internally. In Mac OS X v10.4 and later, Cocoa supports even more file formats using the Image I/O framework. For information on supported file types, see “Supported Image File Formats.”
The following example shows how to load an existing image from a file. It is important to remember that images loaded from an existing file are intended primarily for rendering. If you want to manipulate the data directly, copy it to an offscreen window or other local data structure and manipulate it there.
NSString* imageName = [[NSBundle mainBundle] |
pathForResource:@"image1" ofType:@"JPG"]; |
NSImage* tempImage = [[NSImage alloc] initWithContentsOfFile:imageName]; |
For frequently used images, you can use the Application Kit’s named image registry to store and load them. This registry provides a fast and convenient way to retrieve images without creating a new NSImage object each time. You can add images to this registry explicitly or you can use the registry itself to load known system or application-specific images, such as the following:
System images stored in the Resources directory of the Application Kit framework
Your application’s icon or other images located in the Resources directory of your main bundle.
To retrieve images from the registry, you use the imageNamed: class method of NSImage. This method looks in the registry for an image associated with the name you provide. If none is found, it looks for the image among the Application Kit's shared resources. After that, it looks for a file in the Resources directory of your application bundle, and finally it checks the Application Kit bundle. If it finds an image file, it loads the image, adds it to the registry, and returns the corresponding NSImage object. Subsequent attempts to retrieve the same image file return the already-loaded NSImage object.
To retrieve your application icon, ask for an image with the name NSApplicationIcon. Your application's custom icon is returned, if it has one; otherwise, Cocoa returns the generic application icon provided by the system. For a list of image names you can use to load other standard system images, see the constants section in NSImage Class Reference.
In addition to loading existing image files, you can also add images to the registry explicitly by sending a setName: message to an NSImage object. The setName: method adds the image to the registry under the designated name. You might use this method in cases where the image was created dynamically or is not located in your application bundle.
Note: When adding images to the registry explicitly, choose a name that does not match the name of any image in your application bundle. If you choose a name that is used by a bundle resource, the explicitly added image supersedes that resource. You can still load the resource using the methods of NSBundle, however.
It is possible to create images programmatically by locking focus on an NSImage object and drawing other images or paths into the image context. This technique is most useful for creating images that you intend to render to the screen, although you can also save the resulting image data to a file.
Listing 6-1 shows you how to create a new empty image and configure it for drawing. When creating a blank image, you must specify the size of the new image in pixels. If you lock focus on an image that contains existing content, the new content is composited with the old content. When drawing, you can use any routines that you would normally use when drawing to a view.
Listing 6-1 Drawing to an image
NSImage* anImage = [[NSImage alloc] initWithSize:NSMakeSize(100.0, 100.0)]; |
[anImage lockFocus]; |
// Do your drawing here... |
[anImage unlockFocus]; |
// Draw the image in the current context. |
[anImage drawAtPoint:NSMakePoint(0.0, 0.0) |
fromRect: NSMakeRect(0.0, 0.0, 100.0, 100.0) |
operation: NSCompositeSourceOver |
fraction: 1.0]; |
Drawing to an image creates an NSCachedImageRep object or uses an existing cached image representation, if one exists. Even when you use the lockFocusOnRepresentation: method to lock onto a specific image representation, you do not lock onto the representation itself. Instead, you lock onto the cached offscreen window associated with that image representation. This behavior might seem confusing but reinforces the notion of the immutability of images and their image representations.
Images and their representations are considered immutable for efficiency and safety reasons. If you consider the image files stored in your application bundle, would you really want to make permanent changes to the original image? Rather than change the original image data, NSImage and its image representations modify a copy of that data. Modifying a cached copy of the data is also more efficient for screen-based drawing because the data is already in a format ready for display on the screen.
There are a few different ways to create bitmaps in Cocoa. Some of these techniques are more convenient than others and some may not be available in all versions of Mac OS X, so you should consider each one carefully. The following list summarizes the most common techniques and the situations in which you might use them:
To capture the contents of an existing view or image, use one of the following techniques:
Lock focus on the desired object and use the initWithFocusedViewRect: method of NSBitmapImageRep.
In Mac OS X v10.4 and later, use the bitmapImageRepForCachingDisplayInRect: and cacheDisplayInRect:toBitmapImageRep: methods of NSView. The first method creates a bitmap image representation suitable for use in capturing the view's contents while the second draws the view contents to the bitmap. You can reuse the bitmap image representation object to update the view contents periodically, as long as you remember to clear the old bitmap before capturing a new one.
To draw directly into a bitmap, create a new NSBitmapImageRep object with the parameters you want and use the graphicsContextWithBitmapImageRep: method of NSGraphicsContext to create a drawing context. Make the new context the current context and draw. This technique is available only in Mac OS X v10.4 and later.
Alternatively, you can create an NSImage object (or an offscreen window), draw into it, and then capture the image contents. This technique is supported in all versions of Mac OS X.
To create the bitmap bit-by-bit, create a new NSBitmapImageRep object with the parameters you want and manipulate the pixels directly. You can use the bitmapData method to get the raw pixel buffer. NSBitmapImageRep also defines methods for getting and setting individual pixel values. This technique is the most labor intensive but gives you the most control over the bitmap contents. For example, you might use it if you want to decode the raw image data yourself and transfer it to the bitmap image representation.
The sections that follow provide examples on how to use the first two techniques from the preceding list. To manipulate a bitmap pixel-by-pixel, see the NSBitmapImageRep reference.
Important: In many operating systems, offscreen bitmaps are used to buffer the actual content of a view or window. In Mac OS X, you should generally not use offscreen bitmaps in this way. Most Mac OS X windows are already double-buffered to prevent rendering artifacts caused by drawing during a refresh cycle. Adding your own offscreen bitmap would result in your window being triple-buffered, which is a waste of memory.
A simple way to create a bitmap is to capture the contents of an existing view or image. When capturing a view, the view can either belong to an onscreen window or be completely detached and not onscreen at all. When capturing an image, Cocoa chooses the image representation that provides the best match for your target bitmap.
Before attempting to capture the contents of a view, you should consider invoking the view’s canDraw method to see if the view should be drawn. Cocoa views return NO from this method in situations where the view is currently hidden or not associated with a valid window. If you are trying to capture the current state of a view, you might use the canDraw method to prevent your code from capturing the view when it is hidden.
Once you have your view or image, lock focus on it and use the initWithFocusedViewRect: method of NSBitmapImageRep to capture the contents. When using this method, you specify the exact rectangle you want to capture from the view or image. Thus, you can capture all of the contents or only a portion; you cannot scale the content you capture. The initWithFocusedViewRect: method captures the bits exactly as they appear in the focused image or view.
Listing 6-2 shows how to create a bitmap representation from an existing image. The example gets the image to capture from a custom routine, locks focus on it, and creates the NSBitmapImageRep object. Your own implementation would need to replace the call to myGetCurrentImage with the code to create or get the image used by your program.
Listing 6-2 Capturing the contents of an existing image
NSImage* image = [self myGetCurrentImage]; |
NSSize size = [image size]; |
[image lockFocus]; |
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: |
NSMakeRect(0,0,size.width,size.height)]; |
[image unlockFocus]; |
To capture the content of an onscreen view, you would use code very much like the preceding example. After locking focus on the view, you would create your NSBitmapImageRep object using the initWithFocusedViewRect: method.
To capture the content of a detached (offscreen) view, you must create an offscreen window for the view before you try to capture its contents. The window object provides a backing buffer in which to hold the view’s rendered content. As long as you do not order the window onto the screen, the origin you specify for your window does not really matter. The example in Listing 6-3 uses large negative values for the origin coordinates (just to make sure the window is not visible) but could just as easily use the coordinate (0, 0).
Listing 6-3 Drawing to an offscreen window
NSRect offscreenRect = NSMakeRect(-10000.0, -10000.0, |
windowSize.width, windowSize.height); |
NSWindow* offscreenWindow = [[NSWindow alloc] |
initWithContentRect:offscreenRect |
styleMask:NSBorderlessWindowMask |
backing:NSBackingStoreRetained |
defer:NO]; |
[offscreenWindow setContentView:myView]; |
[[offscreenWindow contentView] display]; // Draw to the backing buffer |
// Create the NSBItmapImageRep |
[[offscreenWindow contentView] lockFocus]; |
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: |
NSMakeRect(0, 0, windowSize.width, windowSize.height)]; |
// Clean up and delete the window, which is no longer needed. |
[[offscreenWindow contentView] unlockFocus]; |
[offscreenWindow release]; |
In Mac OS X v10.4 and later, it is possible to create a bitmap image representation object and draw to it directly. This technique is simple and does not require the creation of any extraneous objects, such as an image or window. If your code needs to run in earlier versions of Mac OS X, however, you cannot use this technique.
Listing 6-4, creates a new NSBitmapImageRep object with the desired bit depth, resolution, and color space. It then creates a new graphics context object using the bitmap and makes that context the current context.
Listing 6-4 Drawing directly to a bitmap
NSRect offscreenRect = NSMakeRect(0.0, 0.0, 500.0, 500.0); |
NSBitmapImageRep* offscreenRep = nil; |
offscreenRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil |
pixelsWide:offscreenRect.size.width |
pixelsHigh:offscreenRect.size.height |
bitsPerSample:8 |
samplesPerPixel:4 |
hasAlpha:YES |
isPlanar:NO |
colorSpaceName:NSCalibratedRGBColorSpace |
bitmapFormat:0 |
bytesPerRow:(4 * offscreenRect.size.width) |
bitsPerPixel:32]; |
[NSGraphicsContext saveGraphicsState]; |
[NSGraphicsContext setCurrentContext:[NSGraphicsContext |
graphicsContextWithBitmapImageRep:offscreenRep]]; |
// Draw your content... |
[NSGraphicsContext restoreGraphicsState]; |
Once drawing is complete, you can add the bitmap image representation object to an NSImage object and display it like any other image. You can use this image representation object as a texture in your OpenGL code or examine the bits of the bitmap using the methods of NSBitmapImageRep.
The process for creating an image representation for PDF or EPS data is the same for both. In both cases, you use a custom NSView object together with the Cocoa printing system to generate the desired data. From the generated data, you then create an image representation of the desired type.
The NSView class defines two convenience methods for generating data based on the contents of the view:
For PDF data, use the dataWithPDFInsideRect: method of NSView.
For EPS data, use the dataWithEPSInsideRect: method of NSView.
When you send one of these messages to your view, Cocoa launches the printing system, which drives the data generation process. The printing system handles most of the data generation process, sending appropriate messages to your view object as needed. For example, Cocoa sends a drawRect: message to your view for each page that needs to be drawn. The printing system also invokes other methods to compute page ranges and boundaries.
Note: The NSView class provides a default pagination scheme. To provide a custom scheme, your view must override the knowsPageRange: and rectForPage: methods at a minimum. For more information about printing and pagination, see Printing Programming Topics for Cocoa.
After the printing system finishes, the code that called either dataWithPDFInsideRect: or dataWithEPSInsideRect: receives an NSData object with the PDF or EPS data. You must then pass this object to the imageRepWithData: method of either NSEPSImageRep or NSPDFImageRep to initialize a new image representation object, which you can then add to your NSImage object.
Listing 6-5 shows the basic steps for creating a PDF image from some view content. The view itself must be one that knows how to draw the desired content. This can be a detached view designed solely for drawing the desired content with any desired pagination, or it could be an existing view in one of your windows.
Listing 6-5 Creating PDF data from a view
MyPDFView* myView = GetMyPDFRenderView(); |
NSRect viewBounds = [myView bounds]; |
NSData* theData = [myView dataWithPDFInsideRect:viewBounds]; |
NSPDFImageRep* pdfRep = [NSPDFImageRep imageRepWithData:theData]; |
// Create a new image to hold the PDF representation. |
NSImage* pdfImage = [[NSImage alloc] initWithSize:viewBounds.size]; |
[pdfImage addRepresentation:pdfRep]; |
If you choose to use an existing onscreen view, your view’s drawing code should distinguish between content drawn for the screen or for the printing system and adjust content accordingly. Use the currentContextDrawingToScreen class method or the isDrawingToScreen instance method of NSGraphicsContext to determine whether the current context is targeted for the screen or a print-based canvas. These methods return NO for operations that generate PDF or EPS data.
Important:
When drawing in a printing context, the only supported compositing operators are NSCompositeCopy and NSCompositeSourceOver. If you need to render content using any other operators, you must composite them to an image or offscreen window first and then render the resulting image to the printing context using one of the supported operators.
The NSImage class does not provide any direct means for wrapping data from a Quartz image object. If you have a CGImageRef object, the simplest way to create a corresponding Cocoa image is to lock focus on an NSImage object and draw your Quartz image using the CGContextDrawImage function. The basic techniques for how to do this are covered in “Drawing to an Image.”
Last updated: 2007-10-31