The NSBezierPath class provides the behavior for drawing most primitive shapes, and for many complex shapes, it is the only tool available in Cocoa. An NSBezierPath object encapsulates the information associated with a path, including the points that define the path and the attributes that affect the appearance of the path. The following sections explain how NSBezierPath represents path information and also describe the attributes that affect a path’s appearance.
Path Elements
Subpaths
Path Attributes
Winding Rules
An NSBezierPath object uses path elements to build a path. A path element consists of a primitive command and one or more points. The command tells the path object how to interpret the associated points. When assembled, a set of path elements creates a series of line segments that form the desired shape.
The NSBezierPath class handles much of the work of creating and organizing path elements initially. Knowing how to manipulate path elements becomes important, however, if you want to make changes to an existing path. If you create a complex path based on user input, you might want to give the user the option of changing that path later. Although you could create a new path object with the changes, it is far simpler to modify the existing path elements. (For information on how to modify path elements, see “Manipulating Individual Path Elements.”)
The NSBezierPath class defines only four basic path element commands, which are listed in Table 5-1. These commands are enough to define all of the possible path shapes. Each command has one or more points that contain information needed to position the path element. Most path elements use the current drawing point as the starting point for drawing.
When you add a new shape to a path, NSBezierPath breaks that shape down into one or more component path elements for storage purposes. For example, calling moveToPoint: or lineToPoint: creates a Move To element or Line To element respectively. In the case of more complex shapes, like rectangles and ovals, several line or curve elements may be created. Figure 5-1 shows two shapes and the resulting path elements. For the curved segment, the figure also shows the control points that define the curve.
Listing 5-1 shows the code that creates the path shown in Figure 5-1.
Listing 5-1 Creating a complex path
NSBezierPath* aPath = [NSBezierPath bezierPath]; |
[aPath moveToPoint:NSMakePoint(0.0, 0.0)]; |
[aPath lineToPoint:NSMakePoint(10.0, 10.0)]; |
[aPath curveToPoint:NSMakePoint(18.0, 21.0) |
controlPoint1:NSMakePoint(6.0, 2.0) |
controlPoint2:NSMakePoint(28.0, 10.0)]; |
[aPath appendBezierPathWithRect:NSMakeRect(2.0, 16.0, 8.0, 5.0)]; |
A subpath is a series of connected line and curve segments within an NSBezierPath object. A single path object may contain multiple subpaths, with each subpath delineated by a Move To or Close Path element. When you set the initial drawing point (typically using the moveToPoint: method), you set the starting point of the first subpath. As you draw, you build the contents of the subpath until you either close the path (using the closePath method) or add another Move To element. At that point, the subpath is considered closed and any new elements are added to a new subpath.
Some methods of NSBezierPath automatically create a new subpath for you. For example, creating a rectangle or oval results in the addition of a Move To element, several drawing elements, and a Close Path and Move To element (see Figure 5-1 for an example). The Move To element at the end of the list of elements ensures that the current drawing point is left in a known location, which in this case is at the rectangle’s origin point.
Subpaths exist to help you distinguish different parts of a path object. For example, subpaths affect the way a path is filled; see “Winding Rules.” The division of a path into subpaths also affects methods such as bezierPathByReversingPath, which reverses the subpaths one at a time. In other cases, though, subpaths in an NSBezierPath object share the same drawing attributes.
An NSBezierPath object maintains all of the attributes needed to determine the shape of its path. These attributes include the line width, curve flatness, line cap style, line join style, and miter limit of the path. You set these values using the methods of NSBezierPath.
Path attributes do not take effect until you fill or stroke the path, so if you change an attribute more than once before drawing the path, only the last value is used. The NSBezierPath class maintains both a custom and default version of each attribute. Path objects use custom attribute values if they are set. If no custom attribute value is set for a given path object, the default value is used. The NSBezierPath class does not use path attribute values set using Quartz functions.
Note: Path attributes apply to the entire path. If you want to use different attributes for different parts of a path, you must create two separate path objects and apply the appropriate attributes to each.
The following sections describe the attributes you can set for a path object and how those attributes affect your rendered paths.
The line width attribute controls the width of the entire path. Line width is measured in points and specified as a floating-point value. The default width for all lines is 1. To change the default line width for all NSBezierPath objects, you use the setDefaultLineWidth: method. To set the line width for the current path object, you use the setLineWidth: method of that path object. To set the default line width for shapes rendered without an NSBezierPath object, you must use the CGContextSetLineWidth function in Quartz.
Fractional line widths are rendered as close as possible to the specified width, subject to the limitations of the destination device, the position of the line, and the current anti-aliasing setting. For example, suppose you want to draw a line whose width is 0.2 points. Multiplying this width by 1/72 points per inch yields a line that is 0.0027778 inches wide. On a 90 dpi screen, the smallest possible line would be 1 pixel wide or 0.0111 inches. To ensure your line is not hidden on the screen, Cocoa nominally draws it at the screen’s larger minimum width (0.0111 inches). In reality, if the line straddles a pixel boundary or anti-aliasing is enabled, the line might affect additional pixels on either side of the path. If the output device were a 600 dpi printer instead, Quartz would be able to render the line closer to its true width of 0.0027778 inches.
Listing 5-2 draws a few paths using different techniques. The NSFrameRect function uses the default line width to draw a rectangle, so that value must be set prior to calling the function. Path objects use the default value only if a custom value has not been set. You can even change the line width of a path object and draw again to achieve a different path width, although you would also need to move the path to see the difference.
Listing 5-2 Setting the line width of a path
// Draw a rectangle using the default line width: 2.0. |
[NSBezierPath setDefaultLineWidth:2.0]; |
NSFrameRect(NSMakeRect(20.0, 20.0, 10.0, 10.0)); |
// Set the line width for a single NSBezierPath object. |
NSBezierPath* thePath = [NSBezierPath bezierPath]; |
[thePath setLineWidth:1.0]; // Has no effect. |
[thePath moveToPoint:NSMakePoint(0.0, 0.0)]; |
[thePath lineToPoint:NSMakePoint(10.0, 0.0)]; |
[thePath setLineWidth:3.0]; |
[thePath lineToPoint:NSMakePoint(10.0, 10.0)]; |
// Because the last value set is 3.0, all lines are drawn with |
// a width of 3.0, not just the second line. |
[thePath stroke]; |
// Changing the width and stroking again draws the same path |
// using the new line width. |
[thePath setLineWidth:4.0]; |
[thePath stroke]; |
// Changing the default line width has no effect because a custom |
// value already exists. The path is rendered with a width of 4.0. |
[thePath setDefaultLineWidth:5.0]; |
[thePath stroke]; |
The current line cap style determines the appearance of the open end points of a path segment. Cocoa supports the line cap styles shown in Figure 5-2.
To set the line cap style for a NSBezierPath object, use the setLineCapStyle: method. The default line cap style is set to NSButtLineCapStyle. To change the default line cap style, use the setDefaultLineCapStyle: method. Listing 5-3 demonstrates both of these methods:
Listing 5-3 Setting the line cap style of a path
[// Set the default line cap style |
[NSBezierPath setDefaultLineCapStyle:NSButtLineCapStyle]; |
// Customize the line cap style for the new object. |
NSBezierPath* aPath = [NSBezierPath bezierPath]; |
[aPath moveToPoint:NSMakePoint(0.0, 0.0)]; |
[aPath lineToPoint:NSMakePoint(10.0, 10.0)]; |
[aPath setLineCapStyle:NSSquareLineCapStyle]; |
[aPath stroke]; |
The current line join style determines how connected lines in a path are joined at the vertices. Cocoa supports the line join styles shown in Figure 5-3.
To set the line join style for an NSBezierPath object, use the setLineJoinStyle: method. The default line join style is set to NSMiterLineJoinStyle. To change the default line join style, use the setDefaultLineJoinStyle: method. Listing 5-4 demonstrates both of these methods:
Listing 5-4 Setting the line join style of a path
[// Set the default line join style |
[NSBezierPath setDefaultLineJoinStyle:NSMiterLineJoinStyle]; |
// Customize the line join style for a new path. |
NSBezierPath* aPath = [NSBezierPath bezierPath]; |
[aPath moveToPoint:NSMakePoint(0.0, 0.0)]; |
[aPath lineToPoint:NSMakePoint(10.0, 10.0)]; |
[aPath lineToPoint:NSMakePoint(10.0, 0.0)]; |
[aPath setLineJoinStyle:NSRoundLineJoinStyle]; |
[aPath stroke]; |
The line dash style determines the pattern used to stroke a path. By default, stroked paths appear solid. Using a line-dash pattern, you can specify an alternating group of solid and transparent swatches. When setting a line dash pattern, you specify the width (in points) of each successive solid or transparent swatch. The widths you specify are then repeated over the entire length of the path.
Figure 5-4 shows some sample line dash patterns, along with the values used to create each pattern.
The NSBezierPath class does not support the concept of a default line dash style. If you want a line dash style, you must apply it to a path explicitly using the setLineDash:count:phase: method as shown in Listing 5-5, which renders the last pattern from the preceding figure.
Listing 5-5 Adding a dash style to a path
void AddDashStyleToPath(NSBezierPath* thePath) |
{ |
// Set the line dash pattern. |
float lineDash[6]; |
lineDash[0] = 40.0; |
lineDash[1] = 12.0; |
lineDash[2] = 8.0; |
lineDash[3] = 12.0; |
lineDash[4] = 8.0; |
lineDash[5] = 12.0; |
[thePath setLineDash:lineDash count:6 phase:0.0]; |
} |
The line flatness attribute determines the rendering accuracy for curved segments. The flatness value measures the maximum error tolerance (in pixels) to use during rendering. Smaller values result in smoother curves but require more computation time. Larger values result in more jagged curves but are rendered much faster.
Line flatness is one parameter you can tweak when you want to render a large number of curves quickly and do not care about accuracy. For example, you might increase this value during a live resize or scrolling operation when accuracy is not as crucial. Regardless, you should always measure performance to make sure such a modification actually saves time.
Figure 5-5 shows how changing the default flatness affects curved surfaces. The figure on the left shows a group of curved surfaces rendered with the flatness value set to 0.6 (its default value). The figure on the left shows the same curved surfaces rendered with the flatness value set to 30. The curvature of each surface is lost and now appears to be a set of connected line segments.
To set the flatness for a specific NSBezierPath object, use the setFlatness: method. To set the default flatness value, use setDefaultFlatness:, as shown in Listing 5-6:
Listing 5-6 Setting the flatness of a path
[- (void) drawRect:(NSRect)rect |
{ |
if ([self inLiveResize]) |
{ |
// Adjust the default flatness upward to reduce |
// the number of required computations. |
[NSBezierPath setDefaultFlatness:10.0]; |
// Draw live resize content. |
} |
// ... |
} |
Miter limits help you avoid spikes that occur when you join two line segments at a sharp angle. If the ratio of the miter length—the diagonal length of the miter—to the line thickness exceeds the miter limit, the corner is drawn using a bevel join instead of a miter join.
Figure 5-6 shows an example of how different miter limits affect the same path. This path consists of several 10-point wide lines connected by miter joins. In the figure on the left, the miter limit is set to its default value of 10. Because the miter lengths exceed the miter limit, the line joins are changed to bevel joins. By increasing the miter limit to 16, as shown in the figure on the right, the miter joins are restored but extend far beyond the point where the two lines meet.
To set the miter limits for a specific NSBezierPath object, use the setMiterLimit: method. To set the default miter limit for newly created NSBezierPath objects, use setDefaultMiterLimit:. Listing 5-7 demonstrates both of these methods:
Listing 5-7 Setting the miter limit for a path
// Increase the default limit |
[NSBezierPath setDefaultMiterLimit:20.0]; |
// Customize the limit for a specific path with sharp angles. |
NSBezierPath* aPath = [NSBezierPath bezierPath]; |
[aPath moveToPoint:NSMakePoint(0.0, 0.0)]; |
[aPath lineToPoint:NSMakePoint(8.0, 100.0)]; |
[aPath lineToPoint:NSMakePoint(16.0, 0.0)]; |
[aPath setLineWidth:5.0]; |
[aPath setMiterLimit:5.0]; |
[aPath stroke]; |
When you fill the area encompassed by a path, NSBezierPath applies the current winding rule to determine which areas of the screen to fill. A winding rule is simply an algorithm that tracks information about each contiguous region that makes up the path's overall fill area. A ray is drawn from a point inside a given region to any point outside the path bounds. The total number of crossed path lines (including implicit lines) and the direction of each path line are then interpreted using the rules in Table 5-2, which determine if the region should be filled.
Fill operations are suitable for use with both open and closed subpaths. A closed subpath is a sequence of drawing calls that ends with a Close Path path element. An open subpath ends with a Move To path element. When you fill a partial subpath, NSBezierPath closes it for you automatically by creating an implicit (non-rendered) line from the first to the last point of the subpath.
Figure 5-7 shows how the winding rules are applied to a particular path. Subfigure a shows the path rendered using the nonzero rule and subfigure b shows it rendered using the even-odd rule. Subfigures c and d add direction marks and the hidden path line that closes the figure to help you see how the rules are applied to two of the path’s regions.
To set the winding rule for an NSBezierPath object, use the setWindingRule: method. The default winding rule is NSNonZeroWindingRule. To change the default winding rule for all NSBezierPath objects, use the setDefaultWindingRule: method.
Last updated: 2007-10-31