Shiny Red Buttons (2)

Today, we’ll see how to use Quartz 2D to generate rounded rectangles, either as outlines, or as clipped regions. We’ll begin with some background on Quartz technicalities, and then see some simple path generation code.

Quartz 2D

Quartz 2D (aka Core Graphics) is a low-level 2D graphics toolkit/framework on Mac OS X and iPhone OS. We’ll use it (over the next few days) to create some shiny buttons. Before we do that, we need to understand some subtleties of its coordinate, path, and rendering systems.

Coordinates. You refer to Quartz 2D pixels in user space, a 2-D grid. On this grid, pixels are:

  • 1 unit square
  • Spaced 1 unit center-to-center
  • Centered between grid lines

Since the Quartz 2D system places its origin in the lower-left, this means that the center of the lower-left pixel in user space is located at (0.5, 0.5).

Paths. Paths are zero-thickness lines that run through user space. If a path is stroked, a thickness is supplied as part of the rendering operation, not as a characteristic of the path.

Rendering. When a pixel is drawn its “coverage fraction” is taken into consideration. For instance:

  • If you stroke a path with 1-pixel width from (0,1) to (5,1), then pixels (0,0), (1,0), (2,0), (3,0), and (4,0) will have their top halves covered, while pixels (0,1), (1,1), (2,1), (3,1), and (4,1) will have their bottom halves covered; all 10 pixels will be drawn with an alpha blend of 50%.
  • If a path segment runs from (0,0.5) to (5,0.5), and the entire path is defined and clipping configured such that everything below this path segment is clipped out, then pixels (0,0), (1,0), (2,0), (3,0), and (4,0) will be considered only 50% covered, and rendered with an alpha blend of 50%.

Rounded Rects

With all that covered, we can define some rounded rect code. In an attempt to compromise between generality and ease of use, I decided to define a function that adds a rounded rect path to a Quartz 2D CGContext. These paths can be placed anywhere in the context, and configured for either stroking or clipping. (However: stroking paths should run through the middle of pixels, while clipping paths should run around their borders.)

@implementation UIButton (Glossy)

+ (void)setPathToRoundedRect:(CGRect)rect forInset:(NSUInteger)inset inContext:(CGContextRef)context
{
	// Experimentally determined
	static NSUInteger cornerRadius = 10;

	// Unpack size for compactness, find minimum dimension
	CGFloat w = rect.size.width;
	CGFloat h = rect.size.height;
	CGFloat m = w<h?w:h;
	
	// Bounds
	CGFloat b = rect.origin.y;
	CGFloat t = b + h;
	CGFloat l = rect.origin.x;
	CGFloat r = l + w;
	CGFloat d = (inset<cornerRadius)?(cornerRadius-inset):0;
	
	// Special case: Degenerate rectangles abort this method
	if (m <= 0) return;

	// Limit radius to 1/2 of the rectangle's shortest axis
	d = (d>0.5*m)?(0.5*m):d;
	
	// Define a CW path in the CG co-ordinate system (origin at LL)
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, (l+r)/2, t);		// Begin at TDC
	CGContextAddArcToPoint(context, r, t, r, b, d);	// UR corner
	CGContextAddArcToPoint(context, r, b, l, b, d);	// LR corner
	CGContextAddArcToPoint(context, l, b, l, t, d);	// LL corner
	CGContextAddArcToPoint(context, l, t, r, t, d);	// UL corner
	CGContextClosePath(context);					// End at TDC
}

@end

Upcoming

Tomorrow, I’ll post some test cases that exercise this code, and that show how to call it. On Monday, we’ll get into the “shiny” part of creating shiny buttons.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

One Response to Shiny Red Buttons (2)

  1. Pingback: Shiny buttons at Under The Bridge