The Quartz 2D API provides no functions to obtain a windows graphics context. Instead, you use the Cocoa framework to obtain a context for a window created in Cocoa, and the Carbon framework to obtain a context for a window created in Carbon. Obtaining a window graphics context from the Cocoa framework is fairly straightforward. The approach for obtaining a window graphics context from the Carbon framework depends on whether you are creating a new Mac OS X application or moving a QuickDraw-based application to Quartz 2D.
Window Graphics Context in Cocoa
Window Graphics Context in Carbon: HIView
Windows Graphics Context: QuickDraw
You obtain a Quartz graphics context from within the drawRect: routine of a Cocoa application using the following line of code:
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort]; |
The method currentContext returns the NSGraphicsContext instance of the current thread. The method graphicsPort returns the low-level, platform-specific graphics context represented by the receiver, which is a Quartz graphics context. (Don’t get confused by the method names; they are historical.) For more information see NSGraphicsContext.
After you obtain the graphics context, you can call any of the Quartz 2D drawing functions in your Cocoa application. You can also mix Quartz 2D calls with Cocoa drawing calls. You can see an example of Quartz 2D drawing to a Cocoa view by looking at Figure 2-1. The drawing consists of two overlapping rectangles, an opaque red one and a partially transparent blue one. You’ll learn more about transparency in “Color and Color Spaces.” The ability to control how much you can “see through” colors is one of the hallmark features of Quartz 2D.
To create the drawing in Figure 2-1, you first create a Cocoa application Xcode project. In Interface Builder, drag a Custom View to the window and subclass it. Then write an implementation for the subclassed view, similar to what Listing 2-1 shows. For this example, the subclassed view is named MyQuartzView. (You can name it whatever you like.) The drawRect: method for the view contains all the Quartz drawing code. A detailed explanation for each numbered line of code appears following the listing.
Note: The drawRect: method of the NSView class is invoked automatically each time the view needs to be drawn. To find out more information about overriding the drawRect: method, see NSView Class Reference.
Listing 2-1 Code that draws to a window graphics context
@implementation MyQuartzView |
- (id)initWithFrame:(NSRect)frameRect |
{ |
self = [super initWithFrame:frameRect]; |
return self; |
} |
- (void)drawRect:(NSRect)rect |
{ |
CGContextRef myContext = [[NSGraphicsContext // 1 |
currentContext]graphicsPort]; |
// ********** Your drawing code here ********** // 2 |
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);// 3 |
CGContextFillRect (myContext, CGRectMake (0, 0, 200, 100 ));// 4 |
CGContextSetRGBFillColor (myContext, 0, 0, 1, .5);// 5 |
CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200));// 6 |
} |
@end |
Here’s what the code does:
Obtains a graphics context for the view.
This is where you insert your drawing code. The four lines of code that follow are examples of using Quartz 2D functions.
Sets a red fill color that’s fully opaque. For information on colors and alpha (which sets opacity), see “Color and Color Spaces.”
Fills a rectangle whose origin is (0,0) and whose width is 200 and height is 100. For information on drawing rectangles, see “Paths.”
Sets a blue fill color that’s partially transparent.
Fills a rectangle whose origin is (0,0) and which width is 100 and height is 200.
If you are using the Carbon framework to create a new application for Mac OS X, you will want to use the HIToolbox API, and HIView in particular, for Quartz 2D drawing. HIView is the Quartz-based object-oriented view system available for implementing Carbon user interface elements in Mac OS X. You use Carbon events to obtain a window graphics context from an HIView by installing an event handler that responds to a draw event (kEventControlDraw). As long as the HIView is in a composited window, you can obtain the event parameter kEventParamCGContextRef from the draw event. Draw events for windows that are not composited do not contain this event parameter, which means you must use a composited window. If you can’t, see “Windows Graphics Context: QuickDraw.”
You need to perform these steps to draw to an HIView:
In Xcode, create a Carbon application.
Open the .nib file provided by Xcode and place an HIView in the main window.
Compositing must be turned on for the window. It’s on by default, so make sure you don’t turn it off.
Assign a signature and an ID to the view, as shown in Figure 2-2.
You don’t have to use the signature and ID in the figure. Make note of what you assign to these items. You need to declare constants in your code that have these exact values. Otherwise, your code won’t draw to the view.
In your application code, declare constants for the signature and ID.
Install an event handler on the HIView that you want to draw to. The handler must process the event whose class and kind are {kEventClassControl, kEventControlDraw}.
In your event handler, obtain a graphics context by calling the Carbon Event Manager function GetEventParameter and passing the constant kEventParamCGContextRef.
Listing 2-2 and Listing 2-3 show code that implements the previous steps. Listing 2-2 is the main routine and Listing 2-3 implements the event handler for the HIView. The handler obtains a graphics context for an HIView and then draws into that view. A detailed explanation for each numbered line of code appears following each listing.
Listing 2-2 The main routine in a Carbon drawing application
#define kMyHIViewSignature 'mVue'// 1 |
#define kMyHIViewFieldID 130 |
int main (int argc, char* argv[]) |
{ |
IBNibRef nibRef; |
OSStatus err; |
WindowRef myMainWindow; |
HIViewRef myHIView;// 2 |
static const EventTypeSpec myHIViewSpec[] = {kEventClassControl,// 3 |
kEventControlDraw }; |
static const HIViewID myHIViewID = { kMyHIViewSignature,// 4 |
kMyHIViewFieldID }; |
err = CreateNibReference (CFSTR("main"), &nibRef); |
require_noerr (err, CantGetNibRef); |
err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar")); |
require_noerr (err, CantSetMenuBar); |
err = CreateWindowFromNib (nibRef, CFSTR("MainWindow"), &myMainWindow); |
require_noerr (err, CantCreateWindow ); |
DisposeNibReference(nibRef); |
HIViewFindByID (HIViewGetRoot(myMainWindow), myHIViewID, &myHIView); // 5 |
err = InstallEventHandler (GetControlEventTarget (myHIView), // 6 |
NewEventHandlerUPP (MyDrawEventHandler), |
GetEventTypeCount (myHIViewSpec), |
myHIViewSpec, |
(void *) myHIView, |
NULL); |
ShowWindow (myMainWindow); |
RunApplicationEventLoop(); |
CantCreateWindow: |
CantSetMenuBar: |
CantGetNibRef: |
return err; |
} |
Here’s what the code does:
Declares constants for the signature and ID that you assign to the HIView in Interface Builder. Make sure they match exactly and that the combination is unique to your application.
Declares a variable for an HIViewRef data type to reference the HIView. You need this to set up the event handler.
Declares an event specification for the draw event. This is the event your HIView event handler responds to. Your event handler can respond to as many events as you’d like. This example handles only the draw event so that you can see exactly what needs to be done to handle drawing.
Declares an HIView ID using the constants for the signature and ID that you previously assigned and that uniquely identify the HIView in your application.
Obtains the reference to the HIView you placed in the window.
Calls the Carbon Event Manager function to install your event handler on the HIView.
Listing 2-3 An event handler for an HIView
OSStatus MyDrawEventHandler (EventHandlerCallRef myHandler, |
EventRef event, void *userData) |
{ |
OSStatus status = noErr; |
CGContextRef myContext; |
HIRect bounds; |
status = GetEventParameter (event, // 1 |
kEventParamCGContextRef, |
typeCGContextRef, |
NULL, |
sizeof (CGContextRef), |
NULL, |
&myContext); |
require_noerr(status, CantGetGraphicsContext); |
HIViewGetBounds ((HIViewRef) userData, &bounds);// 2 |
require_noerr(status, CantGetBoundingRectangle); |
// ********** Your drawing code here ********** // 3 |
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);// 4 |
CGContextFillRect (myContext, CGRectMake(0, 0, 200, 100 ));// 5 |
CGContextSetRGBFillColor (myContext, 0, 0, 1, .5);// 6 |
CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200 ));// 7 |
CantGetGraphicsContext: |
CantGetBoundingRectangle: |
return status; |
} |
Here’s what the code does:
Calls the Carbon Event Manager function to obtain the graphics context from the event. You must pass:
The event to get the parameter from. The system passes this event to your handler. Recall that you registered for the draw event.
The symbolic name of the parameter you want to obtain. In this case, pass the system-defined constant kEventParamCGContextRef.
The desired type of the parameter, which you specify as the system-defined constant typeCGContextRef.
The actual type of the parameter, which is NULL because it’s not necessary to get this information for this example.
The size of the data you are obtaining.
The actual size of the data, which is NULL because it’s not necessary to get this information for this example.
A pointer to a CGContextRef data type which, on output, is the window graphics context for the view. This is what you’ll draw to.
Obtains the bounding rectangle for the HIView. Note that this code assumes the userData passed to the event handler is an HIViewRef. Another approach you can take is to obtain the view by extracting the event parameter kEventParamDirectObject from the event.
This is where you insert your drawing code. The four lines of code below this are examples of using Quartz 2D functions.
Sets a red fill color that’s fully opaque. For information on colors and alpha (which sets opacity), see “Color and Color Spaces.”
Fills a rectangle whose origin is (0,0) and whose width is 200 and height is 100. For information on drawing rectangles, see “Paths.”
Sets a blue fill color that’s partially transparent.
Fills a rectangle whose origin is (0,0) and which width is 100 and height is 200.
Figure 2-3 shows the output produced by the drawing code in Listing 2-3. Compare Figure 2-3 with the output produced by the same Quartz 2D drawing calls from a Cocoa application, and shown in Figure 2-1. Notice that one drawing is flipped with respect to the other.
The Quartz coordinate system, as mentioned in “Overview of Quartz 2D,” places the origin at the lower-left corner of the view. But HIView returns a context that places the origin a the upper-left corner of the view. Each origin has its advantages. HIView uses the upper left to ensure that the coordinates of objects, such as controls, do not change as the user resizes the window. If you want to use the Quartz coordinate system, you can add the following two lines of code to your event handler, placed just before your drawing code:
CGContextTranslateCTM (myContext, 0, bounds.size.height); |
CGContextScaleCTM (myContext, 1.0, -1.0); |
The first line of code translates the coordinate system so that the y values are moved towards the bottom of the HIView by the height of the HIView bounding rectangle. If you were to draw now, your drawing would be below the HIView, not in a visible area.
The second line of code flips the y-coordinates by a factor of –1.0. Because you just translated the coordinates below the HIView, the scaling effectively flips them into the HIView. After this operation, the origin is at the lower left of the HIView, with the y values increasing from bottom to top. The x values are unchanged; they still increase from left to right.
Figure 2-4 shows the output from Listing 2-3 after inserting the translation and scaling code before the drawing code in the handler. The sample code in the rest of this book uses the Quartz coordinate system. If you plan to use an HIView to try out the sample code from the book, you may want to transform the coordinates so that your output matches that shown in the figures.
For more information, see Carbon Event Manager Reference and HIView Programming Guide.
If your application can’t use a compositing window, or if you are moving older QuickDraw code to Quartz, you might need an alternative to obtaining a graphics context from HIView. The QuickDraw functions QDBeginCGContext and QDEndCGContext provide such an alternative. The function QDBeginCGContext obtains a graphics context for a window port and signals the beginning of Quartz 2D drawing calls. The function QDEndCGContext signals the end of Quartz 2D drawing calls and restores the window port. These functions must be used in pairs.
The code in Listing 2-4 produces the output shown in Figure 2-5. A detailed explanation for each numbered line of code appears following Listing 2-4. Note in the figure that the graphics context provided by QDBeginCGContext uses Quartz coordinates. Unlike previous examples, the code draws to a window, not a view, which is why the drawing starts in the window corner and is not indented as it is in Figure 2-1, Figure 2-3, and Figure 2-4.
Listing 2-4 Code that obtains a graphics context from QDBeginCGContext
void MyDrawInWindow (WindowRef window) |
{ |
CGContextRef myContext; |
SetPortWindowPort (window);// 1 |
QDBeginCGContext (GetWindowPort (window), &myContext);// 2 |
// ********** Your drawing code here ********** // 3 |
CGContextSetRGBFillColor (myContext, 1, 0, 0, 1); |
CGContextFillRect (myContext, CGRectMake (0, 0, 200, 100)); |
CGContextSetRGBFillColor (myContext, 0, 0, 1, .5); |
CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200)); |
CGContextFlush(myContext);// 4 |
QDEndCGContext (GetWindowPort(window), &myContext);// 5 |
} |
Here’s what the code does:
Sets the current graphics port to the window port.
Returns a Quartz graphics context for the supplied color graphics port.
Performs Quartz 2D drawing. You would replace this and the following four lines of code with the Quartz drawing routines appropriate for your application.
Note: You can’t mix Quartz 2D routines with QuickDraw calls. You need to replace QuickDraw with Quartz 2D routines that provide similar functionality. For more information, see Quartz Programming Guide for QuickDraw Developers.
Forces all pending drawing operations in a window graphics context to be rendered immediately to the destination device. Normally you don’t need to call this function. But you must call this function when you obtain a graphics context using the function QDBeginCGContext. As an alternative, you can call the function CGContextSynchronize, which marks a window graphics context for updating.
Ends the Quartz 2D drawing session.
Last updated: 2007-12-11