Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2006-211
$eventId
ID of event: wwdc2006
$eventContentId
ID of session without event part: 211
$eventShortId
Shortened ID of event: wwdc06
$year
Year of session: 2006
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC06 • Session 211

Programming with Quartz

Graphics and Media • 1:09:32

Quartz is the powerful 2D graphics imaging library in Mac OS X that lets you draw vector-based graphics, images, and PDF in amazingly flexible ways. This session introduces the Quartz drawing model and the fundamental concepts needed to take advantage of its extensive capabilities. Whether you work with Carbon or Cocoa, you'll see how to use Quartz to create, stroke, and fill shapes, apply transformations, use color spaces, use alpha blending, and more.

Speakers: David Gelphman, Bunny Laden

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it may have transcription errors.

Okay, well, the titles on the slides show only one aspect of our lives. For the over past year now, David and I have been more than a utility infielder and a word wrangler. We've been leading secret lives. In fact, we've been book authors. Today's session... Thank you. Thank you.

Everything that we talk about today will be in the book that we released earlier this year, Programming with Quartz. In fact, the book contains a lot more than what we're going to talk about today. It's a comprehensive guide to Quartz and it contains a lot of code examples, all of which are available for download. But I'll tell you a lot more about the book later on.

Is that okay? I'm going to start off the session and David's going to return later. In this session you'll learn what Quartz is. You'll also learn the fundamental concepts that are necessary to use the Quartz 2D API effectively. But most of the session, David and I will show you how to use the Quartz 2D API to accomplish these tasks. All of the code that you see today is in the Quartz 2D basic sample application that's It's available for download on the WWDC site for this session.

We'll wrap up the session by showing you some of the advanced capabilities of Quartz. And then we'll conclude by giving you some tips for how you can get started as a Quartz programmer. Thank you. Now, Quartz has many facets. It's really a catch-all term that's used to describe most of the graphics technologies that are available in Mac OS X. In the beginning of Mac OS X, Quartz referred to the windowing system and the 2D drawing engine. The Quartz 2D API at that time supported 2D drawing and display management. But with each release of the operating system, more and more capabilities have been added to Quartz. For example, the image handling capabilities have grown with each release, and today it's almost trivial to read an image data and to export it out.

With Tiger, in addition to the advanced image handling capabilities, a number of other Quartz technologies were introduced, including Core Image, which is the image processing API, Core Video, which is an API that allows you to get frame-level access to video, and Quartz Composer, which brings together a number of Mac OS X technologies in a graphical programming environment that you can use to create motion graphics, and PDFKit, which supports opening, reviewing and searching PDF documents in an application.

And with Leopard, you've heard about two other Quartz technologies. Core animation that allows you to render layers and animate those layers so that you can create front-row style applications. An image kit which supports user interface elements for image views so that your users can view and edit images and also get access to core image filters and to use them. Today's talk, however, focuses solely on Quartz 2D.

So let me go over a few of the features. Device independence. This means that when you draw in Quartz, you don't have to think of the device that you're drawing to. Quartz takes care of the details for you. Quartz will map coordinates, match colors, and if necessary, convert pixel formats and do anything else that's necessary to convert your drawing to the device. Quartz is resolution independent. It's coordinate spaces in units, not pixels. When you draw, Quartz makes sure that your drawing output optimal resolution for the device that you're drawing to. Quartz is high performance in that it leverages the graphics hardware whenever it can.

It has fully anti-alias graphics and text. It supports alpha transparency, which you'll see more about in a moment. And it has built-in PDF support. PDF, in fact, is a metafile format for Quartz. And because of that, Quartz drawing can be fully captured in PDF without the need to downsample or to compress. And this is really important too. Quartz is the gateway to using other graphics technologies. Quartz images, for example, can be used in core image. And core image and other technologies use quartz colors.

So where is Quartz 2D? First of all, I'd like to see how many of you are new to the Mac platform or to WWDC. Okay, so as you go on this week, you may see here that Core Graphics is used as a synonym for Quartz 2D. And that's because the Quartz 2D API is largely declared in the Core Graphics framework. The image handling capabilities are declared in the ImageIO framework. And the diagram here that you see gives you an idea of where Quartz sits in relation to the other graphics technologies in Mac OS X.

Just about any software that's built for Mac OS X can use Quartz. The Cocoa Drawing API is built directly on Quartz 2D. In some cases, Cocoa developers will want to call the Quartz API directly, either to get access to features that might not be exposed in the Cocoa Drawing API, or to gain finer grain control over some of the features that are needed for your program.

Now, Carbon is the procedural C API for Mac OS X, and Quartz 2D is procedural as well. In fact, Quartz 2D is the API to use in Carbon applications if you want to draw. You'll find in C++ developers will find that it's really easy to use this API. But your application doesn't have to have an interface for you to use Quartz. You can create Unix tools and scripts that create dynamic web pages, process PDF, and do other scriptable tasks that don't require a UI. And the same for Python scripters. You can use Python to improve workflow with a number of graphics tasks. And the Python API to Quartz is defined in developers, examples, Quartz, Python, if you'd like more information about that.

So before I move on and start to show you any code, I'd like to go over some of the fundamental concepts that you really need to know to use Quartz 2D. The Quartz2D API is organized around opaque data types. And there's a number of opaque data types, so I've chosen to show you only a few here today. Each opaque data type in Quartz represents an object.

For example, the CG image ref data type represents a quartz image. CG color ref represents a color object. And the CG path ref data type represents a path object. Now you'll see how to use a number of these opaque data types and more today as we go on.

Now because Quartz has its object-like flavor, its API is designed with a number of functions that create, access, modify, use, and more importantly, reuse Quartz objects. So here I've put some examples of some of the functions that are in the Quartz 2D API. And as you can see, it's a pretty well-designed API. The function names are pretty straightforward. You can pretty much guess what the functions do. And the CG prefix refers to core graphics.

