LockFocus

Fun with Compositing Operations

Graphics in Cocoa have a powerful but rarely used feature: compositing operations. These are similar to blend modes in the PDF format and in many graphics editing programs. They let you easily do some interesting things…

What are They?

Compositing operations determine how a graphic is drawn on top of existing graphics. They essentially determine what equation is used to determine the resulting color of the draw operation. These operations are often called Porter-Duff compositing after the programmers who defined them. In Cocoa, they’re represented by the NSCompositingOperation enumeration and their counterpart in Quartz is CGBlendMode. CGBlendMode was originally just for PDF blend modes, but in 10.5, the compositing operations were added. Note, though, that these new blend modes are not guaranteed to work with PDF CGContextRef‘s, only bitmap ones.

Where They’re Used

NSCompositingOperation is used in several places in AppKit. Most prominently, you can set the compositingOperation for an NSGraphicsContext to determine the operation for subsequent drawing. NSImage also uses NSCompositingOperation in most of its drawing methods. Finally, NSCompositingOperation is used in several graphics related AppKit functions like NSRectFillUsingOperation, and in fact this is related to a common Cocoa graphics problem. NSRectFill and NSFrameRect both use the NSCompositeCopy operation, which is not the right operation to use when drawing with a partially transparent color. In that case, when the color gets drawn it will me partially black instead of partially transparent. To make sure your rects get drawn right with partial transparency, use NSRectFillUsingOperation and NSFrameRectWithWidthUsingOperation and specify NSCompositeSourceOver (which is generally what you use for “normal” drawing) for the compositing operation.

Highlighting

One convenient use for composite operations is to replicate the type of effect used by the Finder when you select an icon. This is essentially just darkening the icon, but not the background behind the icon. The operation to do this is NSCompositeSourceAtop, and here’s some example NSView drawing code:

- (void)drawRect:(NSRect)rect
{
   NSRect bounds = [self bounds];

   // Draw the image
   NSImage *image = [NSImage imageNamed:@"Opacity"];
   [image drawInRect:bounds fromRect:NSZeroRect
           operation:NSCompositeSourceOver fraction:1.0];

   // Draw the highlight
   [[NSColor colorWithCalibratedWhite:0.0 alpha:0.5] setFill];
   NSRectFillUsingOperation(bounds, NSCompositeSourceAtop);
}

And here’s the resulting output:

Bad Highlighting

That’s not right! The problem here is that the NSView’s drawing is getting drawn together with the existing content (the window background). We need to isolate our drawing from what’s already there. A good way to do this is with a transparency layer, available from Quartz:

- (void)drawRect:(NSRect)rect
{
   NSRect bounds = [self bounds];

   // Start a transparency layer
   CGContextRef context = [[NSGraphicsContext currentContext]
                           graphicsPort];
   CGContextBeginTransparencyLayerWithRect(context,
                                           NSRectToCGRect(bounds),
                                           NULL);

   // Draw the image
   NSImage *image = [NSImage imageNamed:@"Opacity"];
   [image drawInRect:bounds fromRect:NSZeroRect
           operation:NSCompositeSourceOver fraction:1.0];

   // Draw the highlight
   [[NSColor colorWithCalibratedWhite:0.0 alpha:0.5] setFill];
   NSRectFillUsingOperation(bounds, NSCompositeSourceAtop);

   // End the transparency layer
   CGContextEndTransparencyLayer(context);
}

And now the image highlights correctly:

Good Highlighting

Coloring Images

Another nice trick you can do with the NSCompositeSourceAtop operation is re-coloring an image or graphic. This is a similar idea to highlighting, but goes further and completely replaces all of the (non-transparent) colors in the image, treating it sort of like a mask. I’ve found this useful for things like drawing images in table rows that should highlight in white when selected. I’ve put together a special function to do this coloring for you (including setting up a transparency layer to prevent coloring the background):

View LTColoredDrawing code

Here’s an example of how to use LTColoredDrawing in an NSView:

- (void)drawRect:(NSRect)rect
{
   NSRect bounds = [self bounds];

   // Start coloring
   NSColor *color = [NSColor blueColor];
   [color beginDrawingColoredInRect:bounds];

   // Draw the image
   NSImage *image = [NSImage imageNamed:@"Opacity"];
   [image drawInRect:bounds fromRect:NSZeroRect
           operation:NSCompositeSourceOver fraction:1.0];

   // Finish coloring
   [color endDrawingColoredInRect:bounds];
}

And here’s what this looks like in action:

Colored Opacity

Find Out More

You can explore compositing modes more with Opacity. Create an image with 2 or more layers, then select the top layer. Select the Window > Show Layers Info menu item and look at the Blend popup. The top two sections are PDF blend modes, and the bottom section includes the compositing modes. See what kinds of effects you can come up with!