MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


A Simple Drawing Sample in Quartz 2D

by Scott Thompson
11/02/2004

The previous article in this series examined some of the basic differences between the Quartz 2D and QuickDraw drawing models. It also covered a few of the fundamental ideas that make up the Quartz 2D drawing model, like the virtual coordinate space and the painting algorithm.

This article uses a bit of sample code to point out how some of those ideas actually look when used inside an application. Along the way we'll explore the creation of CGContexts, look at the painting algorithm in more depth, talk about how paths are constructed, see an example of how Quartz 2D handles colors, and look an some examples of how the Quartz 2D coordinate system can be manipulated to make drawing easier.

Sample Code--Drawing a Texas Flag

Long ago my family hosted a foreign exchange student from Denmark. He arrived not long after my family had purchased our first computer, an Apple II+. I had done some BASIC programming and was playing with code that drew low-resolution graphics on the computer. The exchange student was also interested in computers, and we took the time to figure out how to get the computer to draw the Danish flag. Once we had succeeded (the country's flag is not terribly complicated), we dragged out an atlas that had pictures of lots of different flags in it and tried to see how many of those we could replicate.

Our code sample for this article builds upon that fine tradition by drawing the flag of the state of Texas using Quartz 2D. The Texas flag was chosen for two reasons. First, it is pretty easy to draw. As you'll see, in spite of its simplicity it also lets us explore quite a few different areas of the Quartz 2D drawing system. The second reason, of course, is that this article was written in Texas. For the morbidly curious, and in order to make the drawing accurate, I actually looked up the standards for the Texas flag as spelled out in chapter 3100 of the Texas Government Code. You can see a picture of the flag and find information about it, its dimensions, its colors, as well as other interesting tidbits within those laws at the About Texas web site.

In drawing the flag, we're going to demonstrate a few of the common things that any program might do to draw with Quartz 2D:

  1. Obtain the CGContextRef we will draw in.
  2. Mark off areas of the coordinate plane we want to color using paths.
  3. Ask Quartz 2D to fill those areas with color.
  4. Use coordinate transformations to simplify things where appropriate.

Related Reading

Mac OS X Panther Hacks
100 Industrial Strength Tips & Tools
By Rael Dornfest, James Duncan Davidson

A Brief Digression on API Issues

Before we get into the details of the sample code, it may be worth taking a moment to stop and talk about some API issues in Core Graphics and Quartz 2D in particular. The routines that make up Quartz 2D, like the rest of the Core Graphics routines, are found in the Core Graphics framework. The Core Graphics framework, in turn, is found inside the Application Services umbrella framework. An application will usually link to the Application Services framework alongside Carbon. (If you don't know what a framework is, I strongly recommend you explore the System Overview at Apple's web site. Don't stop at just the section on frameworks, either. The entire book is full of valuable information.)

The constants, routines, and other code elements that make up the Core Graphics incorporate the two-letter prefix CG into their name (for example, CGContextRef, CGContextFillRect, CGImageCreate, and so on), which should make them easier to spot.

The Core Graphics API, like many modern APIs, categorizes its calls in terms of objects and has rules for managing the lifetime of those objects. In particular, any routine that creates a Core Graphics object--for example, CGBitmapContextCreate--will have a corresponding routine for releasing that object back to the system: in this instance, CGContextRelease. Our sample won't be creating any Core Graphics objects directly, and we won't have to concern ourselves too much with the life cycle of the objects we work with. The object ownership idioms are important nevertheless, and you can find more information on the topic in Chapter 2 of Drawing With Quartz 2D.

The object-oriented nature of the API has implications beyond memory management. In QuickDraw, the operating system maintains a reference to a current GrafPort, and any drawing commands are executed using that port. In contrast, Core Graphics issues commands to a particular instance of a some object when your application calls a C API. The first argument of most of those routines, of course, is the object to which the command is sent. For example, the first argument to routines that talk to a CGContext is a CGContextRef. However, you may be wondering: if we don't create the CGContext we interact with, where does it come from?

Getting a CGContext

The CGContextRef represents the object that is the link between your application and a graphics device. In that respect, it is a rough analog to the QuickDraw CGrafPtr. Your application can obtain different types of CGContexts from different sources depending on the type of device those contexts communicate with. Since they all descend from the same root class, however, they will all accept the same drawing commands and do their best to translate those commands into something meaningful on the destination device. The following table summarizes some of the most popular ways your application might receive, or create, an CGContext:

HIViewUsing Carbon Events, you can attach a handler for the kEventControlDraw event. Inside that handler you can get a CGContext by asking for the kEventParamCGContextRef parameter of the event.
QuickDraw PortsThe routine QDBeginCGContext and QDEndCGContext gives you a graphics context for a QuickDraw graphics port. While you can use this capability to mix Quartz 2D and QuickDraw drawings, one important caveat is that you should not use QuickDraw drawing commands between the QDBeginCGContext and QDEndCGContext calls.
Carbon PrintingThe routine PMSessionGetGraphicsContext, when called with the constant kPMGraphicsContextCoreGraphics, gives you a graphics context for drawing on a printed page. Often a QuickDraw print loop obtains a graphics port after opening a print document and reuses that port for each page. For Quartz 2D you usually obtain a new graphics port for each page, inside of a PMBeginPage and PMEndPage pair.
Offscreen DrawingOne way to get an offscreen graphics context is to attach a CGContext to an offscreen GWorld (see QuickDraw Ports above). If you want to avoid QuickDraw altogether, however, you can get a CGContext for offscreen drawing from CGBitmapContextCreate and related routines. Another handy technique is to use CGBitmapContextCreate in conjunction with the QuickTime routine QTNewGWorldFromPtr to create a QuickDraw-compatible port and a CGContext on a block of memory that you allocate yourself.
MetafilesJust as PICT is the metafile format for QuickDraw, PDF is the metafile format for recording Quartz 2D commands. You can create a CGContext for a Quartz 2D PDF metafile by using CGPDFContextCreate. There are also routines for creating PDF contexts inside a file.
NSViewThe CGContext for the currently focused view can be obtained from [[NSGraphicsContext currentContext] graphicsPort]. Cocoa provides a number of utility classes like NSBezierPath, NSImage, NSTextLayout, and NSAffineTransform that may preclude you from having to work directly with a CGContextRef. However, should you ever need one, you can get one from NSGraphicsContext.

For our code sample, we are going to be using an HIView. We're going to override the kEventControlDraw event and get a CGContext from that event. Since the CGContext has a coordinate system of infinite extent, we're going to use the bounds of the HIView to limit the size of our drawing. Other than that--and one other concession we have to make because we're using an HIView, which we'll get to in a moment--there is nothing specific to HIViews in our drawing code. The same code could just as easily draw our flag in an NSView, send it to the page of a PDF file, or print it.

The small concession that we must make, which is specific to the way the system passes an HIView's graphics context to an application, is a feature designed to make the transition of QuickDraw programmers easier. Recall from the previous discussion that Quartz 2D can orient the coordinate axes of a CGContext arbitrarily with respect to the coordinate axes of the destination device. A common tactic when drawing with Quartz 2D is to use this fact to make drawing easier. The HIView system does just that.

In most cases, when the system passes a CGContext to your application, the origin of that context's coordinate system coincides with in the lower-left corner of the device's coordinate system. From that starting point, the convention is to have the positive y-axis extend up the left side of the device and the positive x-axis point off to the right (Figure 1a). Compare this with the default QuickDraw coordinate system, which has the origin in the upper-left corner of the device and the y-axis extending downward (Figure 1b):

Image of default Quartz 2D axis orientation Image of default QuickDraw axis orientation
Figure 1a. Default Quartz 2D axes Figure 1b. Default QuickDraw axes

Apple created the HIView system as a more powerful replacement for the classic Control Manager, and it wants to make the transition from the Control Manager to HIView as simple as possible. To make it easier to incorporate legacy QuickDraw drawing code into a view, HIViews follow a convention in which the CGContext that the operating system passes to your application orients its coordinate axes the same way the QuickDraw coordinate system does. Recall from our prior discussion that for bitmapped devices, the system scales the CGContext's initial coordinate system so that one unit on the CGContext's coordinate axis covers the same distance as one pixel in the bitmap. That means that by default, the operating system passes an HIView a CGContext whose coordinate system matches the QuickDraw coordinate system!

By and large, this setup is advantageous for programmers trying to reimplement custom QuickDraw controls using the HIView system. Depending on the needs of the HIView in question, however, this unconventional arrangement of the axes can lead to unexpected behavior. One of the most frequently encountered problems crops up when an application tries to draw an image, or ATSUI text, in an HIView using Quartz 2D. The application will obtain a CGImageRef from some source and then call CGContextDrawImage to draw that image.

Quartz 2D draws the image oriented to the coordinate system such that "up" on the image is the same direction as the positive y-axis (Figure 2a). In the default HIView coordinate system, however, the positive y-axis travels down the window. That means that images (or text) drawn through Core Graphics wind up on the screen upside down relative to the window (Figure 2b). In the case of images, the Human Interface Toolbox provides a convenience routine to handle this common error directly. The HIViewDrawCGImage routine will reorient the coordinate axes, draw the image, and return the coordinate axes to their previous state, all in one routine call.

Quartz 2D axes with right-side-up image QuickDraw axes with upside-down image
Figure 2a. Image drawn with axes in Quartz 2D orientation Figure 2b. Image drawn with axes in QuickDraw orientation

For this sake of this sample, we will try to insulate our drawing code from the peculiarities of HIView as much as we are able. As part of achieving that goal, we will write our drawing routines so that they assume the coordinates are set up in the conventional Quartz 2D orientation. To do that, one of the first steps we will take after getting the CGContext for our HIView is to rearrange the coordinate system to suit us. That involves moving the origin to the lower-left corner of the view and flipping the y-axis around so that it points upward on the window. The code that the sample uses to accomplish this is:

// Reorient the coordinate system so that the 
// origin is in the lower left corner of
// the view and the y axis points "up" on the 
// window.
CGContextSaveGState(contextToDraw);

CGContextTranslateCTM(
	contextToDraw, 0, 
	flagViewBounds.size.height);
CGContextScaleCTM(contextToDraw, 1.0, -1.0);

... Drawing commands here ...
	 
CGContextRestoreGState(contextToDraw);

Listing 1. Reorienting an HIView's coordinate system

You could undo this transformation simply by repeating the translation and scale commands (a fact you can verify yourself by applying logic similar to that in the discussion above). However, if your transformation is complex, undoing it can be difficult and prone to error. Listing 1 demonstrates a much better technique. The routine calls CGContextSaveGState and CGContextRestoreGState form a pair. As you may remember, both the CGContext and the QuickDraw GrafPort maintain a certain amount of information about how to interpret drawing commands.

For example, a QuickDraw port has a foreground color, a background color, a clipping region, a pen size, and a several other fields in its graphics state. Quartz 2D graphics contexts have similar state items such as the stroke color, the fill color, and the text drawing mode. One of the most popular fields in a CGContext's graphics state is the Current Transformation Matrix, or CTM. The CTM is that part of the drawing state responsible for keeping track of the way that the context's coordinate system maps to the device's coordinate system. From the API perspective we can think of coordinate transformations as translations, scales, and rotations, but Quartz 2D represents all of its coordinate transformations as 3-by-3 matrices and combines and applies them using matrix mathematics.

Quartz 2D will let you get at the matrices themselves, but the specifics of that math don't concern us yet. It may nevertheless be helpful to realize that when you issue calls like CGContextTranslateCTM, you're not actually moving the origin; rather, you are changing the value of some numbers in the CTM. This means that the coordinate transformation routines are fairly inexpensive operations.

What the CGContextSaveGState routine does, in essence, is to push the fields of the current graphics state onto a stack. From that point, your code can change the graphics state freely to accomplish whatever drawing tasks it may need to perform. When you call CGContextRestoreGState, Quartz 2D pops all the fields it had saved to the stack and restores them to their previous values. By placing our CTM calls inside the scope of the save and restore pair, we insulate other code that doesn't need those changes. We call CGContextSaveGState to save the current CTM (and therefore the current origin and y-axis directions). We then change the coordinate system to suit our needs, and call our drawing code. Once we're done we politely restore the CTM back the way we found it for any drawing code that follows.

The CGContextTranslateCTM routine moves the origin to the lower-left corner of the view. To get from the top left to the bottom left, we have to move the origin down the window. Recall that at the time we call this routine, the positive y-axis is already pointing down the window, so the new location we want for the origin is flagViewBounds.size.height units away along the positive y-axis. We don't want to move the axis horizontally, so the x-coordinate of our translation is 0.

The CGContextScaleCTM command we use here is not very intuitive at first. Normally you would use CGContextScaleCTM to change the scale of the Quartz 2D coordinate axes from their initial scale of 1 unit as 1 point (or, for bitmap contexts, 1 unit as 1 pixel). If you wanted your drawing to be twice the size (2X, or 200 percent), for example, you might use the command:

CTContextScaleCTM(contextToDraw, 2.0, 2.0);

and from that point on, every unit you specify in the context's coordinate system would cover twice the distance on the device that it did before. In our code, we're using a scale factor of 1.0 to the x-axis. Scaling an axis by 1X (or 100 percent) actually means that you're keeping everything exactly the same that it was before. Oddly, however, when scaling the y-coordinate axis, the code uses a scale factor of -1.0. The net effect of this strange scale factor is that the current coordinate system gets "flipped" across the x-axis. During the flip, the positive and negative y-axes simply trade directions without otherwise being affected.

A common mistake people sometimes make when using this trick is scale the x-axis by 0 instead of 1.0. Scaling an axis by 0 doesn't really make much sense, but the system is happy to try to do it for you. In scaling, if you want the x-axis to remain as it is, you use 1.0. In translations, to keep the origin at the same x position, use 0.

Drawing the Flag

Now that we've got the coordinate system arranged to our liking, we can turn to actually drawing the flag. Unlike QuickDraw, where most drawing commands simply replace the pixels in a buffer with new values, Quartz 2D uses a painting algorithm. In order to draw on the context, an application identifies an area of the context that it wants to color, then has Quartz 2D apply virtual paint to that area by either filling it or stroking it.

When working with paint that is opaque, the drawing model is not very different from QuickDraw's drawing model; each painting operation will replace whatever had been drawn to the context beneath it. Unlike QuickDraw, however, Quartz 2D maintains transparency information for its paint alongside the color information. The transparency is carried in an additional color component called the alpha channel.

This formal support makes it easier to achieve transparency effects in Quartz 2D than through QuickDraw. In Quartz 2D, you can actually paint with translucent paint. For this drawing sample, we'll leave transparency out of the equation. All our paints will be opaque. When we specify colors for our paint, however, we'll have to specify a value for the alpha channel as well. The alpha channel value that represents opaque paint is 1.0.

The first thing we will draw is the three rectangular stripes that make up the background. For each stripe, we will select a paint color, outline a region of the context, and then tell Quartz 2D to fill the region we have selected. Just as in QuickDraw, Quartz 2D will allow us to specify colors using the red, green, and blue components of each color. Unlike QuickDraw, however, RGB colors are not our only option. In Quartz 2D you can specify colors in different ways depending on the color space that the colors come from.

Color spaces play a vital role in color management, the science of making sure the color you want your users to see is the color they will see. Some color spaces use red, green, and blue components to identify a color. As an example of an alternative color space, printing code might choose to use the subtractive primitive colors cyan, magenta, yellow, and black to specify colors in a CMYK color space. There are also grayscale color spaces, indexed color spaces, and a number of others as well.

Quartz 2D incorporates a lot of the color management capabilities of ColorSync directly into its drawing model. This offers midlevel and professional high-end graphics applications plenty of flexibility where they need complicated color handling. Other applications, like ours, will satisfy themselves with allowing Quartz 2D to handle common color management tasks behind the scenes. Our sample code specifies its colors in a color space known as Device RGB. Basically this means that we will specify colors using three RGB numbers (plus a fourth for the alpha channel). Because it is a "device" space, Quartz 2D will pass the component values through to the destination device and allow it to interpret the component values as a color.

With the colors in hand, we need to outline our rectangles and tell the system to fill them. You may recall that to specify an area for Quartz 2D to draw, an application constructs a path. A path is a series of line segments or curves that define an area to be filled or a track to be stroked. You can also use a path to define a clipping area for subsequent drawing commands. Our stripes are simple rectangles that we want to fill with solid colors. We could go through the trouble of constructing paths for each rectangle using line segments. Because rectangles are a pretty basic shape, however, Quartz 2D provides a convenience routine that make filling rectangles simple. Here is the drawing code that sends the blue stripe of the flag to the drawing context:

// add the blue vertical stripe
CGRect blueRect = flagBounds;
blueRect.size.width = 
	(flagBounds.size.width / 3.0);
	
// According to the Texas State Flag Code, the 
// blue stripe should be Pantone Matching System 
// color number 281 (dark blue). One web site 
// translated that to RGB (0, 40, 104).
CGContextSetRGBFillColor(inContext, 
	0, 40.0 / 255.0, 104.0 / 255.0, 1.0);

CGContextFillRect(inContext, blueRect);

Listing 2. Drawing the blue stripe

QuickDraw maintains separate colors for the background and foreground. Quartz 2D, on the other hand, doesn't have the concept of background and foreground colors. If you want to draw a background, you simply draw it first and then layer the foreground items on top of it. Quartz 2D does follow the PDF convention of maintaining separate fill and stroke colors as part of its current drawing state. CGContextSetRGBFillColor is a convenient routine for changing the current fill color using color components in the Device RGB color space.

Note we have to supply a fourth component, 1.0 for the alpha channel, to make our paint opaque. Notice as well that Quartz 2D color components are specified using floating-point numbers in the range 0 to 1.0. We use an (inefficient) division to convert the 0 to 255 color coordinates from a web site to the appropriate range.

In addition to drawing state, the CGContext also maintains a current path. Normally to fill an area of the context, we would use Quartz 2D commands to outline some part of the coordinate system with the current path and then call CGContextFillPath. Since we're only drawing a rectangle, we can accomplish both tasks with CGContextFillRect. If you look through the routines available in CGContext.h, you will find a number of these convenience APIs for handling simple shapes like line segments and rectangles. For example, CGContextFillRects is useful for filling a large number of rectangles at once, and the routines CGContextStrokeWith and CGContextStrokeRectWithWidth both outline rectangles.

Two drawing primitives that QuickDraw programmers will miss are primitives for drawing ovals and rounded rectangles. It isn't difficult for an application to create paths for those shapes, but they aren't quite as easy to produce in Quartz 2D as they are in QuickDraw.

One point may be worth mentioning for the sake of anyone who might be translating PostScript code, or PostScript PICT comments, into Quartz 2D drawing calls. The stroke and fill operations in both PostScript and PDF will consume the current path. PostScript considers the current path to be part of the graphics state. When you want to apply both a stroke and fill to a particular path in PostScript, a common idiom is to construct the path, save the graphics state, issue a fill command that consumes the current path, restore the graphics state and the path along with it, and then stroke the path. Quartz 2D follows the PDF convention that says the current path is not saved and restored as part of the graphics state. In compensation, however, the Quartz 2D graphics state provides separate fill and stroke colors and complements them with a routine that can both fill and stroke the current path in one operation.

The code draws the red and white stripes in pretty much the same way the blue stripe is drawn. Only the rectangles change, so there is little point in examining that code here. This leaves the famous lone star to contend with. For such a simple design element, we'll encounter quite a bit of Quartz 2D knowledge in drawing the star.

Drawing the Star

To create the star, we'll follow the same basic steps we've been through before. The star is a more complex figure than the rectangles we've worked with so far. We're going to select an algorithm to construct a star-shaped path into the current path of our context. From there it's a simple matter of using CGContextFillPath to fill that path with white. We could easily construct the path so that it works just for our Texas flag. The path would be created in place over the blue field and at just the right size for this particular drawing.

In order to make our star-drawing routine a bit more generic, however, and to blatantly modify the sample so that it demonstrates more Quartz 2D functionality, we will eschew the obvious course and take a less direct approach. Our sample code will contain code to create a generic star path. With that done, it will show how changing the coordinate transformations before calling that routine will result in a star that is precisely positioned for our Texas flag. As an added bonus, the resulting routine will be general enough that some other application could use the path routine to draw other stars--say, the 50 stars of the U.S. flag. The code that creates the star path would be the same. All that would have to change is the coordinate manipulation code used to set up the coordinate system before it is called.

To draw our generic star path, we need five points that are evenly spaced around a circle. We can connect every other point in that set with lines, and the resulting figure is a five-pointed star. The sample code uses a little bit of trigonometry, in a loop, to simultaneously give us the points we need and generate the segments that connect them together. Don't let the math scare you; we could just as easily have taken our points from a geometry book and stored them in an array. The point is not to grasp the math behind the routine, but rather to understand the way the points are used to construct a path.

void DrawStar(CGContextRef inContext)
{
    CGPoint starPoints[5];
    const float starAngle = 2.0 * pi / 5.0;

    // Begin a new path (any previous path is 
    // discarded)
    CGContextBeginPath(inContext);
	
    // The point (1,0) is equivalent to 
    // (cos(0), sin(0))
    CGContextMoveToPoint(inContext, 1, 0);	

    // nextPointIndex is used to find every 
    // other point
    short nextPointIndex = 2;	
    for(short pointCounter = 1; 
              pointCounter < 5; 
              pointCounter++) {

        CGContextAddLineToPoint(inContext, 
		cos( nextPointIndex * starAngle ), 
		sin( nextPointIndex * starAngle ));
        nextPointIndex = (nextPointIndex + 2) % 5;

    }

    CGContextClosePath(inContext);

    CGContextFillPath(inContext);
}

Listing 3. The star's path-construction routine

A representation of the path this code creates is shown in Figure 3. The numbers in the figure indicate the order in which the points are added to the path. The call to CGContextBeginPath simply lets the context know that it can forget anything it has in its current path. It's not specifically needed in this case, but clearing the current path before you begin a new one is one of those habits that can save you headaches in more complicated code. The CGContextMoveToPoint routine begins constructing a new current path by setting the location of its first point. From here the loop adds a series of line segments to the path using CGContextAddLineToPoint. The code only draws four segments, and then closes the path with a call to CGContextClosePath.

When an application closes a path, it lets the computer know that the path should make a complete circuit. At the time Quartz 2D receives the close instruction, if the first and last points of the path do not match, the computer will add a final segment from the last point to the first point. This is exactly what we want, so we do that with our star code. A closed path is a bit different from just a path with the same staring and ending points. It is possible to create a path where the first and last points are coincident but the path is not closed. This has important implications when stroking the path, which we will have to leave until later.

Figure showing how the star path is constructed
Figure 3. Diagram of the star path

The path we have created is a lovely star shape, but it has a couple of issues in its current form that make it unsuitable for our flag. One of the most obvious problems, evident in Figure 3, is the direction that the star path points. On the Texas flag the apex of one arm of the star points straight up, while our star path doesn't have an arm pointing straight up. Less obvious from the diagram is the fact that the path we have created is centered around the origin. Our origin is the lower-left corner of the view, but we want the star to draw at the center of our blue field.

Finally, Figure 3 is a greatly enlarged representation of our path. The actual path we've created is tiny, with a radius of only one point. If we printed it on a high-resolution laser printer, it would be about 1/36 inch in diameter. That's on the order of the size of the period at the end of a sentence. Because we're drawing the screen's pixel map, that translates to few pixels at most. That's much too small for the flag of a state as big as Texas.

Earlier we looked at how coordinate transformations could simplify our drawing task. As it turns out, we can use coordinate transformations to make our path construction routine work in this case as well. To make the star larger, we can scale the coordinate system before creating its path. With the scaling factor in place, Quartz 2D will magnify every unit we cover in our star's path to several units (aka pixels) on the destination device. In a handy quirk of fate, the radius of our path just happens to be 1.0. That means all we need to do is decide what radius we would like our star to have, then scale the coordinate system by that amount to get a star path of the correct size.

Strange how that worked out so nicely, isn't it? The standards for the Texas flag tell us the star should be in a circle whose diameter is three-fourths of the width of the blue field. We're working with the radius of the star, which is half of a diameter, so we'll scale our star so that the radius is three-eighths of the width of the blue field:

// The star should be in a circle whose diameter 
// is 3/4 the width of the blue field.
float starRadius = 
		(blueRect.size.width * 3.0) / 8.0;	

...
CGContextScaleCTM(inContext, 
	starRadius, 
	starRadius);

...
DrawStar(inContext);

Listing 4. Scaling the coordinate system to make the star bigger

This works well and makes the star path as large as we need it to be. But scaling doesn't change the fact that the path is still centered on the origin, not the blue field. However, we've already seen that we can use CGContextTranslateCTM to relocate the origin to wherever we like. We can use that routine to move the origin to the center of the blue field before we draw our path. With that setup, the routine will still construct the star around the origin, but the origin will be at the center of the blue field. Consider, however, that we need to combine this translation with the scale transformation we've already decided to use. Some care is in order when combining transformations like this.

If you scale the coordinate system first and follow it with a translation, the computer will magnify your translation distances by the scaling factor. In the simple case, therefore, if we want to perform our translation with unscaled axes, we need to apply the translation before scaling the axes. In general, reversing the order of any two transformations has the potential to lead to vastly different results. This phenomenon leads to a popular malady in 3-D graphics programming called the dreaded black screen. The term comes from the black screen that is shown when 3-D graphics transformations are applied incorrectly, moving the objects in the scene outside the visible area. With Quartz 2D you are more likely to see a white screen than a black one, but the underlying cause is the same. If you can't see your drawing, double-check to ensure that your transformations are applied in the proper order.

With the translation in place, code looks like this:

// The star should be in a circle whose diameter 
// is 3/4 the width of the blue field.
float starRadius = 
        (blueRect.size.width * 3.0) / 8.0;	

...
CGContextTranslateCTM(inContext, 
	CGRectGetMidX(blueRect), 
	CGRectGetMidY(blueRect));

CGContextScaleCTM(inContext, 
	starRadius, 
	starRadius);

...
DrawStar(inContext);

Listing 5. Adding a translation to move the star into position

At this point, our star is drawing in the correct place and is of the correct size, but it's still not pointing the right way. By now you can probably guess that what we'll do is rotate the coordinate system. You can ask Quartz 2D to rotate the coordinate system about the origin by any angle. When the axes are set up in the default orientation, positive angles turn the axes counterclockwise and negative angles turn the axes clockwise (if the coordinate axes are in QuickDraw's orientation, the directions are reversed). Just as translations depend on the scale of the coordinate system, rotations depend on the position of the origin. In this case we want to get the origin into place (by performing our translation) before we turn the axes. Rotations are not really affected by scale factors, so it doesn't matter in which order we have the scale and rotate transformations.

The sample code adds the rotation after the scale. We need to rotate so that any of our star's points are turned upward. A convenient one to use is the point that is bisected by the x-axis. To make that point turn upward, we can rotate the coordinate system 90 degrees counterclockwise so that the x-axis points upward. We need to rotate the coordinate system by 90 degrees counterclockwise. Rotation angles in Quartz 2D, however, are specified in radians, not degrees. To convert degrees to radians we simply multiply the number of degrees by (pi / 180) radians per degree. That means our rotation will be 90 * (pi / 180) radians per degree or simply (pi / 2) radians. The final code looks like this:

// The star should be in a circle whose diameter 
// is 3/4 the width of the blue field.
float starRadius = 
        (blueRect.size.width * 3.0) / 8.0;	

// Rearrange the coordinate system for the 
// DrawStar routine then call it to draw the star.
CGContextSaveGState(inContext);
CGContextTranslateCTM(inContext, 
	CGRectGetMidX(blueRect),
	CGRectGetMidY(blueRect));
	
CGContextScaleCTM(inContext, 
	starRadius, 
	starRadius);
	
CGContextRotateCTM(inContext, pi / 2);

CGContextSetRGBFillColor(inContext, 
	1.0, 1.0, 1.0, 1.0);

DrawStar(inContext);
	
CGContextRestoreGState(inContext);

Listing 6. Adding a rotation

Once again, we wrap our transformation code in a CGContextSaveGState and CGContextRestoreGState pair. Our changes are encapsulated and less likely to affect surrounding code, should we ever add any. We've also added the code that sets the current fill color to white. Our DrawStar routine uses CGContextFillPath, which means that the system will use a technique called the nonzero winding rule to determine which pixels are inside the star.

A winding rule in this case is simply the technique the computer uses to decide which pixels are inside the path and therefore need to be painted with the current fill color. The path we've created for the star is self-intersecting and can be used to demonstrate other options. If you want to see one, change the CGContextFillPath call to CGContextEOFillPath. That tells Quartz 2D to fill the path using the even/odd winding rule. Apple provides at least two explanations of these winding rules at its web site. One can be found under "Filling a Path" in Chapter 4 of Drawing With Quartz 2D. Another is part of the discussion of the NSBezierPath class from Cocoa. You can find this at a page titled "Bezier Paths." In addition, most modern graphics textbooks have descriptions of these winding rules in the sections on filling polygons or paths.

A final note, as mentioned briefly before: when you issue a drawing command to paint the current path (usually either CGContextFillPath or CGContextStrokePath), the drawing operation will consume the current path and leave you with a clean slate. Our star drawing code re-creates the path each time, but if we were going to draw a lot of stars (say, the 50 stars on the U.S. flag), it might be more efficient to compute the path once and reuse it repeatedly.

To give you a glimpse at how we might accomplish this, our star code could create its path in a CGPath object and reuse the resulting CGPathRef to draw as many stars as necessary. Using CGPath to draw multiple stars is left as an exercise for you.

Final Thoughts

This concludes a simple sample of the way an application builds Quartz 2D graphics. We learned how Quartz 2D images are drawn from the background to the foreground by layering painted shapes. We saw how an application creates a shape by constructing a path in the drawing context. Once it has created a path, the code asks Quartz 2D to either fill the interior of the shape or outline the path with a stroke.

We looked at some convenience routines for working with simple shapes like rectangles, and created a more complex shape from line segments. We explored some of the basic tools that Quartz 2D gives you for selecting colors. We also looked at ways to use coordinate transforms to simplify these drawing tasks. In spite of how far we've come, however, we've examined only basic shapes, straight lines, and fills. We haven't created a stroke or even played with Bézier curves yet. There's still much to learn, but the little information we've covered so far provides fertile ground for further exploration.

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.