Well, with any API that uses objects, there are going to be some rules for memory management, and they're pretty simple. If you create an object in Quartz, you own it. Owners must release all objects that they create when they no longer need them. If you don't own an object, don't release it. But if you want to keep an object around that you don't own, you need to retain it. Quartz provides functions for many of the opaque data types that will retain and release that specific type, as you can see for images. But because quartz opaque data types are derived directly from core foundation CF type data types, you can also use the function CF retain and CF release. Thank you. The quartz has a very flexible coordinate system. The quartz coordinates are floating point values. And the origin is at the lower left. X values increase from left to right. And Y values increase from bottom to top.

Quartz provides a number of functions that allow you to operate on coordinate space. Here I've listed three of the simplest functions that you can use. As you can see, you can change the scale of the coordinates space. You can turn or rotate the coordinate space. You can move or translate the origin.

For example, you can scale the space. Okay, let's take a look at the default again. You can also concatenate several operations together. So for example, you can flip the coordinate space by translating and scaling x negatively. Okay, let's go back and look at the default. If you look at the text on the woman's shirt and the stain on her sleeve, you'll see that indeed it's been flipped, and that the origin is now at the lower right, and X values now go increase from right to left. You can also skew the space. And by the way, if you want to know why the woman is screaming, you'll have to read our book. The answer is in there.

Now transforming space is very powerful because it allows you to operate on the space rather than having to manipulate object coordinates. So for example, if you want to make an object bigger and draw it in a different part of the space, it's much easier to transform the space. Now for example, you can do really easy zooming that way.

Now Quartz 2D has a number of graphics primitives as you might expect in any 2D drawing library. And as you might expect, Quartz supports line art drawing. You can create paths in Quartz, and paths are made up of lines and curves. And the paths can be open, closed, simple, complex. You can draw just about any arbitrary shape that you want.

Quartz supports a number of image formats as well. It supports JPEG, PNG, TIFF, GIF, camera raw formats that can contain multiple images. You can create thumbnails and you can load images incrementally. Quartz provides a number of functions for you to manipulate images, for you to read an image data and export it, and even to create image masks.

And because PDF is the metafile format for Quartz, as you might expect, the PDF is the first class graphic primitive too. You can just as easily create PDF content as to draw it. how Quartz has a number of low-level text functions. Now, these text functions are used primarily by higher-level frameworks. In this talk today, we're not going to tell you anything more about Quartz text functions. In most cases, applications either need more sophisticated text layout and rendering than what Quartz offers, or they need something really simple, like a text field. So, if you need to support text in your application, I invite you to look at one of the other Mac OS X APIs, such as Cocoa Drawing or Core Text.

Okay, now quartz uses a painter's model. The painter's model is nothing new. It's been around for hundreds of years, used by such people as Rembrandt. Now, just as in Rembrandt's time, in order to, and if you have paint that you want to apply to the canvas like Rembrandt did in our digital model here, if you want to change any paint that's on the virtual page, the only way that you can do it is to apply more paint on top of it. So let's see how that works.

Here we have a gradient. We have an image of a rather nasty looking cat. And we have some text. We have our blank canvas. And we first draw the gradient. then the text, then the cat, you'll see that the drawing order really matters, and we have a result that isn't quite as pleasing as if we change the drawing order and draw our gradient, our cat, and our text. So as you can see, you can create rather sophisticated images in Quartz using a small number of very powerful primitives.

Now quartz also supports alpha transparency. Every color component in quartz has an alpha component attached to it. And the alpha component of a color, the value can range from 0 to 1. 1 means that that color is completely opaque, and 0 means that it's completely transparent. So let's see how this works. I have my canvas. I draw the image of my screaming woman. And on top of it, I'm going to paint a red rectangle.

And the alpha value of that red color is 1. It's completely opaque, so you cannot see the image underneath. But if I change the alpha value of the red color to 0.5, you'll see that the image underneath shows through. And finally, if I draw that rectangle using a red color with an alpha value of zero, it's as if I didn't draw the rectangle at all.

Quartz also has the notion of global alpha. And global alpha is applied to all the colors that are drawn. So if the alpha value is 1, that means that everything that's drawn will be completely opaque. Nothing is changed here. So we can see in our image that our image here looks just like it did on the previous slides that I showed you.

But if we change the global alpha, as we decrease the global alpha, that image will dim until finally when the value is zero, you won't be able to see the image at all. It's as if I didn't draw it. Now when you combine the notion of alpha transparency with the notion of the painter's model, you'll see that what we really have in quartz is a watercolor artist painter's model. So you might think of someone like Winslow Homer instead of Rembrandt.

Now, color management is the process of ensuring consistent color across peripheral devices, such as displays or printers, and across operating system platforms. Now, quartz, fortunately, has built-in color management. But let's take a look at what a color-managed picture looks like compared to one that's not. As you can see, the color-managed picture, the colors exactly match what's in the top image. But in the one that's not color-managed, there are striking differences. Some of them you can look at as the color of the life vests, but the water, the raft, there are some striking color differences there. But fortunately in quartz, if you use the correct color functions, which David will show you how to do later, you do not have to worry about this. You will get color management for free.

Now, Quartz, one set of drawing commands will draw to any destination. Your drawing commands are independent of the destination. All you'd have to do is specify your drawing and tell Quartz what destination that you want to draw to. Whether it's a window of view, a printer, a PDF or a bitmap, the drawing commands are the same. So how do you specify to Quartz which destination that you want to draw to?

You do that with a graphics context, which is represented by the CG context rep data type. A quartz graphics context is an abstract drawing destination. There are several flavors of them. There are graphics context for on-screen drawing, off-screen drawing, printing, PDF documents. So I'd like to show you how to create some on-screen drawing context, and later on David's going to show you how to create some of the other flavors of drawing context. So let's start with Cocoa first.

So, Cocoa programmers need to obtain a Quartz graphics context in the drawRect method for the view that they want to draw to. The first thing they need to do is to call the current context method of the NSGraphicsContext class to obtain the Cocoa graphics context associated with the current thread.

