Editor's note: Apple is moving from QuickDraw to Quartz for the handling of 2D graphics on Mac OS X. In this article, R. Scott Thompson introduces you to the concepts behind Quartz 2D, and explains how to make the transition from QuickDraw.
QuickDraw has been a vital part of the Macintosh operating system since its release in 1984. QuickDraw is not only the basis for the easy-to-use interface that redefined the way people work with computers, but over the years it has also offered a wealth of functionality that application developers use to surprise and delight customers.
As the demands on QuickDraw increased, the architecture has grown and evolved. From its modest beginnings, supporting eight basic colors on computers with black-and-white screens, QuickDraw has grown to support 256 colors and then both 16- and 32-bit color graphics. QuickDraw has also complemented hallmark technologies of the Macintosh platform--technologies like QuickTime and ColorSync. QuickDraw has been a valuable asset to the Macintosh operating system for decades.
As quickly as it has changed, however, QuickDraw has not evolved rapidly enough to incorporate many of the features found in more modern graphics libraries. To extend the graphics reach of Mac OS X, Apple has adopted a new strategy for handling 2D graphics: the Core Graphics system.
Quartz 2D is a key component of the Core Graphics architecture of Mac OS X, and is the modern technology in the system whose functionality most closely matches that of QuickDraw. It implements a drawing model very similar to the one found in the Portable Document Format (PDF) from Adobe Systems, and allows an application to issue drawing commands to an extensive range of graphics devices.
Quartz 2D, however, is not the PDF analog to Display PostScript. While the API designers at Apple have used PDF as a guiding hand in implementing Quartz 2D, the actual Quartz API extends the basic drawing model with additional features like integrated color management and support for transparency and compositing when working with devices that support those kinds of features.
Quartz 2D is just one aspect of the entire Core Graphics system. Another popular facet of Core Graphics, for example, is the Quartz Extreme window compositor. The Quartz Extreme window compositor combines the features of Quartz 2D and other graphics libraries on the system, with the power of advanced graphics hardware, to provide an industry-leading compositing window system that offers a tantalizing glimpse into the future potential of Quartz 2D and other parts of the Core Graphics system.
Because QuickDraw does not share the same growth potential found in Quartz 2D, Apple encourages application developers to begin using Quartz 2D in its place. This is a boon to both Apple and application developers as the rich feature set in Quartz has the potential to provide innovation and performance improvements for the foreseeable future.
Along with its rich feature set, Quartz 2D has a similarly rich field of concepts and abstractions that make up its drawing model. Many of those have no clear counterpart in QuickDraw. This presents a learning curve to QuickDraw programmers that want to use Quartz 2D in their applications.
Unfortunately, there is also a bit of an "unlearning" curve for some developers, as many of the tips and tricks that make sense in the QuickDraw environment are either inefficient or have better alternatives under Quartz 2D. This article introduces some of the models and abstractions that make up Quartz 2D. It draws analogs to similar models in QuickDraw, where they exist, with the goal of providing the QuickDraw programmer with a gentle, but practical introduction to the concepts behind the Quartz 2D API.
Pick up any documentation on QuickDraw, and you're not likely to get far into it before you're presented with a discussion of the QuickDraw coordinate system and how it relates to the pixels on the output device. Pixels play a prominent, fundamental role in the QuickDraw drawing model. Indeed, much of the lore an engineer needs to coax the maximum performance out of QuickDraw centers around the efficient storage and shuffling of pixel-based data.
This leads to a bit of culture shock for many QuickDraw programmers early in their exploration of Quartz 2D, because the treatment of pixels in the Quartz 2D API seems almost an afterthought. Some of the drawing commands that an application issues to Quartz 2D find their way onto devices with pixel buffers very similar to QuickDraw's PixMap.
However, Quartz extends the reach of those same commands to include output media that are not raster-oriented. For example, an application can use the same Quartz 2D drawing commands to render an image to a bitmap display that it uses to incorporate that image into an OpenGL context, place it into a PDF file, or send it to a PostScript printer.
How are Quartz 2D drawings built, then? Well, instead of using pixels, a Quartz 2D application works by covering different areas with virtual paint. An application will use drawing commands to identify an area of the current coordinate system and then explain how it wants Quartz to paint that area. Successive layers of paint are built up, one on top of the other, until the entire drawing is complete.
The power of the drawing model comes from a rich set of primitives for identifying areas of the destination where paint should be applied, and a powerful box of virtual paints to color with. As the application is painting, however, it needs to have some link to the destination device, and this is where the Core Graphics Context (represented by the type
CGContextRef) comes into play. A Core Graphics Context shares a lot of similarities to the QuickDraw
GrafPort, so an exploration involving comparison and contrast may be in order.
The central drawing environment in the QuickDraw API is the Color Graphics Port. An application works with a graphics port through the
CGrafPort structure. A graphics port consists of a finite pixel buffer and the information needed to establish a coordinate system on that buffer. It also stores a number of parameters that affect the drawing commands that are issued to the port. This includes things such as drawing colors, pen patterns, the current clipping region, and similar state information. Through that coordinate system, an application has access the individual pixels of the buffer. This leads to the following characteristics of the QuickDraw coordinate system:
Like the QuickDraw Graphics Port, a Quartz graphics context defines a coordinate system for drawing and maintains a number of important drawing parameters like the drawing color information, clipping information, and other such things. Unlike its QuickDraw counterpart, however, the Quartz graphics context supports drawing to devices that don't have pixels.
Instead, the coordinate system for a Core Graphics context is set up to allow an application the maximum flexibility in specifying areas that it wants to paint. This frees the application from many considerations about how those areas map onto the output device. This level of abstraction grants a certain freedom to the Quartz coordinate system that the QuickDraw coordinate system doesn't enjoy. Compare these features of the Quartz coordinate system with the QuickDraw features above.
The drawing plane defined by the coordinate system of the
CGContext is called user space. One of the challenges to understanding the Quartz 2D imaging model is grasping how drawing commands that are issued in user space will be translated into images on the output device.
When working with QuickDraw coordinates, it is clear that the points (1, 0) and (2, 0) are one pixel apart, since the units of the QuickDraw coordinate axes are pixels. What then are the units of the Core Graphics coordinate axes? For Quartz 2D to maintain its device independence, these units must also be device-independent. For this reason, the unit defined for the coordinate axes in Quartz 2D is the point that is defined to be exactly 1/72 of an inch (or to put it another way, there are 72 points per inch).
Most developers familiar with modern computer systems will recognize the point from its popular usage in specifying the size of text. For example, 64-point text is larger than 18-point text. In fact, the definition of the point unit has a long-standing relationship to typesetting from the time it was done using lead slugs on Gutenberg-style printing presses. A traditional typographic point is slightly more than 1/72 of an inch, but in the imaging model shared by Quartz 2D and PDF, a point is defined to be exactly 1/72 of an inch.
CGContext first connects to a device, a convention of Quartz is that it positions the origin of the coordinate system in the lower left-hand corner of the device and scales the coordinate system so that one unit in user space system is equivalent to one point on the destination device. By way of an example, that means that if the destination is a page in a 300-dpi laser printer, where one dot on the printer is 1/300th of an inch, and a program draws a line that is 72 points long in user space, that same drawing will be represented on the printer as 300 dots. It is possible to change the Quartz context's coordinate system so that one inch of user space no longer equals one inch on the output device, but when the context is first created, the convention is that Quartz sets it up so that 72 points in user space equals one inches worth of device pixels.
There are exceptions to every rule, and with Quartz as we know it today,
CGContexts created on pixel buffers (like the pixel buffer of a window, or a bitmap context for offscreen drawing) are exceptions to the 72-points-equals-one-inch rule. One day Quartz may be able to determine the dot pitch of your monitor and set up its context so that one inch on the monitor matches 72 units of user space. Currently, however, when Quartz creates a graphics context that is attached to a pixel context, the context is set up so that one unit of user space corresponds to one pixel in the output pixel map.
This is an important point to know. What it means is that when Quartz sets up a context for a bitmap destination the units of user space correspond to the units of QuickDraw's coordinate system where one unit in user space represents one pixel on the output device. This relationship can be very handy when moving QuickDraw drawing code to Quartz.
In fact, the HIView system takes advantage of this fact to present HIViews with a coordinate system that very closely matches that of QuickDraw. But a bit of care is prudent because the Quartz coordinate system, unlike the QuickDraw coordinate system, can change in such a way that this relationship no longer holds. The details of transforming the Quartz coordinate system will have to wait for another time. For the moment, however, with the coordinate system in hand, however, let's look a bit more closely about how you use those coordinates to tell Quartz where to paint.
Quartz understands three different categories of drawing primitives: line art, sampled images, and text. At a fundamental level, text drawing is really just a special case of line art, but the proper imaging of text is a complex enough topic that text drawing receives separate treatment in the API.
In order to work with line art, Quartz has the concept of the path. A path is nothing more than a set of infinitely thin lines and curves joined together. Given a path, an application can perform two basic drawing operations. The application can ask Quartz to fill the area inside the path, or it can ask that the computer draw a line that follows the path (this operation is called stroking the path).
These two primitive operations are deceptively flexible, as Quartz offers a rich set of variations to even these simple operations. For example, a Quartz 2D application has broad latitude in selecting the type of "paint" used when drawing: it can change the definitions on what constitutes the "inside" of a path when filling, or it can select different options on how the ends of the stroked path should be drawn.
The fundamental tool used to create paths is the Bézier curve. These curves, named in honor of the famous French CAD pioneer Pierre Bézier, were selected for the imaging model because they offer the artist a good level of flexibility, and they offer the computer elegant mathematics for number crunching. For better or worse, much of the literature on Bézier curves concerns itself with the elegance of the mathematics involved and leaves little room for understanding how to use those curves when drawing. However, the beauty of Bézier curves, and an important reason they are so helpful in computer graphics, is the fact that you don't have to understand the mathematics behind the curves in order to make use of them!
An application specifies the Bézier curves as a series of short curve segments. In the most common case, there are four points in user space that make up each segment. The curve is set up so that, once the numbers are crunched, the path passes through the first point and the last point. The curve is pulled away from a straight line by the two other points in a way that is both mathematically elegant and ultimately intuitive. In order to develop that intuition, QuickDraw programmers who are new to Quartz, and don't care much about the mathematics involved, can get a good sense of how to use Bézier curves by writing a few simple programs and studying the ways that changing the positions of the points in a segment changes the resulting curve.
The term "sampled images" is a fancy label that encompasses the familiar QuickDraw PixMap. The idea is that you have a large array of pixels where the color of each pixel is specified using some combination of color values from a given color space. Each individual set of color values is called a color sample, and the term "sampled images" follows from that.
In the case of QuickDraw images, the colors are usually specified using either an indexed color space, using the port's color map to specify the individual color samples, or as direct samples in the device's RGB color space. While most of the sampled images you are likely to encounter through Quartz come from grayscale or an RGB color spaces, Quartz is not limited to those spaces alone. Quartz can also handle sampled images that use a CMYK-based color space, or specialized ICC color profile spaces as well.
The underlying API makes it easy to use images from many different color spaces; the application can draw a sampled image simply by specifying where in user space that sampled image should appear. As with the primitive line art operations, Quartz provides a wealth of options related to how the image should be drawn at that point. For example, if drawing the image requires that the computer scale the image up or down, a Quartz application can specify an "interpolation quality" to identify how it would like the destination device to extrapolate the extra pixels that must be drawn. If the image uses a different color space than the destination device, the application also has the option of specifying how colors should be mapped from one color space to another. The primitive drawing operation of "draw this image here" becomes a powerful tool in the hands of Quartz 2D.
The text-handling capabilities of Quartz are much lower-level than those found in the more complex systems of ATSUI and the Cocoa text system. Nevertheless, Quartz provides all of the primitive operations necessary for those more complicated environments to draw their text on a broad selection of output devices. The primary difference between working with text through Quartz 2D and drawing text with QuickDraw is the fact that QuickDraw works with text strings, at the character level, while Quartz 2D is primarily a tool for drawing glyphs.
The difference between characters and glyphs is a large enough topic to merit an article of its own. Fundamentally, however, you may think of the difference as the difference between an "A" drawn in the Helvetica font versus an "A" drawn in the Times font. Both of these represent the exact same character, the letter A, but the glyphs, the actual pictures, used to represent that character are very different.
In the days of QuickDraw, the act of converting a string of characters into glyphs was very straightforward. With Unicode, multi-directional international text, and complex and beautiful typography, that mapping is now much more complex. Quartz 2D does provide a rudimentary primitive for converting characters to glyphs, but for the most part, Quartz leaves the complex typography to ATSUI or Cocoa text. Instead, Quartz concerns itself primarily with knowing which font it should pull glyphs from, and where an application would like to draw those glyphs in user space. The power of fine control given by the Quartz 2D coordinate system combines with the richness of the color capabilities of Quartz to allow precise typography across the full range of Quartz-compatible devices.
Line art, sampled images, and text are the primitive drawing operations in Quartz, but Quartz offers other drawing commands that, for the most part, combine those primitives in various ways. One such mechanism that will be familiar to QuickDraw programmers is the concept of the clipping path. Just as a QuickDraw port contains a clipping region, the Quartz Context has a clipping path.
The same drawing primitives that construct line art can be used to create a mask, or clipping path, for the drawing commands issued in a given context. Any drawing that an application does in the context gets clipped to the inside of the current clipping path. Unlike a QuickDraw region, a pixel-based structure, the Quartz clipping path can draw upon the full power of the floating-point user space coordinate system to provide smoothly clipped edges.
Quartz supplements the drawing primitives with a few higher-level drawing systems that an application can take advantage of. The
CGShading drawing system offers a way to create smoothly varying color gradients that maintain their integrity on a broad range of output devices.
CGPattern allows an application to efficiently repeat a series of Quartz drawing commands in a grid-like pattern in a way that will be very familiar to QuickDraw programmers who use pixel patterns. The Quartz 2D contains routines for transparent layering of graphics, efficient drop shadows (in come contexts), and the other drawing primitives that give Mac OS X its refreshing appearance.
QuickDraw has reached the limits of its evolution, and Apple has deprecated that technology in favor of Quartz 2D for future versions of Mac OS X. Since they try to serve similar goals, QuickDraw and Quartz are similar in many ways.
However, their differences can be strange and exciting for the uninitiated. The transition from QuickDraw to Quartz has both learning curves and "unlearning curves." While both graphics APIs share the concept of a graphics context, the coordinate systems used by each are very different. Along with the differences in the coordinate systems, there are vast differences in the drawing models provided by each API. Quartz uses an abstract coordinate system, very precise drawing primitives, and rich color handling to provide a powerful graphics API that works across a wide variety of devices.
Through the Quartz Extreme technology that pairs drawing contexts with graphics hardware, Quartz has plenty of headroom to grow in features and performance in the future. The time to start thinking about how to move your QuickDraw code to Quartz has arrived. I hope that this article has given you some of the basics needed to make that transition just a little easier.
For more information on moving QuickDraw to Quartz 2D, you can read Apple's "Introduction to Transitioning to Quartz 2D" documentation.
For more information on the Quartz 2D API, visit the Quartz 2D Reference.
A wealth of information about Bézier curves and their mathematics is available on the Web. If you are looking for interesting books about the mathematics of Bézier curves and other B-splines I might recommend:
Scott Thompson is a professional, contract software engineer with a long-standing interest in both computer graphics and their mathematics.
Return to MacDevCenter.com.
Copyright © 2009 O'Reilly Media, Inc.