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 has known transcription errors. We are working on an improved version.
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. 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. 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 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. 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, viewing, 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. And ImageKit, 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 on 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. Its coordinate space is in units, not pixels. When you draw, Quartz makes sure that your drawing output is at the 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 Image.io 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. And 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-grained 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 C and C++ developers will find that it's really easy to use this API. 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 C and C++ developers will find that it's really easy to use this API. 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 C and C++ developers will find that it's really easy to use this API. The Quartz 2D 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 functions CF retain and CF release.
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's 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. Now, 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 Quartz 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, 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 0, 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.
One of them you can look at is 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 review, 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 contexts for on-screen drawing, off-screen drawing, printing, PDF documents. So I'd like to show you how to create some on-screen drawing contexts, and later on, David's going to show you how to create some of the other flavors of drawing contexts. 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 GraphicsPort method. After you have the Quartz graphics context, you can then pass out 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 is 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 graphics 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 graphics 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 CGContextRestoredGState and that pops the state off the stack and I revert to the previous one. So graphic state management, it's that easy. Okay, enough talking about Quartz. 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, CGContextFillRex, 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.
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 going to provide a green color, again, using another function that David's written. And then I'm actually going to 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, 5 units of the paint will be on one side of the path, and 5 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 quartz.
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. This function here, CGImageSourceCreateWithURL, is a function to create an image source object. That Quartz-- it 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. Once we have an image source, we can create from that image source a CGImageObject, a CGImageRef. CGImageObjects are what Quartz uses to represent an individual image. And once we have an image, we can draw it with CGContextDrawImage.
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 TIF images in a TIF file, for example, as a