Now, that's a Cocoa graphics context. Quartz is at a much lower level, so you need to extract the lower level graphics context out of the Cocoa graphics context, and you do that by calling the graphics port method. After you have the quartz graphics context, you can then pass that to the appropriate quartz drawing functions. That's all there is to it, it's pretty easy.

Now for Carbon developers, it's just as easy. First you install an event handler on the HIVO object that you want to draw to. Then in your drawing event handler, you call the Carbon event manager function, get event parameter, to extract the quartz graphics context. And then you pass that context to the appropriate quartz drawing functions. Now the one caveat here is that the window that's associated with the HIVO that you're drawing to must have compositing turned on. But compositing is on by default, so just make sure that you don't turn it off.

Quartz maintains a graphic state, and that state contains a lot of parameters that control exactly how drawing's done. I've listed only a few of the parameters here, such as stroke color, fill color, and any coordinate space transformations that you apply to the coordinate space. Now, the other thing about Quartz's graphic state is that it's stack-based. And that means that you can save a snapshot of the current state by pushing it onto the graphic stack. And you can push several snapshots of the graphic state on if you want. And you can return to previous states by popping states from the graphics stack. Now, the only way that you can change parameters, well, there's two ways that you can actually have parameters change. One is to set the value of a parameter, and that parameter will stay set to that value until you either set it to a different value, or until you restore a graphic state to a previous value. So let's see how working with a graphic stack works.

works here. Here I have my current graphic state and in it I have only two parameters: the stroke color and the fill color. Now if I call the Quartz function CGContextSaveGState, what that does is it makes a copy of the current state and pushes it onto the stack, and that copy now becomes my current state. Now any operations I do in changing the parameters associated with that state, such as the fill color, operate on the current state. I can go ahead and do drawing and my fill color will be green. If I want to revert back to a previous state, I simply call the function CG context restore G state and that pops the state off the stack and I refer to the previous one. So graphic state management, it's that easy. Okay, enough talking about courts, it's time to take a look at some code.

and I'm going to start with filling and stroking a rectangle. So here I put five of the convenience functions that Quartz offers for drawing shapes. What I want you to notice about this is that in each of these functions, the first parameter is a graphics context. The second parameter is the shape that you want to draw. And for the case of the ellipse, the rectangle defines the major and minor axis of the ellipse. Now one of the functions takes a third parameter, CGContextFillRects, and that's a function that will fill many rectangles. The third parameter indicates to Quartz how many rectangles you're passing it to fill. So let's take a look at a routine. I'm going to step through this line by line so you can understand exactly what's going on.

The first thing I'm going to do is to create a rectangle. This is just an abstract rectangle. No drawing will be done. I'll call the function CG RectMake, and I'm going to provide the origin of that rectangle and the width and height. So here I've specified x equals 10, y equals 10, and the width has 130 units, and the height is 100 units.

Next I want to set my fill color by providing a color to quartz, the new fill color, because by default the fill color is black and it's not going to show up very well here. So I'm providing a blue color, and I'm getting that color from a function that David wrote, my get blue color, and David will show you how to write this function later. It's going to provide me a color managed blue color. So, so far no drawing's been done yet. Now I'll call a function that will actually fill the rectangle, and this is my result. I don't know. Of course, the axes aren't drawn. I put them there just to remind you that the origin of the rectangle is at x equals 10, y equals 10.

Okay, let's stroke that same rectangle. So now I need to set a stroke color, and I'm gonna provide a green color, again, using another function that David's written. And then I'm actually gonna call a function that will stroke the rectangle, providing it a width of 10 units. And this is my result. So here's where I'd like to take a little detour to show you exactly how Quartz does stroking.

So a path is an abstract concept. It's infinitely thin. When you ask quartz to stroke a path, it applies paint such that the paint straddles the path. So in the case of my rectangular path, the stroke is 10 units wide. Five units of the paint will be on one side of the path, and five units will be on the other. Now this has implications for how the results come out, because drawing order will matter, as you'll see. Now in the second part of this routine, I'm going to do just the opposite of what I've done. First, I'm going to stroke the rectangle, then I'm going to fill it so that you can see the difference in drawing order. The first thing I'll do in the second part here is to save the graphic state. Why? Because next, I'm going to translate the origin 200 units in the X direction. So that's what I'll have. Now, when I start drawing, it's going to be with respect to that new origin. My stroke and fill colors are already set, so I'm just going to go right ahead and call my stroking function, and this is what I'll get. then I'll call the filling function and that's what I'll get. Now you can compare the two. You'll see in the case of the one on the left that the stroke partially obscures the filled rectangle, whereas on the right the filled rectangle partially obscures the stroke and that has to do with how strokes are accomplished in courts.

Okay, now finally I'm going to restore my state, my graphic state, and that will push the origin back over to where it was to begin with. So if I want to do any other drawing, it's been reset that way. Okay, with that I'd like to bring David up, and he's going to show you a lot more code, starting with images.

Thanks, Bunny. And the microphone works, great. I'm used to being a utility infielder and coming off the bench. So here I am. And we're going to talk about drawing an image from a file. Bunny's already talked about some of the capabilities of Quartz for drawing images. And I want to show you how easy it is to do.

So these are the functions we're going to use in our code to draw an image. Function here, cgImageSource, create with URL is a function to create an image source object that provides a way to get at image data that may be on disk or in some other manner. This function is to get at data that's on disk. image source, we can create from that image source a CG image object, a CG image ref. CG image objects are what Quartz uses to represent an individual image. And once we have an image, we can draw it with CG Context Draw Image.

So let's take a look at some code that does that. This is just going to read an image from a URL and draw it to the context that's passed into the function. First thing we do is we create an image source from that URL. We have a quartz object that we've created. The next line of code is to create an image from that image source. Now, image sources can contain a wide variety of types of data. For example, JPEG, PNG, the data formats that Bunny's already talked about. There's a lot of image sources that are supported. Some of those image sources can contain more than one image in the image source.

