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.
Pingback: Shiny buttons at Under The Bridge