Scale-9 in Objective-C Cocoa on the Mac

18th June 2009

Having used flash extensively for the last few years I have really come to rely on using Scale-9 scaling on UI elements in the rich internet apps I design and develop. It is a great technique for things like rounded rectangles, button graphics etc and ensuring the rounded corners do not distort when stretched.

I have recently been doing Cocoa Development on Mac OS X 10.5 and was looking for a way to do this in Cocoa and came across a great drawing method named NSDrawNinePartImage. The method has the following signature:

void NSDrawNinePartImage(NSRect frame,
   NSImage *topLeftCorner,
   NSImage *topEdgeFill,
   NSImage *topRightCorner,
   NSImage *leftEdgeFill,
   NSImage *centerFill,
   NSImage *rightEdgeFill,
   NSImage *bottomLeftCorner,
   NSImage *bottomEdgeFill,
   NSImage *bottomRightCorner,
   NSCompositingOperation op,
   CGFloat alphaFraction,
   BOOL flipped

This method requires the developer to provide 9 separate images to create the 9 sections of the scale 9 image. Luckily the article I had stumbled across had a handy example for using a single class, so I thought that I would use their technique to create a simple generic class that can be used to draw a single NSImage as a Scale-9 scalable image. This could be used for example in an NSView or NSButtonCell.

//  GTDrawImage.m
//  StretchMyView
//  Created by Jon Baker on 17/06/2009.
//  Copyright 2009 Go Tripod Ltd. All rights reserved.

#import "GTDrawImage.h"

@implementation GTDrawImage

+(void) drawScalableImage:(NSImage *)sourceImage scaleTopLeft:(NSPoint)topLeftPoint scaleBottomRight:(NSPoint)bottomRightPoint inFrame:(NSRect)frame
	NSSize sourceSize = [sourceImage size];

	// Top left
	NSRect topLeftTileRect = NSMakeRect(0, 0, topLeftPoint.x, sourceSize.height - topLeftPoint.y);
	NSRect topLeftCutRect = NSMakeRect(0, topLeftPoint.y, topLeftTileRect.size.width, topLeftTileRect.size.height);

	// TopRight
	NSRect topRightTileRect = NSMakeRect(0,0, sourceSize.width - bottomRightPoint.x, topLeftTileRect.size.height);
	NSRect topRightCutRect = NSMakeRect(sourceSize.width - topRightTileRect.size.width, topLeftPoint.y, topRightTileRect.size.width, topRightTileRect.size.height);

	// Top
	NSRect topTileRect = NSMakeRect(0, 0, sourceSize.width - topLeftTileRect.size.width - topRightTileRect.size.width, topLeftTileRect.size.height);
	NSRect topCutRect = NSMakeRect(topLeftPoint.x, topLeftPoint.y, topTileRect.size.width, topTileRect.size.height);

	// Bottom Left
	NSRect bottomLeftTileRect = NSMakeRect(0, 0, topLeftCutRect.size.width, bottomRightPoint.y);
	NSRect bottomLeftCutRect = NSMakeRect(0, 0, bottomLeftTileRect.size.width, bottomLeftTileRect.size.height);

	// Bottom Right
	NSRect bottomRightTileRect = NSMakeRect(0,0, topRightCutRect.size.width, bottomLeftTileRect.size.height);
	NSRect bottomRightCutRect = NSMakeRect(topRightCutRect.origin.x, 0, bottomRightTileRect.size.width , bottomRightTileRect.size.height );

	// Bottom
	NSRect bottomTileRect = NSMakeRect(0,0, topTileRect.size.width, bottomLeftTileRect.size.height);
	NSRect bottomCutRect = NSMakeRect(topCutRect.origin.x, 0, bottomTileRect.size.width, bottomTileRect.size.height);

	// left

	NSRect leftTileRect = NSMakeRect(0, 0, bottomLeftTileRect.size.width, sourceSize.height - topTileRect.size.height - bottomTileRect.size.height);
	NSRect leftCutRect = NSMakeRect(0, bottomRightPoint.y, leftTileRect.size.width, leftTileRect.size.height);

	// right

	NSRect rightTileRect = NSMakeRect(0, 0, topRightCutRect.size.width, leftCutRect.size.height);
	NSRect rightCutRect = NSMakeRect(bottomRightPoint.x, bottomRightPoint.y, rightTileRect.size.width, rightTileRect.size.height);

	NSRect centerTileRect = NSMakeRect(0, 0, topTileRect.size.width, leftTileRect.size.height);
	NSRect centerCutRect = NSMakeRect(topCutRect.origin.x, bottomRightPoint.y, centerTileRect.size.width, centerTileRect.size.height);

	NSImage *topLeft = [[NSImage alloc] initWithSize:topLeftTileRect.size];
	[topLeft lockFocus];
	[sourceImage drawInRect:topLeftTileRect fromRect:topLeftCutRect operation:NSCompositeCopy fraction:1.0];
	[topLeft unlockFocus];

	NSImage *top = [[NSImage alloc] initWithSize:topTileRect.size];
	[top lockFocus];
	[sourceImage drawInRect:topTileRect fromRect:topCutRect operation:NSCompositeCopy fraction:1.0];
	[top unlockFocus];

	NSImage *topRight = [[NSImage alloc] initWithSize:topRightTileRect.size];
	[topRight lockFocus];
	[sourceImage drawInRect:topRightTileRect fromRect:topRightCutRect operation:NSCompositeCopy fraction:1.0];
	[topRight unlockFocus];

	//setup center section, left, right
	NSImage *left = [[NSImage alloc] initWithSize:leftTileRect.size];
	[left lockFocus];
	[sourceImage drawInRect:leftTileRect fromRect:leftCutRect operation:NSCompositeCopy fraction:1.0];
	[left unlockFocus];

	NSImage *center = [[NSImage alloc] initWithSize:centerTileRect.size];
	[center lockFocus];
	[sourceImage drawInRect:centerTileRect fromRect:centerCutRect operation:NSCompositeCopy fraction:1.0];
	[center unlockFocus];

	NSImage *right = [[NSImage alloc] initWithSize:rightTileRect.size];
	[right lockFocus];
	[sourceImage drawInRect:rightTileRect fromRect:rightCutRect operation:NSCompositeCopy fraction:1.0];
	[right unlockFocus];

	NSImage *bottomLeft = [[NSImage alloc] initWithSize:bottomLeftTileRect.size];
	[bottomLeft lockFocus];
	[sourceImage drawInRect:bottomLeftTileRect fromRect:bottomLeftCutRect operation:NSCompositeCopy fraction:1.0];
	[bottomLeft unlockFocus];

	NSImage *bottom = [[NSImage alloc] initWithSize:bottomTileRect.size];
	[bottom lockFocus];
	[sourceImage drawInRect:bottomTileRect fromRect:bottomCutRect operation:NSCompositeCopy fraction:1.0];
	[bottom unlockFocus];

	NSImage *bottomRight = [[NSImage alloc] initWithSize:bottomRightTileRect.size];
	[bottomRight lockFocus];
	[sourceImage drawInRect:bottomRightTileRect fromRect:bottomRightCutRect operation:NSCompositeCopy fraction:1.0];
	[bottomRight unlockFocus];

						topLeft, top, topRight,
						left, center, right,
						bottomLeft, bottom, bottomRight,
						NSCompositeSourceOver, 1.0, NO);

	[topLeft release];
	[top release];
	[topRight release];
	[left release];
	[center release];
	[right release];
	[bottomLeft release];
	[bottom release];
	[bottomRight release];


To use this generic classes, you simply need to import it where required:

#import "GTDrawImage.h"

…and then you can call from within, for example the drawRect:, selector using the following method:

- (void)drawRect:(NSRect)rect {
	[GTDrawImage drawScalableImage:baseImage scaleTopLeft:NSMakePoint(8, 16) scaleBottomRight:NSMakePoint(16,8) inFrame: [self bounds]];

drawScalableImage takes an NSImage, in this case baseImage, and two reference points (NSPoint) to split the image into nine slices, firstly the scaleTopLeft: and secondly the scaleBottomRight:, both located from the bottom left corner. So for example to split a 24px x 24px image into 9 equal 8px by 8px segments (see below), the topLeftPoint would be x=8px, y=16px and the bottom x=16 and y=8.

Just download the following classes into your projects and away you go:

Got an idea for a project?

Need a website? Web-enabled software to streamline your business? Just some advice? A hug?