So I don't know if you went to the resolution-independent user interface talk earlier this week, but for example, it was recommended that you supply multiple ping-- excuse me, multiple TIFF images in a TIFF file, for example-- The object from the image source, we want to release the image source in this code. We're not going to use it any further, and we created it, so we use it. Thank you. OK, in order to draw the image now, we need to choose a destination that is a location that we want to draw the image. And to do that, we create a CG Rect structure that describes the destination that we want to draw.

We're specifying the lower left corner of the image of the rectangle that is the location we want to draw. And we're going to specify the width and height of the destination by the width and height that we make our rectangle. And in this case, we're using CG Image Get Width and CG Image height to get from our CG image object its native width and height.

OK, we haven't done any drawing. Now we're going to call the function CGContextDrawImage to draw that image to the context donation with the rectangle that we've specified and the image object that we want to draw. Once we draw that, the image will appear. OK, we're all done doing our drawing. And since we created a CG image object, we need to release it. That's it. Any of the formats that Quartz supports natively, you can draw using this kind of code.

Now, of course, there's other kinds of images that you might want to draw and other strategies that you may need to use to draw images that have different sources besides, for example, compressed image data on disk. And so there are other functions to allow you to create CG image objects from data that you have. For example, if you know all about the image data itself, and you're going to supply the image data yourself as uncompressed data, you can use CGImageCreate to create a CG image object that you can later draw.

Another function that I'm showing here is CGBitmap Context Create Image. That is, if you've created an image through quartz drawing to a bitmap context-- I'll show you how to do that in just a little bit-- but if you've created a pixel array that contains bits that you want to use as an image, then from that pixel array in a CG context, you can create a CG image object. And then finally, the last one here, this is not a comprehensive list, but another one that's of interest is the ability to create from a CG image object a subimage of that object so you can use that as a smaller than a larger image.

OK, well, Bunny's already talked quite a bit about color, and I'm going to show you some code and talk a little bit in more depth about color. Now, a color in Quartz consists of three things. The first aspect of a color in Quartz is a color space. We'll talk about a color space in more depth in a minute.

In addition to a color space, there are the color components of the color that specify the color in that color space that you're creating. And then thirdly, every color in Quartz has an alpha transparency associated with it. That alpha transparency may be 1.0, that is opaque, but every color does have an alpha value associated with it.

OK, color spaces. Color spaces have a number of aspects to them. The most basic is that they specify a color model, which describes what kind of color you have. For example, a color model of RGB, gray, CMYK, those are all color models, and a color space has a color model associated with it. The color space determines the number of components that are necessary to describe a color in that color space. For example, an RGB color space has three components to specify a color in that color space, R, G, and B. In addition, there's an alpha value that's associated with the color.

And finally, Quartz supports calibrated color spaces. Calibrated color spaces contain a piece of data that tells Quartz how to take a color in that color space and reproduce that color to another drawing destination that is characterized by a different color space. For example, to take a color and reproduce it on one monitor that has one color space or one monitor profile, produce the same color on a different color monitor that has a different color profile, or on a printing device that has a completely different way of characterizing color. So use of color-- of calibrated color spaces is important to get reproducible color in your application or in your course drawing.

OK, so I'm going to talk a little bit here about the different functions that are useful for creating color objects and color spaces, which are color spaces part of a color object. Specifically, creating a color space by name. Quartz has predefined names for some color spaces that are directly available, and we're going to use one of those names to create a color space. And Quartz has the notion of color objects, which is a way of having an object that specifies a specific color. And reuse of that object is the most efficient way to specify the same color repeatedly. So if you have a color that you draw multiple times in your application, you might want to have that represented as a CG color object so that you can reuse it and get the most efficient results. And once you have a color object, you can set the color as Bunny has using the functions to set the fill or stroke color from a color object.

So let's look at some code to first create a color space, and then we'll take a look at some code to create a color object. Now, a color space is something you're very likely to use many times in your application, a specific color space. If you need to use a given color space multiple times, there's no reason to create it every time you need to use it and release it. But instead, as this code does, it's a useful thing to create it once and use that same color space over and over. So this function basically has a static variable to hold the color space. Every time we call my get generic RGB space, we're going to get the same color space that's going to be held in the static variable. The first time the function's called, we're going to create the color space using CG color space create with name. And the name here that we're using is a name that specifies a generic RGB color space, one that basically describes a generic Apple color monitor, but a calibrated monitor. It doesn't correspond to a specific monitor, but a generic color monitor. And this is a useful color space for doing drawing to an arbitrary device if you're drawing with RGB color.

OK, and then of course we return that color space from the function once we've created it. Now Bunny was showing you a number of routines that we wrote to get a specific color, and this is what one of those routines looks like. So again, because color objects are useful when you reuse them, we want to create this color object once and reuse it any time that we need it. So I've made this a static variable. Now, in order to create a color, we need to specify the color components for that color plus an alpha value. So this is RGB color, so the values are R, G, B, and alpha. And in this case, the R and G values are 0.

That is no color value. And the blue and alpha values are 1 or 100%. So this is 100% blue and 100% opaque. So we have our array of floats representing our color. We call CGColorCreate. As the color space characterizing this color, we're using the function that we just looked at on the previous slide and the array of colors that are being passed in. We have our blue color and we return that. So this is just a simple example of for the case where you have a given color used repeatedly in your application, here's how you might write such a function. Most applications use something like white and black a lot, and in our application we use blue a lot, so this is a useful way of setting color.

Now there's other ways of setting color besides using CG color objects. And that's represented by this slide where there we will set the fill color space independently of a particular color. And once we've set the fill color space, we can, by using the floating point components-- excuse me, the float components and passing them in-- to set the color in that color space. And the same with the stroke color. And this is useful if you want to algorithmically set the color, that is, you don't have color objects that you're going to reuse. You just want to set the color to a specific color value that maybe you compute.

