Now that the functionality that the CGContext provides is clear, it is now time to look into where CGContexts come from. The way your application gets its hands on a CGContext changes depending on what kind of device it wants to draw onto. The following section examines different techniques for obtaining CGContexts for different drawing surfaces. The code samples presented may not be complete drawing examples, but hopefully you can use them as templates within your own code. QuickDraw PortsQuickDraw has been deprecated and should not be used as the basis for any new code. Nevertheless, as legacy applications make the transition to Mac OS X, they often have to mix Quartz and QuickDraw graphics in the same QuickDraw port. This technique can also be very valuable for incorporating Quartz 2D graphics into QuickTime movies on versions of Mac OS X prior to Mac OS X 10.4 where a QuickDraw port is the only way to get graphics into QuickTime. Unfortunately, Quartz 2D does not support ports that use color table indexes as pixel values. Nor does it support black and white QuickDraw ports. The upshot is that Quartz 2D cannot draw to ports that use 1, 2, 4, or 8 bits per pixel. It does support ports that use 16 and 32 bits per pixel. When creating a CGContext on a 32-bit offscreen GWorld, Quartz will ignore the high order byte of each pixel (the byte usually reserved for the alpha channel). If you need to create an offscreen drawing environment that honors the alpha channel, you should use a CGBitmapContext. The fundamental routines for mixing Quartz 2D and QuickDraw are QDBeginCGContext and QDEndCGContext. Your application should use these routines as a pair. QDBeginCGContext will return a context that your application can use to draw into the QuickDraw port. After you are done using the context, you can return it to the operating system with QDEndCGContext.
There are some caveats to using QDBeginCGContext/QDEndCGContext. We've already mentioned that these routines can only be used on contexts with certain pixel formats. Also when you first obtain a context on a given port, that context is set to the default state of all CGContexts. In particular the context will not share any of the drawing state of the underlying CGrafPort. The computer does not transfer any of the port's graphics state to the context. The two represent completely independent drawing environments that happen to share the same memory space. Finally, the computer diables QuickDraw commands for the port inbetween the QDBeginCGContext and QDEndCGContext calls. If you need to interleave QuickDraw drawing and Quartz drawing, you can call QDBeginCGContext and QDEndCGContext on a port multiple times. The system caches the context associated with the port between subsequent calls. While you are free to call these routines as many times as necessary, you should not nest calls for the same port. That means that after calling QDBeginCGContext, you must call QDEndCGContext before calling QDBeginCGContext again. There are two other utility routines for working with the CGContexts associated with a QuickDraw port. These routines are part of QuickDraw (found in the QuickDraw.h header) and not part of the CGContext interface. Your application can only use these routines with a CGContext it has attached to a QuickDraw port. The first utility routine is SyncCGContextOriginWithPort. The purpose of this routine is to match the transformation of the CGContext to the transformation of the QuickDraw port. It does this by replacing the current transform of the CGContext with a new transformation that puts the origin of the CGContext in the lower left corner of the port. The coordinate axes of the context reorient to their default directions with the x axis pointing to the right and the y axis pointing upward. The scale of the context changes so one point in user space is equivalent to one pixel in the graphics port. SyncCGContextOriginWithPort also takes the transformation of the port's origin into account. If the port's origin setting is offset to the right by 30 pixels and down by 10 pixels (a port origin of (-30, -10)), the CGContext's origin will also reset to a position to the right and downward of its normal location.
The second utility routine for working with QuickDraw ports is ClipCGContextToRegion. This routine replaces the clipping path in the CGContext with a path that traces the outline of a region from the port. An application usually uses this routine to replicate the QuickDraw port's clipping region on the CGContext.
Remember that CGContexts, in general, don't really understand the concept of pixels. Regions are, by their very nature, pixel-based entities. It follows that ClipCGContextToRegion does not really transfer the region to the context. Instead, it creates a path that traces the outside of the region and uses the interior of that path as the clipping area of the context.
Listing 4.1 ties many of these concepts together into a single routine. The code in the listing is from a Carbon application that is slowly adopting more modern Mac OS X technologies. The routine is a handler for the [kEventClassWindow, kEventWindowDrawContent] Carbon event. A more modern application would not use this event. Instead, it would use a compositing window and would draw all the window's content using HIViews and Quartz. The intrepid developer of this code is trying to make the transition in stages and is looking to update their drawing code first. Listing 4.1. Draw Content Handler that Mixes Quartz and QuickDraw
After retrieving the window from the event and the port from the window, the code draws some QuickDraw graphics. After the QuickDraw commands, the code uses the QDBeginCGContext routine to obtain a CGContext. This context passes to both ClipCGContextToRegion and SyncCGContextOriginWithPort. This code sets up the coordinate system and clipping of the CGContext environment so they closely relate to the QuickDraw environment. From that point until the call to QDEndCGContext, QuickDraw has been disabled for the port, but the code can use Core Graphics to draw into the same memory. The routine could continue to use QuickDraw after the call to QDEndCGContext and could use additional QDBeginCGContext/QDEndCGContext pairs to interleave Quartz and QuickDraw graphics. HIView ContextsHIView is the modern replacement for the classic Macintosh Control Manager. The technology incorporates a number of improvements over the older Control Manager. Of particular interest to us here, however, is the mechanism that HIViews use to draw their custom contents. The HIView system supports a well-defined control compositing model with built-in support for Quartz 2D. You can draw HIViews using QuickDraw, but Apple recommends that applications use Quartz 2D for HIView graphics as well. Getting a CGContext for an HIView is an easy task. When the system wants you to draw your HIView, it will send you the [kEventClassControl, kEventControlDraw] Carbon event. The CGContext you should use to draw your control is stored in the kEventParamCGContextRef parameter of that event. Listing 4.2 shows how an application can extract a CGContextRef from a kEventControlDraw Carbon event. Listing 4.2. Obtaining a CGContext for an HIView
Notice that the code carefully checks to see if it can retrieve the CGContextRef from the event. Your code may, or may not, need to take this step depending on its needs. The kEventParamCGContextRef is a relatively new addition to the kEventControlDraw Carbon event. It was first introduced on Mac OS X 10.2 and is only be available for controls that are drawn in compositing windows. If you want to use Quartz 2D to draw controls on older operating systems, you can still use the QDBeginCGContext/QDEndCGContext routines discussed in the QuickDraw Ports section. Another interesting fact to notice about this code is that it does not dispose of the context returned by GetEventParameter. You do not own the context that you retrieve from the Carbon event, so you should not dispose of it. What's more, once you are outside of the draw event handler, the computer may need to change the characteristics of the context, or even destroy it, without your knowledge. For this reason, your code should not cache the context it retrieves from the event and try to use it outside of the handler. It would also be unwise to assume that your HIView code is always drawing to the display screen. Routines like HIViewCreateOffscreenImage may direct the drawing code to a device other than the screen. HIView is a bit unusual in the way it sets up the coordinate system when it passes a CGContext. The HIView system was designed as a replacement for the classic Control Manager. The Control Manager, in turn, was well integrated with QuickDraw and QuickDraw-style coordinates in particular for much of its functionality. To help HIViews and Control Manager Controls to coexist on the window, the HIView system also bases its coordinate system on the upper left corner of the window, or view. Before the system passes a CGContext for an HIView, it sets up the coordinate system for that view so that it has a QuickDraw-like orientation. The origin of the context is set to the upper left corner of the view. The y axis points downward, and the x axis extends to the right (the orientation illustrated in the left half of Figure 4.1). This leads to a bit of difficulty with Quartz 2D routines that draw images or text. Figure 4.2 demonstrates the effect of drawing text and images with Quartz 2D while the coordinate axes are in the default HIView orientation. In the figure, the image of the Roses was drawn using CGContextDrawImage. When Quartz draws the image, the bottom-to-top axis of the image is oriented up the y axis. Because the HIView hands you a coordinate system where the y axis points downward, the image draws upside down! A similar observation can be made about the way that Quartz 2D draws text. Figure 4.2. Effect of HIView Coordinates on Images and Text
In the case of drawing images, the HIViewDrawCGImage routine can correct this problem. It accepts as parameters the CGContext you want to draw into, the CGImage you want to draw, and an HIRect that tells Quartz where to draw the image. The routine scales the image to fit into the rectangle and adjusts the coordinate system, for the duration of the call, to ensure that the image draws the right way up. Unfortunately, in the case of drawing text, there is not a simple, one-routine solution. Carbon applications have a couple of text drawing technologies available to them. ATSUI is the fundamental technology behind most of them. A full discussion of ATSUI is outside of the scope of this book. Chapter 11, "Drawing Text with Quartz 2D," discusses techniques for drawing text with Quartz in more depth. One technique to solve the problem is to simply return the coordinate axes to their default Quartz 2D orientation. Coordinate system changes are examined in more detail in Chapter 5. In short, the origin of the coordinate needs to be moved from the upper left corner to the lower left. This is done by translating the origin along the y axis by the height of the view. Once the origin is in place, we need to reflect the coordinate system so the y axis points upward. Scaling the coordinate system with a negative number also flips the orientation of the axes. The code to accomplish this is given in Listing 4.3. Listing 4.3. Code to Flip the HIView Coordinate System
To restore the coordinate system you could execute the routine again; however, you should undo coordinate system changes by saving and restoring the graphics state around them. This prevents small round off errors from accumulating and causing inaccurate transformations. NSView ContextsCocoa includes quite a few utility classes that allow your application to draw using Core Graphics without having to use the Core Graphics API directly. The frameworks of Cocoa include NSBezierPath for creating line art, NSImage for drawing images, NSTextView (and related classes) for drawing text, and NSAffineTransform for handling coordinate transformations. In some cases, however, you may want to obtain, through the code, a CGGraphicsContext for a view and make direct calls into Core Graphics. To obtain a CGGraphicsContext inside the drawRect: method of a view, you can use the NSGraphicsContext class and the graphicsPort method. This is demonstrated in Listing 4.4. Listing 4.4. Obtaining a CGContext Inside of an NSView's Draw Method
As with the case of obtaining context from an HIView, your code should not try to hold on to the graphics context it obtains in this way outside of the bounds of the drawRect: method. Also you cannot make assumptions about the device the context draws on. You should not, for example, assume that the context is a bitmap context and typecast it as such. Cocoa uses the drawRect: method to implement features like printing or the dataWithPDFInsideRect: method call. You could be called with a PDF context or a printing context if the computer invokes features like these on your view. When you obtain a context inside of a drawRect: method, you cannot make any assumptions about how the context is initially oriented relative to the device coordinate system. Through routines like setBoundsOrigin:, setBoundsRotation:, and scaleUnitSquareToSize:, the NSView interface allows an application to transform the drawing space of the view independently of its frame. When the view is asked to draw, these transformations are incorporated into the CTM of the context. PDF ContextsA hallmark of the Quartz 2D imaging system is its support for the PDF file format. In addition to being able to draw many PDF files, Quartz uses the PDF file format as a metafile for storing drawings. Your application can use Quartz to record a series of drawing commands and play them back later.
It's important to realize, however, that Quartz is not a generic tool for creating arbitrary PDF files. The PDF file format includes a broad set of features that are not directly supported by Quartz 2D. To put it another way, Quartz 2D metafiles are PDF files, but not all PDF files are Quartz 2D metafiles. To create PDF data from your drawings, you have to create a context using the either the routine CGPDFContextCreate or CGPDFContextCreateWithURL. After you have created the context, you can draw into the context just as you would into any other context. The computer will record the drawing commands you send to the context and write a PDF file that can reproduce those drawings. You provide the routine CGPDFContextCreateWithURL with a file URL. Quartz will try to create a PDF file on disk at that URL. The routine CGPDFContextCreate, in contrast, accepts an object known as a CGDataConsumer. A data consumer holds a number of callbacks that pass PDF data to your application as Quartz generates it. By implementing your own callbacks, your application is free to redirect the PDF data to any location you choose. We explore data consumers and their role in exporting image files, including PDFs from Quartz 2D in subsequent chapters. We also discuss the options and features that Quartz 2D provides for creating PDF files in Chapter 14, "Working with PDF." Listing 4.5 provides a short preview and demonstrates one way to create a PDF context that writes a PDF file on disk. Listing 4.5. Creating a Context for Drawing a PDF File
The code begins by defining the size of the drawing area for the PDF document. All of the drawing that goes into the PDF must occur on a page. While a PDF file can contain pages of different sizes, the drawing area of the document serves as the default page size. In this case, our PDF file is going to use 8.5x11 inch pages, so we define a rectangle with those dimensions (remember one point is of an inch). We create the PDF context with that rectangle. Then, for each page, the code uses the routines CGContextBeginPage and CGContextEndPage to bracket the drawing commands for each page. After drawing all the pages, the code calls CGContextRelease to finalize the PDF file and dispose of the context.
Printing ContextsQuartz 2D is as effective at drawing to printers as it is at drawing to other graphics devices. Your application need only create a graphics context for the printer and draw into it as it would any other graphics context. Printing in Cocoa applications is integrated into the view system. To use Quartz to draw in a Cocoa application, you can obtain a graphics context inside of the drawRect: method and draw into that context. For more information on printing in Cocoa applications visit the Apple documentation at http://developer.apple.com/documentation/Cocoa/Conceptual/Printing/index.html Printing in Carbon applications is a bit more involved. When a Carbon application prints, it has the option of using Quartz 2D or QuickDraw to create graphics on the printer. Fortunately, Quartz 2D is the default printing mechanism, so an application doesn't have to go through as many steps to use Quartz 2D as it might have to go through to use QuickDraw. When your application is within its printing loop and within the context of a page, you can obtain a CGContext for a printer by calling the print manager routine PMSessionGetGraphicsContext. This routine has the ability to return either a QuickDraw Port or a Core Graphics port. Your application sets up a printing session using the routine PMSessionSetDocumentFormatGeneration. However, given that Quartz 2D is the default printing path for Carbon applications, you will not usually have to use that routine. The code fragment in Listing 4.6 outlines the process for printing a page using the Carbon printing services and Quartz 2D. Listing 4.6. Obtaining a Printing Context During Carbon Printing
This routine is only one part of the Carbon printing loop. Prior to calling this routine, your application should have accomplished a number of tasks. The focus of this routine is the portion inside of the PMSessionBeginPage and PMSessionEndPage calls. After beginning a page, the code asks for a Core Graphics context. The PMSessionGetGraphicsContext routine returns a generic, pointer-sized value so a little typecasting is used to disguise the pointer value. Next, you ask the printing system to provide the rectangle of the page and convert it to a CGRect that you can pass on to the drawing routine. After drawing the contents of the page you end printer's page and exit the routine. Note that after leaving the end page routine, the context may not point to a valid object any longer. For more infomation on the Carbon Printing Manager, visit Apple's documentation at http://developer.apple.com/documentation/Carbon/Conceptual/CPM_Concepts/cpm_chap1/chapter_1_section_1.html Bitmap ContextsIf your Quartz 2D application needs to draw off-screen, it has a couple of options. First is to explore the ability to create a graphics context on an off-screen bitmap. The context you create will be able to draw Quartz graphics into an off-screen pixel buffer that your application owns. That off-screen buffer can be in any of a number of pixel formats that Quartz 2D supports including grayscale, alpha channel only, and floating point pixel types. With all this flexibility, there are a number of steps involved in creating a bitmap context. We take the opportunity to explore the options available in creating bitmaps in the chapter on off-screen drawing. Listing 4.7, however, offers many of the basic steps involved in creating an offscreen bitmap. Listing 4.7. Creating a Simple Offscreen Bitmap
Listing 4.7 begins by defining some of the desired context characteristics. The off-screen is going to be 128x128 pixels and use 32-bit ARGB pixels. To tell Quartz 2D about the color space for the off-screen, you also have to create a color space. In this case, the generic RGB color space is used, created by calling CGColorSpaceCreateWithName. To end the preliminaries, allocate a memory buffer large enough to hold the pixels Quartz will draw on. This routine uses the malloc system routine to allocate a buffer just large enough to hold all of the pixels. With the preliminaries out of the way, you can create the actual CGBitmapContext instance using CGBitmapContextCreate. This routine can be a bit intimidating simply by virtue of the large number of arguments it accepts. Most of these simply describe aspects of the pixel map you want to draw on and much of this information is taken from the setup that the routine has already done. This call also tells the system to set the pixels to 8 bits per component and to use the first byte of each pixel for the alpha channel. From there Listing 4.7 goes on to use the specified context and then cleans up after itself and returns. This concludes our brief look at many of the different ways an application can obtain a CGContext. Many of the graphics attributes that are common to all CGContexts regardless of their origin have been explored. The important thing to note about CGContexts and their state variables is that most of the attributes in a context are found in all CGContexts, regardless of what kind of device they are connected to. In most cases, this simple fact allows your application the freedom to ignore exactly what type of graphics device it is drawing to and concentrate, instead, on what it would like to draw. |