For many types of content, path-based drawing has several advantages over image-based drawing:
Because paths are specified mathematically, they scale easily to different resolutions. Thus, the same path objects can be used for screen and print-based drawing.
The geometry information associated with a path requires much less storage space than most image data formats.
Rendering paths is often faster than compositing a comparable image. It takes less time to transfer path data to the graphics hardware than it takes to transfer the texture data associated with an image.
The following sections provide information about the primitive shapes you can draw using paths. You can combine one or more of these shapes to create a more complex path and then stroke or fill the path as described in “Drawing the Shapes in a Path.” For some shapes, there may be more than one way to add the shape to a path, or there may be alternate ways to draw the shape immediately. Wherever possible, the benefits and disadvantages of each technique are listed to help you decide which technique is most appropriate in specific situations.
Adding Points
Adding Lines and Polygons
Adding Rectangles
Adding Rounded Rectangles
Adding Ovals and Circles
Adding Arcs
Adding Bezier Curves
Adding Text
Drawing the Shapes in a Path
An NSPoint structure by itself represents a location on the screen; it has no weight and cannot be drawn as such. To draw the equivalent of a point on the screen, you would need to create a small rectangle at the desired location, as shown in Listing 5-8.
Listing 5-8 Drawing a point
void DrawPoint(NSPoint aPoint) |
{ |
NSRect aRect = NSMakeRect(aPoint.x, aPoint.y, 1.0, 1.0); |
NSRectFill(aRect); |
} |
Of course, a more common use for points is to specify the position of other shapes. Many shapes require you to specify the current point before actually creating the shape. You set the current point using the moveToPoint: or relativeMoveToPoint: methods. Some shapes, like rectangles and ovals, already contain location information and do not require a separate call to moveToPoint:.
Important:
You must specify a starting point before drawing individual line, arc, curve, and glyph paths. If you do not, NSBezierPath raises an exception.
Cocoa provides a couple of options for adding lines to a path, with each technique offering different tradeoffs between efficiency and correctness. You can draw lines in the following ways:
Create single horizontal and vertical lines by filling a rectangle using NSRectFill. This technique is less precise but is often a little faster than creating an NSBezierPath object. To create diagonal lines using this technique, you must apply a rotation transform before drawing. This technique is not appropriate for creating connected line segments.
Use the lineToPoint:, relativeLineToPoint:, or strokeLineFromPoint:toPoint: methods of NSBezierPath to create individual or connected line segments. This technique is fast and is the most precise option for creating lines and complex polygons.
Use the appendBezierPathWithPoints:count: method to create a series of connected lines quickly. This technique is faster than adding individual lines.
Polygons are composed of multiple connected lines and should be created using an NSBezierPath object. The simplest way to create a four-sided nonrectangular shape, like a parallelogram, rhombus, or trapezoid, is using line segments. You could also create these shapes using transforms, but calculating the correct skew factors would require a lot more work.
Listing 5-9 shows code to draw a parallelogram using NSBezierPath. The method in this example inscribes the parallelogram inside the specified rectangle. The withShift parameter specifies the horizontal shift applied to the top left and bottom right corners of the rectangular area.
Listing 5-9 Using lines to draw a polygon
void DrawParallelogramInRect(NSRect rect, float withShift) |
{ |
NSBezierPath* thePath = [NSBezierPath bezierPath]; |
[thePath moveToPoint:rect.origin]; |
[thePath lineToPoint:NSMakePoint(rect.origin.x - withShift, rect.origin.y)]; |
[thePath lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))]; |
[thePath lineToPoint:NSMakePoint(rect.origin.x + withShift, NSMaxY(rect))]; |
[thePath closePath]; |
[thePath stroke]; |
} |
Because rectangles are used frequently, there are several options for drawing them.
Use the methods of NSBezierPath to create your rectangle. The following methods are reasonably fast and offer the best precision:
Create rectangles using the Cocoa functions described in “Drawing Rectangles.” These functions draw rectangles faster than, but with less precision than, the methods of NSBezierPath.
Create a rectangle using individual lines as described in “Adding Lines and Polygons.” You could use this technique to create diagonally oriented rectangles—that is, rectangles whose sides are not parallel to the x and y axes—without using a rotation transform.
Listing 5-10 shows a simple function that fills and strokes the same rectangle using two different techniques. The current fill and stroke colors are used when drawing the rectangle, along with the default compositing operation. In both cases, the rectangles are drawn immediately; there is no need to send a separate fill or stroke message.
Listing 5-10 Drawing a rectangle
void DrawRectangle(NSRect aRect) |
{ |
NSRectFill(aRect); |
[NSBezierPath strokeRect:aRect]; |
} |
In Mac OS X v10.5 and later, the NSBezierPath class includes the following methods for creating rounded-rectangles:
These methods create rectangles whose corners are curved according to the specified radius values. The radii describe the width and height of the oval to use at each corner of the rectangle. Figure 5-8 shows how this inscribed oval is used to define the path of the rectangle’s corner segments.
Listing 5-11 shows a code snippet that creates and draws a path with a rounded rectangle.
Listing 5-11 Drawing a rounded rectangle
void DrawRoundedRect(NSRect rect, CGFloat x, CGFloat y) |
{ |
NSBezierPath* thePath = [NSBezierPath bezierPath]; |
[thePath appendBezierPathWithRoundedRect:rect xRadius:x yRadius:y]; |
[thePath stroke]; |
} |
To draw ovals and circles, use the following methods of NSBezierPath:
Both methods inscribe an oval inside the rectangle you specify. You must then fill or stroke the path object to draw the oval in the current context. The following example creates an oval from the specified rectangle and strokes its path.
void DrawOvalInRect(NSRect ovalRect) |
{ |
NSBezierPath* thePath = [NSBezierPath bezierPath]; |
[thePath appendBezierPathWithOvalInRect:ovalRect]; |
[thePath stroke]; |
} |
You could also create an oval using arcs, but doing so would duplicate what the preceding methods do internally and would be a little slower. The only reason to add individual arcs is to create a partial (nonclosed) oval path. For more information, see “Adding Arcs.”
To draw arcs, use the following methods of NSBezierPath:
appendBezierPathWithArcWithCenter:radius:startAngle:endAngle:
appendBezierPathWithArcWithCenter:radius:startAngle:endAngle:clockwise:
The appendBezierPathWithArcFromPoint:toPoint:radius: method creates arcs by inscribing them in an angle formed by the current point and the two points passed to the method. Inscribing a circle in this manner can result in an arc that does not intersect any of the points used to specify it. It can also result in the creation of an unwanted line from the current point to the starting point of the arc.
Figure 5-9 shows three different arcs and the control points used to create them. For the two arcs created using appendBezierPathWithArcFromPoint:toPoint:radius:, the current point must be set before calling the method. In both examples, the point is set to (30, 30). Because the radius of the second arc is shorter, and the starting point of the arc is not the same as the current point, a line is drawn from the current point to the starting point.
Listing 5-12 shows the code snippets you would use to create each of the arcs from Figure 5-9. (Although the figure shows the arcs individually, executing the following code would render the arcs on top of each other. )
Listing 5-12 Creating three arcs
NSBezierPath* arcPath1 = [NSBezierPath bezierPath]; |
NSBezierPath* arcPath2 = [NSBezierPath bezierPath]; |
[[NSColor blackColor] setStroke]; |
// Create the first arc |
[arcPath1 moveToPoint:NSMakePoint(30,30)]; |
[arcPath1 appendBezierPathWithArcFromPoint:NSMakePoint(0,30) toPoint:NSMakePoint(0,60) radius:30]; |
[arcPath1 stroke]; |
// Create the second arc. |
[arcPath2 moveToPoint:NSMakePoint(30,30)]; |
[arcPath2 appendBezierPathWithArcFromPoint:NSMakePoint(30,40) toPoint:NSMakePoint(70,30) radius:20]; |
[arcPath2 stroke]; |
// Clear the old arc and do not set an initial point, which prevents a |
// line being drawn from the current point to the start of the arc. |
[arcPath2 removeAllPoints]; |
[arcPath2 appendBezierPathWithArcWithCenter:NSMakePoint(30,30) radius:30 startAngle:45 endAngle:135]; |
[arcPath2 stroke]; |
To draw Bezier curves, you must use the curveToPoint:controlPoint1:controlPoint2: method of NSBezierPath. This method supports the creation of a cubic curve from the current point to the destination point you specify when calling the method. The controlPoint1 parameter determines the curvature starting from the current point, and controlPoint2 determines the curvature of the destination point, as shown in Figure 5-1.
Because NSBezierPath only supports path-based content, you cannot add text characters directly to a path; instead, you must add glyphs. A glyph is the visual representation of a character (or partial character) in a particular font. For glyphs in an outline font, this visual representation is stored as a set of mathematical paths that can be added to an NSBezierPath object.
Note: Using NSBezierPath is not the most efficient way to render text, but can be used in situations where you need the path information associated with the text.
To obtain a set of glyphs, you can use the Cocoa text system or the NSFont class. Getting glyphs from the Cocoa text system is usually easier because you can get glyphs for an arbitrary string of characters, whereas using NSFont requires you to know the names of individual glyphs. To get glyphs from the Cocoa text system, you must do the following:
Create the text system objects needed to manage text layout. For a tutorial on how to do this, see Assembling the Text System by Hand in Text System Overview.
Use the glyphAtIndex: or getGlyphs:range: method of NSLayoutManager to retrieve the desired glyphs.
Add the glyphs to your NSBezierPath object using one of the following methods:
When added to your NSBezierPath object, glyphs are converted to a series of path elements. These path elements simply specify lines and curves and do not retain any information about the characters themselves. You can manipulate paths containing glyphs just like you would any other path by changing the points of a path element or by modifying the path attributes.
There are two options for drawing the contents of a path: you can stroke the path or fill it. Stroking a path renders an outline of the path’s shape using the current stroke color and path attributes. Filling the path renders the area encompassed by the path using the current fill color and winding rule.
Figure 5-11 shows the same path from Figure 5-1 but with the contents filled and a different stroke width applied.
Last updated: 2007-10-31