Graphics contexts are a fundamental part of the drawing infrastructure in Cocoa applications. As the name suggests, a graphics context provides the context for subsequent drawing operations. It identifies the current drawing destination (screen, printer, file, and so on), the coordinate system and boundaries for the underlying canvas, and any graphics attributes associated with the destination.
For most of the drawing you do in Cocoa, you never need to create a graphics context yourself. The normal drawing cycle in Cocoa automatically creates and configures a graphics context for you to use. For some advanced drawing, however, you may need to create your own graphics context prior to drawing.
In a Cocoa application, graphics contexts for nearly all types of canvas are represented by the NSGraphicsContext
class. You use graphics context objects to manipulate graphics attributes and to get information about the current drawing environment.
Note: For OpenGL drawing, you use the NSOpenGLContext
class instead of NSGraphicsContext
for the graphics context object. OpenGL drawing, and use of the NSOpenGLContext
class, are covered in “Using OpenGL in Your Application.”
This chapter provides an overview of Cocoa graphics contexts and how you use them in your application. It includes information on how to create custom graphics contexts and when it might be appropriate to do so.
Graphics Context Basics
Modifying the Current Graphics State
Creating Graphics Contexts
Threading and Graphics Contexts
The primary job of any graphics context object is to maintain information about the current state of the drawing environment. In Quartz, the graphics context object is associated with a window, bitmap, PDF file, or other output device and maintains information for that device. The same is true for a Cocoa graphics context, but because Cocoa drawing is view-based, some additional changes are made to the drawing environment before your view’s drawRect:
method is called.
By the time your view’s drawRect:
method is called, Cocoa has made sure that any drawing calls you make stay within the confines of your view. It saves the graphics state to simplify the process of undoing its changes later. It adds an appropriate transform to the current transformation matrix to place the drawing origin at the origin of your view. It also sets the clipping region to your view's visible boundaries, preventing any rendered content from straying into other views. Your view is effectively the star of the show, at least until another view’s drawRect:
method is called.
While the current context is focused on your view, you can draw paths, images, text, or any other content you want. You can also change the attributes of the current drawing environment to achieve the appearance you want for your content. Eventually, the content you draw is sent to the Quartz Compositor, where it is combined with the content from other views in the window and flushed to the screen or output device.
After your drawRect:
method returns, Cocoa goes through the process of resetting the drawing environment for the next view. It reverts any changes you made to the drawing environment and sets up the coordinate transform and clipping region for the next view, giving it its own pristine environment in which to work. This process then repeats itself during each update cycle in your application.
Each thread in a Cocoa application has its own graphics context object for a given window. You can access this object from your code using the currentContext
method of NSGraphicsContext
, as shown in the following example:
NSGraphicsContext* aContext = [NSGraphicsContext currentContext]; |
The currentContext
method always returns the Cocoa graphics context object that is appropriate for the current drawing environment. This object keeps track of the current graphics state, lets you save and restore graphics state information, and lets you modify many graphics state attributes. The changes you make to the graphics state affect all subsequent drawing calls. If you change an attribute more than once, only the most recent setting is used.
To save the current graphics state, you use the saveGraphicsState
method of NSGraphicsContext
. This method essentially pushes a copy of the current state onto a stack, leaving you free to make changes to the current state. When you want to revert back to the previous state, you simply call the restoreGraphicsState
method to pop the current graphics state (including all changes since the last save) off of the stack and restore the previous state.
If you plan to change the current graphics state significantly, it is a good idea to save the current state before making your changes. Modifying one or two attributes usually may not merit saving the graphics state, since you can reset or change those individual attributes easily. However, if you are changing more than one or two attributes, it is usually easier to save and restore the entire graphics state. You can call the saveGraphicsState
method as often as needed in your code to save snapshots of the current graphics state, but you must be sure to balance each call with a matching call to restoreGraphicsState
.
Note: The saveGraphicsState
and restoreGraphicsState
methods are available both as class methods and as instance methods. The class method versions simply save and restore the graphics state of the current context. The instance methods let you save the state of a specific context object, although in most cases this should be the current context.
The following example shows a simple drawRect:
method that iterates over an array of developer-defined objects, each of which is drawn with a different set of attributes. The graphics state is saved and restored during each loop iteration, ensuring that each object starts from the same graphics state.
- (void)drawRect:(NSRect)rect |
{ |
NSGraphicsContext* theContext = [NSGraphicsContext currentContext]; |
int i; |
int numObjects = [myObjectArray count]; |
// Iterate over an array of objects |
// Set the attributes for each before drawing |
for (i = 0; i < numObjects; i++) |
{ |
[theContext saveGraphicsState]; |
// Set the drawing attributes |
// Draw the object |
[theContext restoreGraphicsState]; |
} |
} |
Warning:
When saving and restoring the graphics state, you must balance all calls to saveGraphicsState
with a corresponding call to restoreGraphicsState
. Failure to do so can result in unexpected changes to the appearance of any windows that use that view.
Each Cocoa graphics context object maintains information about the current state of the drawing environment. This information ranges from the global rendering settings to the attributes used to render the current path and is the same state information saved by Quartz. Whenever you save the current graphics state, you save a copy of the settings listed in Table 2-1.
Attribute |
Description |
---|---|
Maps points in the view’s coordinate system to points in the destination device's coordinate system. Cocoa modifies the CTM before calling your view’s |
|
Specifies the area of the canvas that can be painted by drawing calls. Cocoa modifies the clipping region to the visible area of your view before calling its |
|
Specifies the width of paths. The default line width is |
|
Specifies how two connected lines are joined together. The default join style is |
|
Specifies the appearance of an open end point on a path. The default line cap style is |
|
Defines a broken pattern for lines, including the initial phase for the style. There is no default dash style, resulting in solid lines. You modify dash styles for a path using an |
|
Determines when lines should be joined with a bevel instead of a miter. Applies only when the line join style is set to |
|
Specifies the accuracy with which curves are rendered. (It is also the maximum error tolerance, measured in pixels.) Smaller numbers result in smoother curves at the expense of more calculations. The interpretation of this value may vary slightly on different rendering devices. The default value is |
|
Specifies the color used for rendering paths. This color applies only to the path line itself, not the area the path encompasses. You can specify colors using any of the system-supported color spaces. This value includes alpha information. Color information is managed by the |
|
Specifies the color used to fill the area enclosed by a path. You can specify colors using any of the system-supported color spaces. This value includes alpha information. Color information is managed by the |
|
Specifies the shadow attributes to apply to rendered content. You set shadows using the | |
Specifies the technique used to map in-gamut colors to the gamut of the current color space. Cocoa does not support setting this attribute directly. Instead, you must use Quartz. For more information, see “Mapping Physical Colors to a Color Space.” |
|
Specifies the font to use when drawing text. You modify font information using the |
|
Specifies the font size to use when drawing text. You modify font information using the |
|
Specifies the character spacing to use when drawing text. (This attribute is supported only indirectly by Cocoa.) For more information on drawing text, see “Text Attributes.” |
|
Specifies how to render the text. (This attribute is supported only indirectly by Cocoa.) For more information on drawing text, see “Text Attributes.” |
|
Specifies the process used to interpolate images during rendering. You use the | |
Specifies the process used to composite source and destination material together. (The compositing operations supported by Cocoa are related to the Quartz blend modes but differ in their usage and behavior.) You use the | |
Specifies a global alpha (transparency) value to apply in addition to the alpha value for a given color. Cocoa does not support this attribute directly. If you want to set it, you must use the |
|
Specifies whether paths use aliasing to smooth lines as they cross pixel boundaries. You use the |
Note: The winding rule used to fill paths is not stored as part of the current graphics state. You can set a default winding rule for NSBezierPath
objects but doing so affects content rendered using those objects. For more information, see “Winding Rules.”
In a broad sense, Cocoa graphics context objects serve two types of canvases: screen-based canvases and print-based canvases. A screen-based graphics context renders content to a window, view, or image with the results usually appearing on a screen. A print-based graphics context is used to render content to a printer spool file, PDF file, PostScript file, EPS file, or other medium usually associated with the printing system.
For nearly all screen-based and print-based drawing, Cocoa provides an appropriate graphics context object automatically. Cocoa provides a graphics context object during all view updates and in response to the user printing a document. There are situations, however, where you must create a graphics context object manually, including the following:
Drawing to an offscreen bitmap
Creating PDF or EPS data
Initiating a print job programmatically
Using the class methods of NSGraphicsContext
, you can create graphics context objects for drawing to screen-based canvases. You cannot use these methods for print-based canvas, however. Cocoa routes all printing operations through the Cocoa printing system, which handles the task of setting up the graphics context object for you. This means that if you want to generate PDF data, EPS data, or print to a printer, you must use the methods of the NSPrintOperation
class to create a print job for your target. It also means that your views should provide some minimal printing support if you want to produce well-formatted output for print-based canvases.
Note: Although Cocoa does provide some support for creating OpenGL graphics contexts automatically, the default pixel format options are usually limited. In most cases, you will want to create a custom OpenGL graphics context with the pixel format options you need for drawing. For more information, see “Creating an OpenGL Graphics Context.”
You can determine the type of canvas being managed by the current graphics context using the isDrawingToScreen
instance method or currentContextDrawingToScreen
class method of NSGraphicsContext
. For print-based canvases, you can use the attributes
method to get additional information about the canvas, such as whether it is being used to generate a PDF or EPS file.
For more information about obtaining contexts for both screen-based and print-based canvases, see “Creating Graphics Contexts.”
The NSGraphicsContext
class in Cocoa is a wrapper for a Quartz graphics context (CGContextRef
data type). Both types manage the same basic information, and in fact, many methods of NSGraphicsContext
simply call their Quartz equivalents. This relationship makes it easy to perform any Quartz-related drawing in your application. It also means that any time you have a Cocoa graphics context (an instance of the NSGraphicsContext
class), you have a Quartz graphics context as well.
For information on how to use Cocoa graphics contexts to call Quartz functions, see “Using Quartz in Your Application.”
In your view’s drawRect:
method, one of the first things you may want to do is modify the current drawing environment. For example, you might want to configure the current drawing colors, modifying the clipping region, transform the coordinate system, and so on. Many attributes can be set directly using the methods of NSGraphicsContext
but some require the use of other objects. The following sections list the available drawing attributes and how you modify them.
Important: Saving and restoring the current graphics state is a relatively expensive operation that should done as little as possible. In general, you should try to save and restore the graphics state only to undo several changes at once or when there is no alternative, such as to reset the clipping path. For individual changes, setting a new value directly is often more efficient than saving and restoring the entire graphics state.
Cocoa provides support for colors in a variety of different color spaces. The NSColor
class supports RGB, CMYK, and grayscale color spaces by default but can also support custom color spaces defined by ICC and ColorSync profiles. The colors you specify include the color channels appropriate for the color space and an optional alpha component to define the transparency of the color.
To set the current stroke or fill attributes, create an NSColor
object and send it a set
, setStroke
, or setFill
message. The stroke and fill attributes define the color or pattern for paths and the areas they enclose. The current stroke and fill colors affect all drawn content except text, which requires the application of text attributes; see “Applying Color to Text.”
For more information about colors and how to create them, see “Color and Transparency.”
To modify the value of path attributes, you use the NSBezierPath
class. Using the methods of this class, you can set the line width, line join style, line dash style, line cap style, miter limit, flatness, and winding rule attributes. All of these attributes affect the way paths are rendered by Cocoa.
Path attributes come in two flavors: global and path-specific. When you use the class methods in NSBezierPath
to set the "default" value for an attribute, you are setting the global attribute. Global attributes are global to path objects (as opposed to the graphics state), so setting a global attribute affects all paths you render using the NSBezierPath
class, but does not affect Quartz-based paths. To override a global attribute for an individual path object, you should set a path-specific value. For example, to set the global line width, you use the setDefaultLineWidth:
class method of NSBezierPath
. To set the line width for a specific NSBezierPath
object, you use its setLineWidth:
instance method.
For information on how to set both default and path-specific attributes, and to see the resulting appearance of rendered content, see “Path Attributes.”
For most string-based drawing in Cocoa, you apply text attributes directly to the strings, rather than relying on the global font settings. The Cocoa string objects and the Cocoa text system both support the use of attributes for modifying the appearance of string. For NSAttributedString
objects, you apply the attributes directly to character ranges in the string. For regular NSString
objects, you apply the attributes to the entire string when you draw it.
If you want to set the global font settings stored in the graphics state, perhaps for drawing strings using Quartz, you can use the NSFont
object to set the font family and size. After creating a font object, you use its set method to apply the font information to the current graphics state.
For more information about drawing options for text, see “Text.” For more information about the Cocoa text system, see Text System Overview.
When you render each visual element, you need to decide how that element interacts with any surrounding content. You might want the element to be layered on top of or behind the current content or be merged with it in interesting ways. You specify this behavior using different compositing options.
Compositing options specify how the colors in source content are blended with the existing content in the drawing destination. With fully opaque colors, most compositing options simply mask or overlay different parts of the source and destination content. With partially transparent colors, however, you can achieve interesting blending effects.
The Cocoa compositing options differ from the blend modes used in Quartz, although the two perform basically the same task. The Cocoa options are inherited from the NextStep environment, whereas the Quartz blend modes are part of the newer PDF-based rendering model. Despite their historical legacy, the Cocoa options are still a very powerful way to composite content, and may even be a little easier to understand than their Quartz counterparts.
Important: Despite their similarities, there is no direct mapping between the Cocoa compositing options and the Quartz blend modes. In addition, when drawing to a print-based canvas, you should use only the NSCompositeCopy
or the NSCompositeSourceOver
operators. (For PDF content, you should use only the NSCompositeSourceOver
operator or the Quartz blend modes.) If you need to use any other compositing operators, you should render your content to an image and then draw the image to the printing context using one of the supported operators. If your application relies heavily on PDF blend modes, you may want to use Quartz for your drawing instead.
Figure 2-1 shows the Cocoa compositing options and how they affect rendered content. At the top of the figure are the source and destination content being rendered. The veins of the leaf are completely transparent while the rest of the leaf is opaque. In the destination image, the color is rendered at partial opacity. Below that are the results for each of the supported compositing operations.
Table 2-2 lists the mathematical equations used to compute pixel colors during compositing operations. In each equation, R
is the resulting (premultiplied) color, S
is the source color, D
is the destination color, Sa
is the alpha value of the source color, and Da
is the alpha value of the destination color. All color component values and alpha values are in the range 0 to 1 for these computations.
Para | Para |
---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
To set the current compositing operation, you use the setCompositingOperation:
method of NSGraphicsContext
. This sets the global compositing option to use if no other operator is specified. The default compositing option is NSCompositeSourceOver
.
The clipping region is a useful way to limit drawing to a specific portion of your view. Instead of creating complex graphics offscreen and then compositing them precisely in your view, you can use a clipping region to mask out the portions of your view you do not want modified. For example, you might use a clipping region to prevent drawing commands from drawing over some already rendered content. Similarly, you might use a clipping region to cut out specific portions of an image you want to render.
Before invoking your view’s drawRect:
method, Cocoa configures the clipping region of the current graphics context to match the visible area of your view. This prevents your view's drawing code from rendering content outside of your view's boundaries, possibly on top of other views.
You can restrict the drawable region of your view even further by adding shapes to the current clipping region. Whenever you add a new shape to the current clipping region, Cocoa determines the intersection of the shape with the current clipping region and uses the result as the new clipping region. This behavior means that you should generally add only one shape to the clip region before doing your drawing. The shape you add can be a single rectangle, multiple rectangles, or a combination of multiple complex subpaths in a single NSBezierPath
object.
For simple rectangular shapes, the easiest way to clip is using the NSRectClip
function. To specify multiple rectangular regions, use the NSRectClipList
function instead. To clip your view to a nonrectangular region, you must use an NSBezierPath
object. The path you create can be arbitrarily complex and include multiple rectangular and nonrectangular regions. Once you have the path you want, use the object’s addClip
method to add the resulting shape to the current clipping region. (For information on how to create paths, see “Drawing Fundamental Shapes.”)
Figure 2-2 shows the effects of applying a clipping path to an image. The top images show the image to be clipped and the path to use for the clip shape, which in this case consists of two shapes inside a single NSBezierPath
object. Although the clip shape is the same in both cases, the resulting clip region is different. This is because clipping takes into account the current winding rule when calculating the clipping region.
The following example shows you how to create the clip region shown in Figure 2-2. The clip region is composed of an overlapping square and circle, so you simply add a rectangle and oval with the appropriate sizes to a Bezier path object and call the addClip
method.
// If you plan to do more drawing later, it's a good idea |
// to save the graphics state before clipping. |
[NSGraphicsContext saveGraphicsState]; |
// Create the path and add the shapes |
NSBezierPath* clipPath = [NSBezierPath bezierPath]; |
[clipPath appendBezierPathWithRect:NSMakeRect(0.0, 0.0, 100.0, 100.0)]; |
[clipPath appendBezierPathWithOvalInRect:NSMakeRect(50.0, 50.0, 100.0, 100.0)]; |
// Add the path to the clip shape. |
[clipPath addClip]; |
// Draw the image. |
[NSGraphicsContext restoreGraphicsState]; |
Warning:
Although you can also use the setClip
method of NSBezierPath
to modify the clipping region, doing so is not recommended. The setClip
method replaces the entire clipping region with the area you specify. If the new clipping region extends beyond the bounds of your view, this could lead to portions of your content spilling over into neighboring views.
Cocoa graphics contexts support anti-aliasing in the same way that their Quartz counterparts do. Anti-aliasing is the process of artificially correcting the jagged (or aliased) edges surrounding text or shapes in bitmap images. These jagged edges occur primarily in lower-resolution bitmaps where it is easier to see individual pixels. To remove the jagged edges, Cocoa uses different colors for the pixels that surround a shape’s outline. The colors it uses are a blend of the original pixel color and the color of the shape’s outline. By blending colors in this way, the edges of the shape appear much smoother. Figure 2-3 shows the same image aliased and anti-aliased.
To enable or disable anti-aliasing, use the setShouldAntialias:
method of NSGraphicsContext
. Even with anti-aliasing disabled, it may still appears as if Cocoa is drawing content using aliasing. When drawing content on nonpixel boundaries, Cocoa may opt to split the line over multiple pixels, which can give the impression of aliasing. For more information about how to avoid this situation, see “Doing Pixel-Exact Drawing.”
The type of drawing you do in your application will determine whether you need to create any graphics context objects explicitly or simply use the one Cocoa provides you. If all you do is draw in your views, you can probably just use the Cocoa-provided context. This is true both for screen-based and print-based drawing. If your application performs any other type of drawing, however, you may need to create a graphics context yourself.
The following sections provide information on how and when to create Cocoa graphics contexts for your content.
If you want to do any drawing outside of the normal update cycle of your view, you must create a graphics context object explicitly. You might use this technique to draw in an offscreen window or bitmap and then copy the resulting bits elsewhere. You could also use it to draw to a window from a secondary thread. The NSGraphicsContext
class includes methods for creating new graphics context objects specifically for windows and bitmap images.
To draw to a window, you can use the graphicsContextWithWindow:
method of NSGraphicsContext
. The context you get back is initialized to the window itself, and not to a specific view. In fact, you may not want to use this technique if the window contains many subviews. In order to draw the views properly, you would need to walk the list of subviews manually and configure the drawing environment for each one, which is not recommended. Instead, you would use this technique for drawing to an offscreen buffer.
Important: Because most Mac OS X windows are already double-buffered, do not use offscreen windows or bitmaps simply to update the contents of a window. Doing so wastes memory (by adding a third buffer) and requires an extra copy operation to transfer the bits from the offscreen window to the window buffer.
To draw to a bitmap, you have two options. If your code runs in Mac OS X v10.4 and later, you can use the graphicsContextWithBitmapImageRep:
method to create a context object focused on an NSBitmapImageRep
object. The drawing you do is then rendered directly to the bitmap. If your code must run on earlier versions of Mac OS X, you must either lock focus on a view or use an offscreen window and then capture the contents of the view or window. For information and examples on how to create bitmaps, see “Creating a Bitmap”
Unlike screen-based contexts, if you want to create a graphics context for a PDF, EPS, or print-based canvas, you do not do so directly. All print-based operations must go through the Cocoa printing system, which handles the work required for setting up the printed pages and running the print job.
The simplest way to create a PDF or EPS file is to use the dataWithPDFInsideRect:
and dataWithEPSInsideRect:
methods of NSView
. These methods configure a print job automatically and use your view's existing drawing code to generate the PDF or EPS data. For more information and an example of how to use these methods, see “Creating a PDF or EPS Image Representation.”
To create a print job manually, you use the NSPrintOperation
class. This class offers several class methods for creating print jobs for a particular view and outputting the job to a printer, PDF file, or EPS file. Once you have an instance of the NSPrintOperation
class, you can set the print information and use the runOperation
method to start the print job, at which point Cocoa takes over.
Important:
You cannot create a viable graphics context for PDF or PostScript canvases using the graphicsContextWithAttributes:
method. You must go through the Cocoa Printing system instead.
During the execution of a print job, Cocoa calls several methods of your view to handle page layout and drawing. These methods are called for all printing paths, so implementing them for printing will also support PDF and EPS. For information on how to implement these methods, see Printing Programming Topics for Cocoa.
The Application Kit maintains a unique graphics context for each window and thread combination. Because each thread has its own graphics context object for a given window, it is possible to use secondary threads to draw to that window. There are some caveats, however.
During the normal update cycle for windows, all drawing requests are sent to your application’s main thread for processing. The normal update cycle happens when a user event triggers a change in your user interface. In this situation, you would call the setNeedsDisplay:
or setNeedsDisplayInRect:
method (or the display
family of methods) from your application’s main thread to invalidate the portions of your view that require redrawing. You should not call these methods from any secondary threads.
If you want to update a window or view from a secondary thread, you must manually lock focus on the window or view and initiate drawing yourself. Locking focus configures the drawing environment for that window's graphics context. Once locked, you can configure the drawing environment, issue your drawing commands as usual, and then flush the contents of the graphics context to the window buffer.
In order to draw regularly on a secondary thread, you must notify the thread yourself. The simplest way to send regular notifications is using an NSTimer
or NSAnimation
object. For more information on how to animate content, see “Advanced Drawing Techniques.”
Creating bitmaps on secondary threads is one way to thread your drawing code. Because bitmaps are self-contained entities, they can be created safely on secondary threads. From your thread, you would need to create the graphics context object explicitly (as described in “Creating a Screen-Based Context”) and then issue drawing calls to draw into the bitmap buffer. For more information on how to create bitmaps, including sample code, see “Creating a Bitmap.”
Important: Although drawing on secondary threads is allowed, you should always handle events and other user-requested actions from your application’s main thread only. Using multiple threads to handle events can lead to processing those events out of sequence, which can cause inconsistencies in your application’s behavior.
© 2005, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-01-06)