oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Programming With Cocoa Bitmap Image Filters

by Michael Beam, speaker at the O'Reilly Mac OS X Conference

Today we reenter the realm of Cocoa graphics, where we'll learn how to work with bitmap images on a pixel basis. Cocoa represents bitmap images using the NSImageRep subclass called NSBitmapImageRep. This subclass can work with data formats for many file types: TIFF, GIF, JPEG, PNG, BMP, and even raw, untagged data.

To demonstrate how we can work with bitmap images on a low level--byte by byte--we will implement a filter in ImageApp that will take a color image and convert it to a gray-scale image. Our filter will take an NSImage, extract the raw bitmap data, operate on that data, and then return a new, modified NSImage object.

Preparing ImageApp for the Task

If you need the project file to start with, download it here. It's the same file we created in the last column.

Before we discuss the complexities of NSBitmapImageRep and our gray-scale filter, it's a good idea to first set up the infrastructure in ImageApp to support such behavior. This will be in the form of a new menu, Filter, which contains the single menu item, Make Gray scale, which will in turn invoke a method in MyDocument to run the image through the filter. Make Gray scale will communicate to MyDocument through First Responder in MainMenu.nib. (The discussion we had in the last column about File's Owner could also apply to First Responder. There is no First Responder instance contained in the nib; it's another stand-in object. First Responder is of the same nature as File's Owner.)

To set this up, open MainMenu.nib in Interface Builder. Double-click on First Responder to bring up the class info panel. From here add an action to First Responder called makeGrayscale:. This is the action to which we will connect our new menu item.

To create a new menu in the main menu, drag a Submenu object from the Cocoa-Menus palette and drop it between the Edit and Window menus. Rename the menu Filter, and change the name of the default menu item from Item to Make Gray scale. Then connect the menu item to the makeGrayscale: action of First Responder. After you save your work, we'll close shop in Interface Builder and return to Project Builder.

Back in Project Builder

To prepare ImageApp for the addition of a filter object, we need to implement the makeGrayscale: action in MyDocument. The filter object used to change the image will be an instance of the class IAGrayscaleFilter. This class defines only one method -filterImage:, which takes an argument and returns an NSImage. The NSImage will be set to autorelease, so we need to assert MyDocument's ownership over the returned image by sending a retain message to the returned image. Finally, the returned image will be set as the activeImage of the document, and we will then update the display to show the new image. To update the display, we need to add a method to IAWindowController that will allow us to tell the image view what image to draw. This method will be called -setImageToDraw:. Let's take a look at -makeGrayscale: to see how it looks; this will go in MyDocument.m:

- (void)makeGrayscale:(id)sender
    IAGrayscaleFilter *filter = [[IAGrayscaleFilter alloc] init];
    [activeImage autorelease];
    activeImage = [[filter filterImage:activeImage] retain];
    [windowController setImageToDraw:activeImage];
    [filter release];

The first thing we did was instantiate and initialize IAGrayscaleFilter, and assign the new object to the filter variable. For MyDocument to recognize IAGrayscaleFilter, we need to import its header file. Make sure you add that. Next we send an autorelease message to the object currently assigned to activeImage. We do this because in the next line of code we reassign activeImage to a different NSImage object. We need to release it before we lose contact with it. Next we execute the filter operation, then tell windowController to draw the new image, and finally release filter before exiting the method.

In IAWindowController we need to implement -setImageToDraw:. Here's what this looks like:

- (void)setImageToDraw:(NSImage *)image
    [view setImage:image];

All we did here was forward the image along to the image view using setImage:.

Now we're ready to start building our filter class. Let's start this out by creating a new class from the file menu, and naming it IAGrayscaleFilter. Then add the method declaration to the header:

- (NSImage *)filterImage:(NSImage *)srcImage;

and change the import statement to read:

#import <AppKit/AppKit.h>

to import the AppKit headers instead of Foundation. This is necessary because we use AppKit classes in this class. Now let's begin implementing our image filter.

Pages: 1, 2, 3

Next Pagearrow