OK, so let's do some more drawing and talk about both using alpha and also creating paths and using paths in some more detail. So Bunny talked about the idea of paths as representing shapes in Quartz. And here's some examples of the different styles of paths that you may have-- open paths, closed paths, and paths that are more complex that may have multiple sub-components. It's straightforward to create paths with Quartz. The first thing you do when you're drawing with paths is you begin the path. That basically throws away any path that had already been constructed, and now you've got a fresh path with no points in it. The next thing you do is add primitives or path segments to the path or shapes to the path. So for example, the primitives that you can add are line segments or Bezier or quadratic curves to the path. Those are the path primitives. But in addition, you can add whole shapes to the path, such as rectangles, ovals, and arcs. Now optionally, you may want to close the path. If you do want to close path, you would close the path. And remember that paths are mathematical descriptions or abstract representations of shapes. They don't actually do any painting. Once you've got the path, now you want to paint it with either a fill color or a stroke, or perhaps both.

So this is just a subset of some of the path creation functions where we're creating a path for one time use on the context. Begin the path, add potentially lines, arcs, or cubic bezier segments, and closing the path. And all the functions that I've highlighted here in the yellow color are functions to create the path. The second set of functions here that I've highlighted are once you've created the path, now to apply a painting operation like stroking or filling. Or in the last case, there's a CG Context Draw path where you can specify a path drawing mode that combines some of these. For example, you can fill and stroke in one operation.

Now, those functions I was just showing you are functions where you want to do a one-time operation of creating a path and painting it. But there may be a situation where you want to have a path that you actually reuse, because it's a shape that you're going to draw many times in your program. And so Quartz has an object, a CGPath object, represented by CGPathRef, that you can create that contains a path that you're going to use over and over. The functions that you see here, the first one is to create the path, and the rest of them basically are parallel functions to those that I showed you earlier where you're modifying the path on the context. Now it's to actually add segments and so on to a path object that you are supplying. Once you've created the path object, the way you use that is to use the function CGContextAddPath, which adds the path object that you've created to the path in the context so you can then fill in stroke or whatever you choose.

OK, so I like really simple examples of new concepts. So this is an example for creating a path that is a rectangle. Now, believe me, there's lots easier ways to do it than to manually create the path segments. But this is really just to give you an idea of what path construction or creating a path object is like. So the first thing is CGPath createMutable to get a quartz path object that we can add tagments to. The first thing we do is we establish the initial point on the path.

This point is going to be at the lower left corner of the rectangle that we want to represent. Next, we add a path segment that goes from that corner to the far right corner of the rectangle, merely by specifying the x-coordinate now, the width of the rectangle, away from the original point.

Add the next segment where we now move up by the height of the rectangle, the next segment by moving over to the left by the width of the rectangle again in a negative direction, and finally, we close the path. And when we close the path, that connects the last path point to the initial point on the path, creating a closed path and completing our rectangle. So we haven't drawn anything. We're just creating an object that represents a shape. Now we return that, and we can use it.

OK, so let's have some code that actually does something with this shape that we've worked so hard to create. So here's a little bit of code to do that. The first important line of this code is that we are calling the function I just showed you to create the rectangular path. Now we have an object that we can reuse multiple times. The next interesting line of code here is to set the fill color to a red color, in this case an opaque red color that calls a function similar to what we looked at a moment ago, but in this case, it returns a red color.

OK, now I have a little for loop here to loop over and draw this rectangle multiple times. And the first time we draw it, we're going to set the global alpha value, that is the alpha value that applies to all drawing on the context. We're going to set that to one, or completely opaque. Haven't done any drawing yet. We begin the path on the context, and we add the path object that we created earlier that's the rectangular path. We still haven't done any drawing yet, but we've now got a path in the context that represents a rectangle at the origin that has a width and a height of 130, width of 130 units and a height of 100 units. OK, now we call CGContext fillPath to fill the path that we've created. And now the red rectangle appears.

OK, before we do any other drawing, the last thing in our loop is we're going to call CGContextRotateCTM. That is, we're going to rotate our coordinate system about the origin of coordinates by an angle that we've computed just to get our math right as we do this multiple times. When we rotate the coordinate system, now what you see is the coordinate system rotated here. OK, let's go around our loop.

In the for loop, what we're doing each time through is we're adjusting the tint value, or the global alpha value that we're using, and we're subtracting alpha value from that. So the next time through the loop, the global alpha value is less than 1. We do our begin path. We add the path to the context, our rectangular path, and we fill it. And now what you see is a couple of things. One is, because we've rotated our coordinate system, that same rectangle, that path object that we've added to the context path when we paint it, it's rotated relative to the original coordinate system. But relative to our new coordinate system, it's exactly where you would expect.

OK, the other thing that's interesting is now you can actually see some of the content that's underneath the new rectangle that we painted. And the reason we can see that is because we've changed the global alpha value to a value less than 1. OK, we rotate the coordinate system. We go through the loop. Each time we go through this loop, we rotate the coordinate system. We change the global alpha value in paint. And as you can see, as we paint this series of rectangles, you can see more of the content of the previous drawing each time because we've lowered the alpha value each time we draw. OK, once we're done with our loop and our path drawing, because we created the path, we want to release it.

Okay, so we've seen about using paths for drawing and for filling and potentially for stroking, although in this example it was filling. Now let's talk about clipping to a shape. If you've used graphic systems, you're familiar with the concept of clipping. Clipping defines the-- the clipping area in Quartz defines the area where paint will appear when you perform a drawing operation. Drawing that's inside the clipping area will appear, and drawing that's outside the clipping area will not appear. Now path-based clipping, which is what we're going to look at in code in just a minute, is resolution independent in the same way that path filling and stroking is resolution independent. So you get nice clean edges on your drawing and your clip of what the destination output device is.

And one thing that's a little different that maybe some graphics models you've used in the past is that the clipping model for quartz is an intersection clipping model. That is, you have an existing clipping area. You apply a new clip that's smaller than that. You will get a clip that intersects the original clipping area and the clipping area that you're providing. So it's an intersection rather than a wholesale replacement of the clip.

So these are the functions that are relevant for clipping. The one that gets the most use, of course, is to clip to a rectangle, but you can also clip to an arbitrary path. And that's what we're going to do in our code here. Now I showed you an example before of drawing an image. And this code is exactly the same code for drawing an image, except we've added a little bit of code to change the clipping area before we actually perform the image drawing.

So the highlighted code is the new code. You already know the old code. OK, so let's see what we do in order to clip, in this case, to an ellipse. The first thing we do is we begin the path. That gets rid of any path that was already on the context. Next we call a function cgcontext add ellipse in rect. And what we're doing here is we're creating a path that's an elliptical path. The ellipse has its center at the center of the rectangle that we're supplying. And it has its major and minor axes as the width and height of the rectangle.

OK, we haven't done any drawing yet. We've just created a path. Now we call CGContextClip. That takes the current path in the context and intersects the inside of that path with the clipping area that was already in existence, creating a new clipping area. We still haven't painted anything. We've changed the clipping area.

Now when we draw our image, when the image is rendered to the device or to the output, you see that it's now clipped by the ellipse that we just created. And again, because we're good citizens, we want to release the objects we created. And in this case, we've created an image object that we want to release. So that's it for path-based clipping. Let's talk about other kinds of clipping or masking. In this particular, we're going to talk about image masking.

Now, there's a number of different ways that you can use masks. Masks specify not a painting color, but they specify where to paint or not paint. One-bit masks have the one bit says paint or not paint. For deep masks, deeper than one bit, now you have an alpha value. You say how much paint to apply of the painting color. So we're going to look at painting masks directly in just a minute. Masks are represented, like images, using the CG image ref data type. And you draw them with CG context draw image if you want to draw a mask directly. Now, masks can be used for other things besides just painting them directly. You can use them to mask other images. You can also use them to provide generalized clipping to the other drawing that you're going to do.

Now there's another type of masking that you can do with images, and that is masking out colors of an image when you draw it. And I'll show you a little bit, not code, but I'll show you an example of what that looks like in a minute. OK, so let's look at an example of painting one that masks. The functions that you use to create a mask would be CG Image Mask Create, where you have your mask data that you're supplying to courts to create an image object that represents the mask. And just like I said before, when you draw a CG image object, you use CG Context Draw Image, regardless of whether it's a mask or an image with color. So when you draw a mask, The fill color on the context specifies the painting color that the bits in the mask will be painted. So for example, if we draw a mask and we have a black fill color, when we draw that mask, the bits will be painted with black, that is those bits that should be painted will be painted with black, and the other bits will be completely transparent. If we change the fill color on the context and draw with the mask, now again, the bits that we draw with will be painted with the fill color. And so on. Each time we change the fill color on the context and paint our mask, we get a new color.

And remember that the colors in Quartz have an alpha value associated with them. So if our fill color has an alpha value associated with it and we draw, we'll inherit that alpha value when we paint. So portions of our background which are even covered or partially covered by our mask will now show through.

Now, in addition to painting masks directly, you can use them to mask an image. The function CGImageCreateWithMask allows you to take an image with color and apply a mask to it. To draw an image that's created with this function, again, we use CGContextDrawImage. So take a look at what that might look like. We have an image. We have a mask that we want to apply to the image. When we perform the operation to create a new image that's masked with this and draw it, we will get the image masked by the mask.

Now, mask can also be used as a part of the general clipping area. So if you want to clip drawing that you do, line art, text, PDF drawing, whatever, through a mask, you can do that by using the function CGContextClipToMask, where you pass in a mask to the function, and the clipping area in the context is adjusted by the mask. And we specify a rect here because we want to say how to take the mask and what area of the context we want to map the mask onto, just like when we draw an image. So let's take a look what that looks like. Let's say we were to use this mask as a clipping mask that we're going to apply to the context. And now we've applied that mask to this, and we start drawing a series of colored rectangles through the mask. So as we draw-- These rectangles are masked by our clipping area so that only the portions of those rectangles that fall within the clipping area will display. And because our clipping area is described by a mask, you get this kind of result.

Finally, let's talk about masking with color. You have an image, and you want to mask out or not draw specific colors in your image. You can choose a color or a range of colors that you want to mask out from your image when you draw it. This function CGImageCreate with masking colors, you specify the colors as well as the image, and you get a CG image object back, and you will draw that with CGContextDrawImage.

What does that look like? Well, for example, let's say we have a red background. And now this is our example of the image that we want to paint. We've just drawn it as an opaque image. Now let's say we want to paint this, but we want portions of this image to not actually be rendered.

We want to show the background through those portions. And I'm going to pick the portions that are black through a very dark gray. And we're going to mask out those as painting color. When we draw, those portions of the image that have color that fall within our range will not be painted, and we can see the background through.

OK, so we've talked about masking. Now let's talk about taking your drawing that you've created or you're going to draw, and we're going to talk about some ways of exporting that in other file format, or in a way other than drawing it to a window or another device. We're going to export our graphics as a PDF. It's very straightforward. As Bunny said before, you do the same drawing. The context is key. What context you draw with determines what's going to happen. So we will create a PDF context. That is, drawing to that context will create PDF data that describes the drawing.

This is really useful for either exporting your drawing to a file, or if you want to create a PDF representation of your drawing and put it on the clipboard, this is how you would do it. So these are the two functions for creating a PDF context. The first one here is let's create a context that produces a PDF document. The destination for that document is a URL. The second function here is a function that allows you to create a PDF context where you have a custom destination that you want to supply. Destinations in Quartz in this fashion are described by a data consumer, a Quartz data consumer. And that basically is a set of callbacks that you supply that say where to put the data. So both of these functions share the fact that they take a media box that describes the context that you're trying to create.

Now PDF is inherently a page-oriented format. It has a size associated with the thing that you're going to create. Typically, you're used to documents that represent letter-sized page or something. They represent a sequence of pages, and the pages have dimension to them. So when you create your context, you say what the media box is that describes the context.

So here's some code to take quartz drawing and produce a PDF document at a specified URL. The first line of code here just creates a media rack or media box that we're going to use to describe the PDF context that we want to create. And in this case, I've made one that has a width and height that's 8 and 1/2 by 11 inches. 8 and 1/2 times 72 is the number of points in x, and 11 by 72 is the number of points in Y. That's the height. We create our context, pass in the URL, and a pointer to our media rect. Now we have a Quartz drawing destination that represents our PDF context.

Now, as I said, PDF documents are inherently page documents, page-oriented documents, where there's a page one, a page two, and so on. So in order to specify that we want our drawing to now appear on the first page, we call CGContextBeginPage. Now, the pages in a PDF document can each have different media recs associated with them. In this case, we're going to use the same media rec for the first page as we do for the document itself.

Once we said we're starting the drawing for page one, we call our function my_do_drawing for page one, pass in the PDF context, and now that drawing will be associated with page one. In that page, if we had more pages, we would begin that page. We would draw the contents for the second page. We would end that page, and so on, for all the additional pages in our document that we want to create. And once we're done, we created a PDF context. We need to release that. And when we release the context, Quartz flushes out the drawing to the PDF destination, in this case, the URL. That's it for creating PDF documents. And you can do a lot more than this, but this is really what you need to do if you want to just take any drawing that you do with Quartz and create a PDF document.

Well, let's talk about taking graphics or drawing that you perform and exporting it as image data. Many applications want to do that, either image data to a file or image data to the clipboard. Here's what that looks like. Now, as you saw with PDF drawing, we created a PDF context in order to get bits that we can use to export. We want to create a bitmap graphics context that represents the bit array or the pixel array that we want our bits to be captured in. We draw to that context, and that will cause Quartz to render the bits. Now, once we've rendered the bits, the way we export those bits as image data is to create a CG image object from those bits. And once we have that, we can export the bits. Now, as I said, the destination can be a URL, the clipboard, or again, destinations can be custom destinations that you choose. Let's take a look at some code first to create a pixel array that represents our drawing.

So the first line of code here, we're going to create an RGBA image that represents our drawing. And I had to pick a size for the drawing that we're going to perform, so I chose 500, 500. The number of bits per component in our drawing is going to be 8. And because we're capturing alpha, the number of components is 4. RGBA is what we're going to generate. The next line of code is just calculating the number of bytes per row for our pixel array.

Quartz needs a bitmap info flag passed into it that describes the pixel format that it should produce. In this case, we're producing alpha image data with the alpha as the last component in the sequence of components for an individual pixel. Now when you create a bitmap context, you own the raster memory or the memory that Quartz will actually draw into. In other words, you have to create it. And that's what we're doing here. We're mallocing a block of memory that represents the height, number of scan lines in our pixel array, and the width based on the bytes per row. That's how much data we're mallocing.

There's the data block. We pass that data block or that pointer into cgbit.context create. We have to tell Quartz the width and height of the pixel array, the number of bits per component, the number of bytes per each row. And we have to tell Quartz what color space characterizes destination. We're going to use our handy function I showed you earlier to get the generic RGB color space in order to characterize the bits that we're creating. And we pass in the bitmap info. Now, once we've created a bitmap context, the first thing that we want to do here-- we're capturing RGBA data-- the first thing we want to do is we want to clear the context such that every pixel in the context is completely transparent.

By doing this, it ensures that when we do our drawing, Everywhere we draw, we'll actually see the drawing. However, everywhere we didn't touch any pixels, those pixels will be transparent. And that's important because we don't want, when we later on create our image, we don't want portions where we didn't draw to be anything but transparent. OK, we have a bitmap context. Now we do our drawing to the bitmap context just by passing it into our function that does our drawing just like it did to any other context. Now, of course, it's rendered the bits to that context. And from those bits, we want to create an image.

Quartz has a function, told you about it before, CG bitmap context create image that will create a Quartz image from a bitmap context. At that point, Quartz is responsible for obtaining whatever image data it needs in order to be able to draw the image that it's created. And so at that point, now that we've created the image, we need to release both the bitmap context that we've created, because we did create it, and we need to free the data that is the malloc block that represented the pixel array. And this function then returns the image so that now we have a CG image object that represents our drawing.

OK, now we want to export that as a file. So this function takes a URL, and it's going to take the drawing that we just created. It's going to call the function I just showed you. We're going to export it to a destination. To do so, we're going to create a CG image destination object, calling CG image destination-- excuse me, CG image destination create with URL. That is a URL destination. And we have to specify to Quartz, what format do we want to export this as? Now I'm choosing ping here, and I'm passing in the appropriate constant.

And remember, we talked about the fact that data image files can potentially have more than one image in them. Ping doesn't support more than one image, but the API does support more than one image. For example, if you're creating a TIFF data source, or excuse me, data destination, image destination, you might choose to have multiple images that you want to put in. We have to tell Quartz up front how many images we're putting in. And in this case, there's only one.

Okay, we have an image destination. Let's add the image to the destination. That is the image that represents our quartz drawing. We release the image now that we're all done with it. We have to call a function to tell Quartz, we're done with the destination. Finalize the destination. That causes Quartz to flush out the image data to the destination. And finally, because we created the destination object, the image destination object, we need to release it.

OK, something that a lot of people want to do with graphic systems is cache a representation of their drawing in a manner that they can reuse in order to get better performance or in order to redraw a whole scene very quickly. In order to accommodate that, we have a CG layer object in Quartz that allows you to straightforwardly cache a representation that's appropriate for a given destination. I just want to mention, because you've heard about layers in other talks here, for example, for core animation, this is a different kind of object.

So let's take a look at what that involves. Very straightforward. You have a destination for which you want to cache some drawing that you later on perform multiple times. From that destination, you create a CG layer object. Depending on what kind of destination you're drawing to or you want to create the cache representation for, Quartz will represent the drawing that you perform to the layer object in a different way based on the destination, that is, it will create an optimal representation based on the ultimate destination for your drawing. That's why you specify the graphics context destination when you create the layer object. You have an object, in Quartz, as you know, When you want to draw to a destination, you need a CG context. So there is a function that allows you to get the context from the layer so you now have a quartz context in order to draw. When you perform your drawing to that quartz context, what you're doing is you're creating the cast representation of your drawing. Now we have our cast representation in the layer, and if we want to draw that cast representation, we draw the layer to the destination. And every time we call drawLayer in order to draw, We're going to draw a representation from the cache into the destination. This is really pretty straightforward to create this cache drawing. You don't have to create bits off screen. You don't have to know anything about the way Quartz is actually doing things behind the scene. All you need to do is create a layer, draw to it, and then you can draw to your destination.

So here's a little bit of code first to create our cache representation. We call cgLayer create with context, where we pass in the context. We also have to say the width and height of the cache representation that we're trying to create. You want to tell it the minimum size that you need to create your drawing. That's for efficiency purposes. Once you have your layer, you call cgContext get layer-- excuse me, getContext. cgLayer getContext. Passing in the layer, you get a Quartz context back. Now notice that that's a get function, cgLayer get. We didn't create anything. We've got to be careful. We don't want to release that. But we get the context from the layer. And now we do our drawing to that layer, whatever that drawing is.

Now the layer contains the cache representation of our drawing, we're ready to use it. So this next piece of code here uses that layer. So specifically, the first thing it does is it creates that cached content using the function I just showed you. I get the size from the layer. The layer knows its own size that we created. And I'm just using the size here to position our drawing in an optimal way.

And all I'm doing here in a loop is just drawing from the layer to our destination context, adjusting the location of the lower left point of the layer as I draw each time. Once I'm done doing my drawing, we release the layer. We don't need any longer. Now more typically for you, you're not going to have a function that creates this cache representation and throws it away after you do a little bit of drawing. You may have it around. You may need to manage your cache. But this is the basics of how you create a cache representation and how you use it. OK, I'd like to invite Bunny back up to take you through the rest of the presentation, starting with advanced capabilities.

Thank you, David. Okay, I'd like to show you a few of the advanced capabilities of Quartz to whet your appetite so that when you leave here, you might go out and explore some of these on your own. We won't have time to show you the code of actually how to do these things. Quartz provides the CGPatternRef opaque data type that allows you to create a pattern cell. In the pattern cell, you can have any kinds of arbitrary quartz drawing, simple or complex, including PDF content. You can then use that pattern cell to fill and stroke, or you can stamp it out repeatedly to get a wallpaper effect. Now, a pattern can have intrinsic color, or it can operate like a stencil where you would pour color through it. Quartz provides the CGContextSetShadow function that allows you to put a shadow onto an object. You can specify an offset and a blur, and you can even specify a color for the shadow.

Quartz does really nice axial and radial shadings. And for that, it provides two opaque data types. The CG function ref data type allowed you to define a function that computes how the color changes. Whereas the CG shading ref opaque data type, that you use to define the geometry of the shading and also the colors that you want to use.

Quartz has transparency layers that allow you to group one or more objects together so that then you can apply an effect to those objects as if they're one group. And here you can see we have three separate objects on the left, but on the right when they're treated as one group you can apply a shadow to them and get a nice effect. The way you do this is to take the drawing code for those objects and you just surround it by a call to CG context begin transparency layer and CG context end transparency layer. Okay, so right now you're probably champing at the bit to get started as a quartz programmer. So I'd like to give you three things, advise three things that you can do to become a quartz programmer. Whoops.

Well, actually, I wasn't going to give this as my first tip, but I will because it's here. OK. The first thing I want you to do then-- as a co-author of Programming with Quartz, I'm really compelled to give a shameless plug for this book. So I'm going to do it now. What I'd like you to do-- and this is actually the second thing I wanted you to do-- is to either buy or borrow this book and take a look at it.

The book contains a lot of code examples that complement the Quartz 2D reference documentation. But it contains carefully crafted code examples that not only illustrate all the concepts that there are in Quartz, but also they provide a great starting point for you to get started with your own program. All the code examples are available for download from the Morgan Kaufman site. So you can just take them and start modifying them and using them on your own. Now the book is available through the Morgan Kaufman website, and for the next few weeks you can get a 20% discount by supplying the discount code 86868. Amen.

Well, I'm not the only one who is saying good things about it. You can read the reviews. As you can see, Dr. Michael Johnson has many good things to say about the book, as does David Hill, who's a former Apple DTS engineer and is somewhere here at WWDC. You can talk to him directly. The book is available at the Apple Campus Company store, which coincidentally will be open Thursday night during the beer bash, should you want to visit it. OK.

So the other two things I wanted to tell you, apparently the slides have disappeared somehow, is if you do nothing else when you walk out of the session, the thing that I really want you to do is to take a look at the Quartz 2D basic sample application. In it, you'll find all the code from today's session, and what I'd like you to do is to try to look at it and modify it and make some kind of substantial change to it. In addition to that sample code, you might also want to check out the Quartz 2D reference documentation, the Quartz 2D programming guide, and if you're an Objective-C programmer, the Cocoa Drawing Guide. And those three guides are available on the ADC website.

Right now, you can get them. Now, the other thing I want you to do is to stop by the Late Night Graphics Lab, which the Graphics Lab is open tonight from 6 to 10. Stop by, try your hand at doing some quartz code, talk to some of the engineers, get your questions answered, and just see how you do with it. Then immediately following this session is the Optimal 2D Graphics Session. You might want to take a look at that. On Friday, we have a session on Image.io, if you're interested in images in particular.

And unfortunately, you missed the PDF kit session, which would be another session for those of you who want to work with PDF. But there's excellent documentation on PDF kit, and hopefully you'll be able to get a hold of the slides of the presentation after the conference. So with that, I'd like to bring up our newly knighted 2D and 3D graphics evangelists, who I hope is here. Is he? Sir Allan Schaffer. I guess he's not here. Okay. So that's it. I think we have just a few minutes left for questions if there are any.

I don't know if I'm like, yeah. I just was going to say, in the late night lab, we're going to have copies of the book for you to look at. If you come by and are interested in the book, we'll be glad to show it to you. Thank you.