Quartz 2D Graphics for Mac OS X® Developers

Next Page

Book Cover
Quartz 2D Graphics for Mac OS X® Developers
By R. Scott Thompson
...............................................
Publisher: Addison Wesley Professional
Pub Date: March 10, 2006
Print ISBN-10: 0-321-33663-1
Print ISBN-13: 978-0-321-33663-7
Pages: 352

Table of Contents | Index
Overview

R. Scott Thompson is currently a software engineer at Mindjet Corporation (www.mindjet.com). He has been a professional Macintosh developer since 1993. Working with Apple as an independent consultant he helped developers move their applications from the classic Mac OS to Mac OS X. Prior to that, Scott was an engineer with Macromedia helping to create a popular PostScript-based print publishing application.

Next Page


Table of Contents

Previous Page
Next Page

Book Cover
Quartz 2D Graphics for Mac OS X® Developers
By R. Scott Thompson
...............................................
Publisher: Addison Wesley Professional
Pub Date: March 10, 2006
Print ISBN-10: 0-321-33663-1
Print ISBN-13: 978-0-321-33663-7
Pages: 352

Table of Contents | Index

Copyright
Preface
Chapter 1. Getting Started
 | About This Book
 | Formatting Conventions
 | Accessing Quartz 2D
 | The Core Image API
Chapter 2. From QuickDraw to Quartz 2D
 | The Legacy of QuickDraw and the Rise of PDF
 | Graphics Programming in the Modern Age
 | Mac OS X Graphics Architecture
 | Other Graphics Libraries
Chapter 3. Introduction to Quartz 2D
 | Features of Quartz 2D
 | The Quartz 2D Drawing Model
 | A Simple Drawing Example
Chapter 4. The Graphics Context
 | User Space and the Page
 | The Graphics State
 | Other Context Functionality
 | Obtaining CGContexts
Chapter 5. Transformations
 | The Basic Transformations
 | Transformations in the Context
 | Transformations Unleashed
 | Drawing with Transformations
 | Transformations as Matrices
Chapter 6. Line ArtBuilding Paths
 | On Paths
 | Building Paths
 | Path Drawing Performance
Chapter 7. Line ArtDrawing
 | Drawing Basics
 | Fills
 | Strokes
Chapter 8. Image Basics
 | What Is an Image?
 | Introducing CGImage
 | Drawing CGImages
 | Creating CGImages
 | Image Performance Issues
Chapter 9. Importing and Exporting Images
 | Using QuickTime
 | Image I/O
Chapter 10. Drawing with Core Image
 | What Is Core Image?
 | How Core Image Manipulates Images
 | Using a Core Image Filter
 | Combining Filters and Images
 | Core Image Transitions
 | Animation, Timers, and Updates
 | A Transition Example
Chapter 11. Drawing Text with Quartz 2D
 | The Text Processing Pipeline
 | Drawing Text
 | Drawing Simple Strings
 | Drawing with Glyphs
 | A Drawing Sample
Chapter 12. Drawing Offscreen
 | Using a CGBitmapContext
 | Working with CGLayers
 | A Layer Drawing Example
Chapter 13. Shadings and Patterns
 | Shadings
 | Creating Shadings
 | Patterns
 | Creating Patterns
 | Drawing with Patterns
Chapter 14. Working with PDF
 | Drawing PDF Documents
 | Creating PDF Documents
 | Adding Hyperlinks to PDFs
Index

Previous Page


Copyright

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.

The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.

The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact:

U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com

For sales outside the United States please contact:

International Sales
international@pearsoned.com

Library of Congress Cataloging-in-Publication Data:

Thompson, R. Scott, 1970-

Quartz 2D graphics for Mac OS X developers / R. Scott Thompson.

p. cm.

ISBN 0-321-33663-1 (pbk. : alk. paper) 1. Computer graphics. 2. Quartz (Electronic resource)
3. Mac OS. 4. Macintosh (Computer)--Programming. I. Title.

T385.T4953995 2006

006.6--dc22

2005034911

Copyright © 2006 Pearson Education, Inc.

All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, write to:

Pearson Education, Inc.
Rights and Contracts Department
75 Arlington Street, Suite 300
Boston, MA 02116
Fax: (617) 848-7047

Text printed in the United States on recycled paper at Courier in Stoughton, Massachusetts.

First printing, March, 2006

Dedication

To Jennifer, Rachael, and Gabriel with all my heart


Preface

The graphics systems available to Macintosh applications have evolved very quickly over the past few years. Prior to the introduction of Mac OS X, the primary focus of all Macintosh graphics was the QuickDraw graphics library. QuickDraw not only provided the tools that applications needed to draw into their windows, but it also played a role in managing the screen, handling events, and changing the cursor. As the demands placed on the graphics system increased, Apple discovered that a reliance on QuickDraw imparted some limitations to their capability to expand the graphics system. During the transition to Mac OS X, many of QuickDraw's responsibilities migrated to other portions of the system. To handle many of the drawing and screen management tasks, Apple introduced a new graphics system called Quartz. In programming circles, Quartz is also known as Core Graphics.

Quartz not only handles many of the responsibilities of QuickDraw, it is the platform on which many of the innovations in the Mac OS X graphics system are built. For example, Core Graphics has taken over the job of collecting the images of windows and combing them on the screen. In performing this task, it takes advantage of modern graphics hardware to improve performance and introduce features such as translucent windows to the system. The end result is a remarkably flexible graphics system. Quartz allows applications to seamlessly integrate technologies as diverse as the motion graphics of QuickTime and the 3D graphics of OpenGL onto the same screen or even into the same window.

With the introduction of Mac OS X 10.4, Tiger, Apple has deprecated QuickDrawand that library will not evolve any farther. Applications that only rely on QuickDraw for drawing will not enjoy any innovations Apple makes in the graphics system. Even worse, in the future, applications that rely on QuickDraw may actually pay a performance penalty. Any program that wants to take full advantage of the graphics system on Mac OS X will have to replace their QuickDraw drawing code with a more modern alternative. Apple recommends that application developers replace their QuickDraw graphics code with similar code that uses Quartz 2D.

Quartz 2D is a part of the Core Graphics system. It is a modern graphics library based on the imaging model that Adobe created originally for PostScript printers and later as part of the PDF graphics file format. This is the same imaging model that graphics professionals have used for several years to create the artwork on everything from books and advertisements to application splash screens and on-line games.

The Quartz 2D drawing model allows you to create sophisticated graphics with a simple API. The Quartz 2D imaging model is quite different from the drawing models of other graphics libraries. Its library can draw to many kinds of graphics devices while maximizing the fidelity of the graphics on each device. As a result, developers familiar other graphics libraries such as QuickDraw, GDI from Microsoft Windows, or the graphics portions of X11's XLib face a learning curve when trying to work with the device and resolution independent drawing model in Quartz 2D.

The objective of this book is to present a practical introduction to Quartz 2D. Its aim is to help all programmers understand the Quartz 2D imaging model and make effective use of the library from any application environment. Most importantly, the text compares and contrasts the Quartz 2D imaging model with the pixel-based graphics models of other libraries. While this will be of particular value to developers making the transition from QuickDraw or GDI to Quartz 2D, it also provides valuable insight into how to use Quartz 2D effectively. The hope is that this information will be invaluable to anyone trying to draw graphics using Quartz 2D.


Chapter 1. Getting Started

Macintosh computers have enjoyed a strong association with graphics since they were first introduced to the world in 1984. That association undeniably began with the novelty of the graphical user interface. The platform evolved from its humble beginnings as a consumer computer, and as it grew, it retained its strong association to graphics. The platform attracted professionals in many graphics disciplines. In serving those needs, application developers have firmly established the Macintosh as an invaluable asset in today's graphics industry.

Apple, for their part, works hard to meet the needs of graphics professionals and application developers alike. They have focused their efforts on creating a system with the power to satisfy the needs of professionals on both sides of the development equation. Macintosh has already taken a leadership role in emerging fields like digital photography and professional digital video production. The future promises to continue or even accelerate this trend.

The classic Macintosh operating systems relied on the QuickDraw graphics library to provide a graphics system for the platform. As the demands of developers evolved, QuickDraw itself evolved, often in unexpected directions. Eventually, the needs of application developers began to outpace the ability of the QuickDraw technology to adapt. Apple took advantage of a rare opportunity when they introduced Mac OS X with a new graphics architecture. Quartz 2D is a portion of that architecture and carries the functionality that is closest to the drawing capabilities of QuickDraw.

Quartz 2D itself is a modern, device independent library for creating 2D graphics. It is a vital component of the operating system and plays a prominent role in many other parts of the system, like the Aqua user interface and the Mac OS X printing system.

This book offers an introduction to Quartz 2D for application developers. The objective is to introduce the Quartz 2D imaging model and explore some drawing techniques for applying that imaging model when creating graphics on Mac OS X.

While Quartz 2D plays the many of the same roles that QuickDraw played on older operating systems, or that GDI plays on Windows, the imaging model it uses is quite different from those other libraries. This makes learning to use Quartz 2D a challenge. Because Apple has deprecated QuickDraw, many developers are now in a position of needing to rework their application's graphics system to use Quartz 2D. Developers porting applications from Windows or X11 also may find the differences in the imaging models troublesome. Many of the same techniques that a developer might use to optimize graphics with other graphics libraries cannot be applied to Quartz 2D. This book is designed to help programmers understand the differences among Quartz 2D and other graphics libraries and to learn new techniques they can apply through Quartz 2D. At times, this text focuses on QuickDraw because that technology is representative of a pixel-based graphics library and is likely to be familiar to Macintosh developers. However, the discussions should serve those who are bringing their applications to the Macintosh as well.

As an added bonus, a very brief introduction to Core Image is given. Core Image is a high-performance architecture for creating and applying image processing filters to images. It has been included in this book not only because of its novelty, but because it is a very useful tool for creating 2D graphics. Core Image is remarkably easy to use and can generate impressive results quickly. The primary reason for including Core Image, however, is that along with Quartz 2D, Core Image represents the future of graphics on the Macintosh platform.

This first chapter presents some preliminary information. We begin with an overview of the book and a short synopsis of the discussions to follow. Some of the more mundane aspects of accessing the library are covered, such as understanding the naming conventions in the Quartz 2D API. Also presented is a brief description of the Core Image API with some pointers on finding more information about Objective-C.


About This Book

The book begins by placing Quartz 2D and Core Image in the context of the Mac OS X graphics system. As part of that discussion some very important advances in personal computer graphics systems are pointed outadvances that Quartz 2D uses to its advantage. Chapter 2, "From QuickDraw to Quartz 2D," offers a historical perspective on the origins of the Mac OS X graphics system in general, Quartz 2D in particular. It discusses important trends that are shaping the future of computer graphics on the Macintosh. These discussions also contain valuable insights that will hopefully demonstrate the advantages of adopting Quartz 2D in your own applications.

Chapter 3, "Introduction to Quartz 2D," begins the main discussion of Quartz 2D itself. This chapter begins by describing some of the distinguishing features that the library provides. Chapter 3 is also an introduction to the Quartz 2D drawing model. The chapter presents a bird's-eye view of the drawing process and contrasts the Quartz drawing model with the traditional pixel-based model of other graphics systems.

Chapter 4, "The Graphics Context," begins a more in-depth analysis of the Quartz 2D imaging model. This chapter builds upon the foundation in Chapter 3 and describes a central player in the Quartz 2D imaging model, the Core Graphics Context. The chapter describes the many facets of a graphics context and concludes with a discussion of how to create graphics contexts on a variety of devices.

Chapter 5, "Trasformations," discusses coordinate transformations in Quartz 2D. The flexibility in the Quartz 2D coordinate system is an important feature that is not found in many other graphics libraries. The transformations in Quartz 2D form the basis of the library's device independence and are valuable tools that can help applications build their graphics. In describing the transformations in Quartz 2D Chapter 5 offers suggestions about how to use them to simplify the drawing process.

Chapter 6, "Line ArtBuilding Paths," and Chapter 7, "Line ArtDrawing," concern the tools used to draw line art in Quartz 2D. Chapter 6 introduces the fundamental element of line art in the system, the Bezier Path. The chapter discusses the role of the path in the drawing model and describes different ways applications can construct paths. Chapter 7 builds upon the information in Chapter 6 by exploring the basic path drawing operations. While there are only three fundamental drawing operations for paths (stroking, filling, and clipping), the Quartz 2D imaging model includes a large number of options for each operation. Chapter 7 describes these options and their effects.

Chapters 8, "Image Basics," and Chapter 9, "Importing and Exporting Images," address the role of images in the Quartz 2D drawing model. Images are the tools that Quartz 2D provides for working with pixel data. Chapter 8 explains what goes into an image, how to incorporate images into your graphics, and how to generate pixel-based images from Quartz drawing code. The ability to create images becomes much more exciting when you can export those images to other applications. Chapter 9 explores two systems capable of importing and exporting graphics, Image I/O and QuickTime. Image I/O is a new, powerful architecture for exporting images from Quartz 2D. Image I/O, however, is a Tiger-only feature. For the benefit of applications that must work with older Mac OS X systems, also included is a description of working with QuickTime importers and exporters.

Chapter 10, "Drawing with Core Image," continues the theme of working with images by presenting an introduction to Core Image. The chapter provides an quick overview of Core Image features and functionality. It then describes the Core Image drawing process and offers some code demonstrations that show how to use Core Image to apply a simple image filter and to run a transition.

Chapter 11, "Drawing Text with Quartz 2D," discusses the text capabilities of Quartz 2D. In addition to describing the features of the Quartz 2D text system, the chapter explains how Quartz 2D integrates with more sophisticated text handling systems. To demonstrate the text handling capabilities of Quartz 2D itself, the chapter includes a code sample that demonstrates one technique for using Quartz 2D to generate special text effects.

Chapter 12, "Drawing Offscreen," describes two mechanisms that Quartz 2D includes for drawing in off-screen environments. Proper use of offscreen graphics can have a profound impact on the performance of your application. This chapter describes drawing in CGLayers and into offscreen bitmaps. It provides some insight directed at helping developers choose the most appropriate mechanism for their own code.

Chapter 13, "Shadings and Patterns," covers Shadings and Patterns. Shadings and Patterns are two drawing primitives in Quartz 2D that are have not traditionally been available in other graphics libraries but which are found in most contemporary graphics packages. Shadings allow a program to create smoothly blended fields of color and then fill shapes with those colors. Patterns are formed by tiling a particular graphic repeatedly.

Chapter 14, "Working with PDF," focuses on the role of the PDF format in Quartz 2D. The library has the ability to record a series of drawing commands into a PDF. This means that PDF is the metafile format for Quartz 2D. The system also has the ability to draw PDF files. This chapter explores techniques for both creating PDF files from Quartz 2D drawing commands and incorporating external PDF files into your own graphics.


Formatting Conventions

Throughout the text, the examples strive to use text formatting to enhance the presentation. This section describes some of the formatting used in the text.

The text presents the names of routines, constants, or other code element in a particular font. For example, CGContextRestoreGState is the name of one of the routines in the Quartz 2D drawing API. This same font is also used for any source code listings that appear in the text.

As you progress through the text, you will find sections with special icons in the margins. These sections represent supplemental information related to the topic at hand but may not be essential to your understanding. Table 1.1 describes what kind of supplemental information each icon represents.

Table 1.1. Icons in the Text

Icon

Name

Description

Tip

This symbol appears next to descriptions of interesting information related to the topic at hand but which may not be crucial to understanding the main ideas of the text.

Caution

This icon calls out warnings related to the topic of discussion. Caution icons can help you avoid pitfalls and errors in your code or in the operating system itself.

Performance

The performance icon appears next to information that may help you extract the maximum performance from Quartz 2D or Core Image within your own code.

QuickDraw

This icon identifies information that may help developers with legacy applications move those applications from QuickDraw to Quartz 2D.

Apple provides excellent documentation on Quartz 2D and Core Image. You are encouraged to explore that documentation to help deepen your understanding of Quartz 2D. To that end we occasionally provide URLs that hyperlink to relevant Quartz 2D documentation.


Accessing Quartz 2D

Mac OS X includes a remarkable number of different application environments almost all in which Quartz 2D, in one form or another, is available. Clearly Quartz 2D plays an important role in applications with graphical interfaces, like the ones created with the Carbon and Cocoa application APIs. However, Quartz 2D also can be used from command line applications, CGI scripts, and even directly in the HTML of a web page or Dashboard Widget.

While the routines that invoke Quartz 2D will be different in each application environment, the Quartz 2D imaging model should remain familiar. This book is not able to describe each routine of each application interface in detail. For that information, refer to Apple's reference documentation. Instead the focus is on the general information about the different environments in this chapter and then the drawing model is highlighted throughout the rest of the book.

The Quartz 2D C API

Quartz 2D is one part of a larger graphics environment known as Core Graphics. In addition to Quartz 2D, Core Graphics interacts with the window system and hardware to integrate graphics created by the many different graphics libraries on the system. It is responsible for managing the display environment and working with the window server and Quartz compositor to create the image presented on the screens. While Core Graphics encompasses more than Quartz 2D, it's not unusual for people to use the two terms interchangeably when discussing Quartz 2D in context.

The Core Graphics API is an object oriented API. To allow developers to use that API in as many environments as possible, however, the system implements it as a set of C routines. The API itself draws upon the object-oriented concepts of encapsulation, inheritance, and polymorphism. Because C doesn't directly support these concepts, Core Graphics uses opaque data types, in the place of classes, to support encapsulation and inheritance. To provide polymorphism, Core Graphics applies a naming convention to its routines.

Core Graphics draws its design from the Core Foundation API. Developers familiar with Core Foundation can apply much of that knowledge to the Core Graphics APIs as well. In fact, programs can treat Core Graphics objects as Core Foundation objects. For example, Core Graphics objects can be stored in Core Foundation containers.

The Core Graphics APIs manage the life cycle of objects with the Core Foundation ownership model. We present the basics of the naming conventions and the object ownership model here. Apple's documentation on the Core Foundation Design Concepts present the same information in much greater detail (although in the context of Core Foundation and not Core Graphics). You can find it on the Web at the URL:

http://developer.apple.com/documentation/CoreFoundation/Conceptual/CFDesignConcepts/index.html

Naming Conventions

The first thing you will notice about the API elements in Core Graphics is the fact that they include the prefix CG. For example two Core Graphics routines are CGContextBeginPage and CGImageGetWidth. The simple exception to this rule is constants that also follow an older Macintosh convention. The name of constants begin with a lower case k. The constant kCGImageAlphaNone, for example, is one that describes the way images store alpha channels.

After the prefix, the name includes an opaque data type that relates to that API element. CGContextBeginPage is a method of a "Context" object. CGImageGetWidth and kCGImageAlphaNone both have something to do with "Image" objects.

The remainder of the name relates to the specific task that element is for. Object methods are usually verb phrases such as BeginPage or GetWidth. Constants simply have descriptive names. Many times the different parts of the name narrow down the use of the constant from general ideas to more specific ones. For example, in kCGImageAlphaNone, the general concept related to the constant is "Alpha" (the alpha channel), and this particular constant represents an image with no alpha channel (alpha channel of none).

Core Graphics Objects

A Core Graphics object is an instance of a particular opaque data type. This is completely analogous to an instance of a class in other object-oriented environments. Routines that create objects return a value known as an object reference. These data objects are easy to recognize because their type name ends with the suffix Ref. For example, one of the routines that create an image in Quartz 2D is CGImageCreate. The data type this routine returns is a CGImageRef.

It's important to realize that an object reference is not a pointer. Object references might not obey the semantics as pointers. This may have consequences in the way you handle references inside of your application. For example, if your application includes debugging code that validates pointers, you cannot use that code to validate a reference. The Core Graphics API does use NULL to represent an invalid or un-initialized reference. You may compare object references against NULL just as you would a pointer.

Unfortunately, the naming convention applied to object references does not apply to every API on the system. If you are new to Macintosh programming and you are using the Carbon API, one particular case worth noting is the file reference data type, FSRef. Based on its name, you might conclude that an FSRef variable is an object reference. In this case, the value of the FSRef is an opaque data structure, but it is not a pointer-sized value. You cannot compare an FSRef against NULL, and FSRefs do not implement the Core Foundation ownership model.

Core Graphics uses the Core Foundation reference counting system to manage the life cycle of objects. Each object contains an integer valued reference count. When the object is created, its reference count has the value 1. The API includes routines for incrementing and decrementing the reference count. Incrementing the reference count is called retaining the object. Decrementing the reference count is called releasing the object. When the object's reference count becomes 0, the system destroys the object and releases any resources it owns.

To retain an object you can use the Core Foundation routine CFRetain or a type-specific routine. To release a CGContext object, for example you could use the type-specific call CGContextRetain. Similarly, to release an object you can use the Core Foundation routine CFRelease or a type-specific routine such as CGContextRelease. The end result of CFRetain and CGContextRetain are the same; the only difference is that CFRetain will generate an error if you pass it NULL. CGContextRetain will not.

The pseudocode in Listing 1.1 presents an example of reference counting in Core Graphics. Listing 1.1 is not valid codefor example some of the parameters to CGImageCreate are not given. The listing represents code that manages the existence of two objects, a CGDataProvider and a CGImage.

Listing 1.1. Object Ownership Pseudocode

// Create a new data provider from a URL
CGDataProviderRef myDataProvider = CGDataProviderCreateWithURL(myURL);

// Create an image with the contents of the URL
CGImageRef myImage = CGImageCreate(..., myDataProvider, ...);

// The data provider has served our purposes so release it.
CGDataProviderRelease(myDataProvider);
myDataProvider = NULL;

// Call some code that does something interesting with the image.
DrawMyImage(myImage);

// We're done with the image now so we can release it
CGImageRelease(myImage);
myImage = NULL;

The code begins by creating an instance of the opaque type CGDataProvider. This newly created object begins with a reference count of 1. The CGImageCreate routine builds a CGImage as the second object.

The code uses the CGDataProvider as one of the parameters to the CGImageCreate routine. When the image has been created, the program has no use for the CGDataProvider. Listing 1.1 asks Core Graphics to release a reference to the object by calling the CGDataProviderRelease routine.

After you release the object, Quartz 2D may or may not dispose of the CGDataProvider. The code calls CGDataProviderRelease to tell the system that it has finished using the object. However, the CGImageCreate routine may have retained the object for its own uses. From your perspective, however, you no longer have a valid reference to the object, so you would set the reference variable to NULL.

The image also has an initial reference count of 1. The code calls DrawMyImage, which presumably draws the image on a graphics context. This is the only thing the code does with the image. The call to CGImageRelease releases the image and destroys it. If the image has retained the CGDataProvider, it will release that as well.

When you receive an object from a Core Graphics routine, you may or may not be responsible for releasing the object. The key to making the decision is the names of method. If a method's name includes Create (as in CGBitmapContextCreate) or Copy then the routine will return an object that you must release when you have finished using it. If the name includes the term Get, as in CGImageGetColorSpace, then the object returned by the routine belongs to the caller. If you want to maintain a reference to the object, you should retain it. If you do not retain it, however, do not release it.

Other Object-Oriented Features

Object references can participate in polymorphic methods. The abstract data type in Quartz 2D have inheritance hierarchies. For example, Quartz 2D includes an opaque data type called a CGContext. A CGContext is an abstract interface to a particular graphics device. There are several opaque data types that inherit behaviors from the CGContext type. The CGContext for drawing to a bitmap might be an instance of the CGBitmapContext data type, while the context for drawing to a PDF file would be a CGPDFContext. Both of these context types inherit their context behavior from the CGContext base type.

Inside application code, the polymorphic data type CGContextRef can store an object reference to any type of graphics context. Any routine that accepts a CGContextRef as a parameter should accept a bitmap context or a PDF context as well.

That's not to say that every polymorphic method applies equally well to all of the opaque types. For example, PDF files have pages while bitmap contexts do not. Quartz 2D has a method called CGContextBeginPage. This routine accepts a CGContextRef as a parameter and tries to create a new page on that context. Starting a new page in a PDF makes sense but creating a new page in a bitmap context does not. Nevertheless, CGContextBeginPage is a polymorphic routine, and you can pass a bitmap context to it. The system simply will won't do anything in response.

Accessing Quartz 2D from Cocoa

Cocoa and the AppKit include a number of Objective-C classes that are implemented using the Quartz 2D API. For example, NSBezierPath is a class that allows you to draw Quartz 2D line art. An important fact to note about the AppKit's drawing classes is that they are not a one-to-one reflection of the opaque data types found in the Core Graphics API. The Cocoa classes offer the same graphic quality as the "raw" Core Graphics APIs, but the system presents a more direct embodiment of the graphics model than the C APIs.

Cocoa does not yet provide Objective-C interfaces for every feature in Quartz 2D. However, because Objective-C applications can call C routines, they can make direct calls to Quartz 2D's C interface. This allows Cocoa applications to take full advantage of the Quartz 2D system, but it may require stepping away from the graphics support that the Cocoa classes provide by default.

Alternative APIs

In addition to the APIs provided by the traditional graphical application frameworks, Quartz 2D also has bindings to other, more unusual environments. In particular, Quartz 2D is available to shell scripts, command line tools, and CGI scripts through a set of Python bindings. HTML developers can use Quartz 2D in Safari and in Dashboard widgets, using the canvas tag. For the sake of completeness, we briefly explore those interfaces here.

The Python Interface

Many developers are surprised to learn that Quartz 2D has bindings for the Python scripting language. A scripting interface opens the power of Quartz 2D to applications that don't have user interfaces. For example, a server side CGI script, written in Python, could use Quartz 2D to generate an up-to-the-minute weather map.

Working with Quartz 2D through Python is straightforward. Python has its own object model that Apple has integrated with the Quartz 2D object model. The Python runtime environment has a garbage collector. That means that when working with Python, your script does not have to keep track of object references. The Python interface to Quartz 2D takes advantage of this memory scheme. Quartz 2D Python scripts don't have to retain and release objects explicitly.

The methods that you call on Core Graphics objects in Python take their names from the routines in the C API that invoke the same behavior. Because Python has a class hierarchy, the Python methods do not have the CG prefix, and they do not explicitly name the class they work with. Following language guidelines, the initial letter of a method name is not capitalized.

This scheme sounds a bit more complicated than it really is. For example, the Core Graphics method CGContextAddLineToPoint is reflected in the Python interface as the method addLineToPoint called on an instance of the Python CGContext class.

When working with the Quartz 2D API through a C-like language, you have the advantage of being able to call any other API on the system. That means if you need to call QuickTime to import an image, you are free to do so. Unfortunately, Python interfaces do not exist for all that functionality on Mac OS X. The Python interface for Quartz 2D, therefore, includes a number of handy utility routines that help your scripts create graphics. For example, the Python interface includes the routine CGImageImport, which you can use to create a CGImage from a file on disk.

While there is plenty of reference documentation for the Quartz 2D routines themselves, those parts of the Python interface that aren't found in the C interface are not documented as well as they could be. There are quite a few example programs that Mac OS X includes with the developer tools. These files are found at the path

/Developer/Examples/Quartz/Python

That folder also contains a file called API-SUMMARY that holds a wealth of information about the Python interface to Quartz 2D and different ways you can use it in your own scripts.

The HTML Canvas Tag

Beginning with Mac OS X 10.4 Tiger, developers also can encode Quartz 2D graphics into HTML content that is part of a Dashboard Widget or an HTML page displayed in Safari. To get Quartz 2D graphics directly into HTML content, there are two important steps. The first is to identify some area of your HTML in which you would like to draw Quartz graphics. To do this, the HTML code uses the canvas tag. After the drawing area has been identified, the HTML document can draw into that canvas using JavaScript to talk to it.

The JavaScript routines that put content into an HTML canvas follow naming conventions that are very similar to those found in the Python interface. The code will typically begin by asking the canvas for a context object. It can then call methods on that context to create graphics.

Apple has provided some tutorials about working with the canvas in Safari and in Dashboard widgets. You can reach one such tutorial by following the URL:

http://developer.apple.com/documentation/AppleApplications/Conceptual/SafariJSProgTopics/index.html


The Core Image API

Core Image has an Objective-C interface. To work with Core Image, your code creates Objective-C objects representing concepts like an image or a filter and sends messages to those objects.

As an Objective-C API, Core Image uses the same object ownership model and naming conventions shared by all Objective-C interfaces. The Core Image classes begin with the solidly predictable prefix, CI.

One important note: while the Core Image API is accessed through Objective-C, it is not tied to the Cocoa application frameworks at all. Do not confuse the Objective-C language with Cocoa! The Cocoa application framework uses Objective-C in its interface, but that does not mean that all Objective-C interfaces on the system are Cocoa interfaces. In particular, Carbon applications are free to use Core Image too. They will have to include some Objective-C or Objective-C++ files, but they need not use any Cocoa classes.

Some readers may not be familiar with Objective-C. The language is a straightforward implementation of object-oriented programming, built on top of C but drawing some syntax cues from Smalltalk. Developers familiar with C and object-oriented concepts should have no trouble grasping the basic syntax needed to make effective use of Objective-C in a few hours.

If you are unfamiliar with Objective-C and wish to use Core Image, read Apple's introduction to Objective-C at

http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/index.html

Additionally, there are several good, third-party books on Cocoa programming that also cover Objective-C. Any of them will provide enough information about Objective-C that you could easily apply that knowledge to the Core Image API.

This concludes the discussion about this book and about some of the more mundane aspects of working with Quartz 2D. Now to begin a discussion of Quartz 2D in earnest. You will hopefully enjoy the presentation and find it valuable!


Chapter 2. From QuickDraw to Quartz 2D

Before launching into an in-depth discussion of the features of Quartz 2D or a set of drawing techniques, this chapter begins by placing the library in context. The Quartz 2D library has a long legacy behind it and understanding that legacy may help you to recognize some of the idiosyncrasies of the library's imaging model. Moreover, Quartz 2D is a single part in a larger graphics architecture on Mac OS X. This architecture was designed from its inception to take advantage of advances in the graphics hardware of modern personal computers. This chapter begins with a historical perspective of the Mac OS X graphics system and the evolution of technology behind Quartz 2D. We then explore crucial aspects of the graphics technology that is shaping the graphics architecture of the Macintosh.


The Legacy of QuickDraw and the Rise of PDF

The QuickDraw graphics library stands to this day as testament to the original Macintosh computer and its creators. The library was the fundamental technology that made the original Macintosh graphical user interface possible. QuickDraw was brought to life by the ingenuity and skill of software designers such as Bill Atkinson and Andy Herzfeld. Apple created QuickDraw in the late 1970's and early 1980s, an era in which "real" computer graphics were the province of large, powerful mainframe computers, and personal computers were just making the transition from novelty to necessity.

QuickDraw had very modest beginnings. In various forms, the original code ran on computers like the Apple Lisa and the original Macintosh. The high resolution bitmapped displays of these computers was considered a revolution when compared to the character terminals of the previous computer generation. In spite of their sophistication, however, the computers could only display graphics in black and white.

QuickDraw was flexible enough to produce impressive graphics on both the screen and on printer. The library incorporated a number of revolutionary advances, features that were not found on personal computers prior to the Macintosh. Among these were the support for pixel regions, drawing operations that could be recorded into a meta-file (the infamous PICT file format), the ability to scale the drawings in a meta-file on playback, and drawing primitives for ovals, curves, and rounded rectangles.

Although QuickDraw and its immediate ancestors ran on computers that could only display black and white, the API actually supported several colors. Even though the computers could not display those colors on screen, QuickDraw could print in color to some printers.

The 9-inch black and white display of the original Macintosh quickly became a thing of the past. Computers evolved, as did the sophistication and requirements of applications and users. Apple evolved QuickDraw along with its computers. As displays became capable of reproducing millions of colors and two tone dot-matrix printers evolved into high-resolution, photo quality ink jet printers, QuickDraw both kept up the pace and pushed the envelope of graphics evolution. The era of QuickDraw ended, however, when Apple deprecated the technology in Mac OS X 10.4 Tiger. The QuickDraw library came of age as an important building block for vital graphics technologies like QuickTime and ColorSync. Along the way it not only served the graphics industry, but also took a hand in shaping it.

PostScript and Desktop Publishing

QuickDraw was not the only technology born in 1984 to have a profound impact both the graphics industry and the Mac OS X platform. In the same year, Chuck Geschke and John Warnock incorporated a new company, Adobe Systems Inc. Adobe released the first version of their PostScript graphics system that same year.

The PostScript graphics system grew out of research Adobe's founders performed while working at Xerox. That research centered around innovative ways to write control software for laser printers. The PostScript system combines a rich graphics model, a simple programming language, and a run-time environment. At the time of its introduction, the system's ability to repurpose graphics on a broad number of printers with very different capabilities was a clarion call for the graphics industry.

PostScript was unusual because of the device independence inherit in the system. A PostScript program could send the same drawing commands to two printers with very different capabilities, and both would reproduce the same graphic to the best of their abilities. One printer might draw the graphic with a low resolution and in black and white, while the same program on another printer might generate a high-resolution color image. This was in stark contrast to the fixed resolution, device dependent nature of QuickDraw and other graphics libraries.

The paths of QuickDraw and PostScript were destined to converge. Apple and Adobe brought these two technologies together when they developed the LaserWriter printer. In spite of the fact that QuickDraw had one drawing model, and PostScript a completely different one, application developers combined the on-screen drawing with QuickDraw and the printing might of PostScript. This synthesis led to the creation of creative applications such as Aldus Page-Maker and Adobe Illustrator. Applications like these freed document editors from the proprietary typesetting systems that had dominated the industry. The Desktop Publishing revolution had arrived.

PostScript on the Screen

The rift between the graphics model of the screen and the graphics model of the printer continued for some time. In fact, this was not limited to the Macintosh platform. Microsoft Windows, for example, used the GDI graphics library when drawing on screen. On many UNIX systems, applications used the XLib library of the X11 windowing system to create graphics on the screen. Both GDI and XLib are graphics libraries very similar to QuickDraw. Each of these environments also supported PostScript as a tool for creating high-quality graphics on the printer.

In this environment, application developers applied their creativity and ingenuity to bridge the gap between libraries and create WYSIWYG applications. It wasn't long before enterprising developers realized the potential benefit of using PostScript for graphics on the screen as well. In fact, one of the earliest implementations of this idea came when Sun Microsystems created an entire windowing system based on PostScript! That windowing system was called NeWS, the Network extensible Windowing System.

NeWS was unusual because it used PostScript as more than just a graphics library. The windowing system itself was implemented on top of a customized PostScript interpreter. Developers could extend the system or write NeWS applications in PostScript. In spite of the novelty and innovation in NeWS, however, it never gained much of a foothold in the industry. Eventually it faded from view.

The lead engineer of NeWS was Jim Gosling. After working on NeWS, he turned his attention to Java where he designed the original language, compiler, and virtual machine.

The engineers at Sun were not the only group to bring PostScript to the screen. One of the most successful environments to integrate PostScript into its graphics architecture was the NeXT Computer operating system. Steve Jobs started NeXT Inc. shortly after leaving Apple. The computers that company created were revolutionary in a number of ways. The NeXT operating system included an optimized PostScript interpreter. Applications created their graphics in windows by calling routines invoked the PostScript interpreter.

NeXT sold a PostScript laser printer that didn't include a PostScript interpreter! The system relied on the computer to process the PostScript and then sent the resulting graphics to the printer over a high-speed communication line.

The success of the NeXT graphics library caused other industry leaders to take notice. Adobe collaborated with NeXT to create a standard called Display PostScript for PostScript graphics on the screen. Display PostScript incorporated a small set of extensions to the basic PostScript language. In time, Adobe licensed Display PostScript for use in other computing environments. The X11 applications on some UNIX computers could create windows that contained Display PostScript graphics. Display PostScript, like the NeWS window system, never gained a broad acceptance in the industry. When Apple purchased NeXT Computer, in 1996, it acquired all of its technologies, including its implementation of Display PostScript.

PDF to Quartz 2D

Even though Display PostScript enjoyed limited popularity, the appeal of PostScript continues to this day. There is, however, one evolution that is particularly relevant to the Macintosh and Quartz 2D.

The PostScript system includes both an imaging model and a programming language. While the flexibility of the imaging model continues to this day, the programming language aspects of PostScript have proven to be problematic.

PostScript interpreters run PostScript programs. Like most programs, PostScript code must execute sequentially. This can lead to difficulty when printing documents. For example, if a press operator wants to print page 99 of a 100-page document, the PostScript interpreter must execute the code that draws the first 98 pages. The results of that drawing take time, and the resulting graphics are merely discarded. This is obviously a waste of time and resources.

Furthermore, because PostScript drawings are actually programs, they are susceptible to programming bugs. To continue the example just discussed, a logic error on page 97 might cause the printer to abandon the entire print job. As these problems came to light, Adobe added some features to PostScript. The new features helped to alleviate problems but did not eliminate them. In the end Adobe recognized the limitations of PostScript and took another tack on the problem.

The Portable Document Format (PDF) works around many of the limitations of PostScript. At its heart, PDF is a file format that includes the graphics features of PostScript (with a few minor changes). At the same time, the standard removes most of the programming language aspects of PostScript. A PDF document is not an executable program so much as it is a structured container for drawing commands and related metadata. The file format continues PostScript's advantages of device and resolution independence. In short, PDF retains the graphical power of PostScript but eliminates some of its less reliable aspects.

Apple evaluated the technologies they purchased for NeXT with a eye toward creating Mac OS X. They looked at the evolution of PostScript and the PDF strategy. Combining these elements with their own unique flair, Apple developed a new graphics library for Mac OS X that combined the richness of the Adobe Imaging Model with alpha channel support in the high-performance graphics library we know today as Quartz 2D.

Understanding this legacy can be an important part of understanding why the Quartz 2D imaging model behaves as it does. For example, Quartz 2D does not provide an easy mechanism for erasing graphics you have already drawn. The reason is that when you are drawing to a printer, the graphics may not be going to a frame buffer, and there is no way to erase graphics. Instead of erasing graphics, in Quartz 2D you create a mask, or clipping area, and draw the graphics you want the user to see relying on the mask to remove parts of the image you don't want to display. This is a simple example but very illustrative of how the drawing model of the library can affect how it is used.


Graphics Programming in the Modern Age

One advance in technology that contributed to the demise of QuickDraw was the trend in modern personal computers to move more of the graphics capabilities of the system to video cards. Indeed the advent of video cards with dedicated graphics processing units has ushered in a new age of computer graphics. Quartz 2D and Core Image take advantage of these recent developments to improve their functionality and performance. Core Image in particular is a direct benefactor of the power of modern graphics hardware. The evolution of the graphics system in personal computers is extending the reach of those machines to new and exciting fields of endeavor. The Mac OS X graphics system is at the fore-front of this technologies wave. By using modern graphics APIs like Quartz 2D, your application can take advantage of the work Apple has done, and you can enjoy the benefits of the hardware while concentrating on a simple interface.

By way of an example, consider the impact that modern personal computers have had on video production. We live in an age where studios use computer graphics to create full-length, animated feature films. In the past, digital video production houses used expensive, dedicated workstations to produce their films.

Just as PostScript shifted print publishing from proprietary systems to the desktop, the development of software such as Final Cut Pro has professional quality video editing onto consumer computers. This transition works because of a combination of hardware improvements and the advancement of the graphics systems on personal computers.

In recent past, images that used 32 bits per pixel and alpha channels could only be manipulated by high-end applications. Today, however, these images are commonplace. The CCT chips in modern digital cameras can capture images using 12 bits per color channel or more. Storing these images in an 8 bit per channel image drops valuable color information. The high-end applications of today may choose to use a full 32-bit floating point value to represent just one color channel. Each pixel, therefore, requires 128 bits. An image with the same dimensions may require four times the storage just to hold the additional color information! Processing such an image requires the computer to sift through four times the data.

Shuffling around large volumes of pixel data is one difficulty. The color channels in these images are stored in floating point representations. Correspondingly, performing calculations on those pixels requires floating point math. Computing at this level requires significant processing horsepower and efficient use of graphics resources. Computer scientists have answered the demand for greater graphics processing power by adding dedicated computer graphics hardware to personal computers.

General Purpose Vector Processors for Graphics

A good example of the evolution of hardware with a corresponding impact on graphics is the addition of vector processing units to general purpose microprocessors. On the Macintosh platform, for example, the G4 and G5 PowerPC processors have a vector processor known as the Velocity Engine. Developers will recognize it by its geeky name, AltiVec. Intel-based processors include SIMD technologies like MMX or SSE.

AltiVec will serve as a good example of a general purpose vector processor. The registers of the AltiVec unit store quantities that are 128 bit wide. The processors instructions treat those bits as vectors. Depending on the instruction, the processor will interpret the 128 bits as a vector whose components have different lengths. Figure 2.1 shows the different ways that AltiVec processors can interpret 128 bits.

Figure 2.1. AltiVec Register Configurations

When interpreting a vector of four 32-bit values, the processor can treat those bits as either a 32-bit integer or a 32-bit floating point value. A graphics application might feed the AltiVec unit with 16 pixels of an 8-bit grayscale image all at once. The program could then lighten all 16 pixels at once using a single AltiVec command.

Using different instructions, a program could also load an AltiVec register with the four 32-bit floating point numbers that make up a single floating point ARGB pixel. The AltiVec processor could combine two floating point ARGB pixels in a single operation. From these two examples, it's easy to see how the processing muscle of a vector unit like AltiVec can improve graphics performance.

One shadow that complicates the use of vector processing units for graphics is the fact that the AltiVec unit is not dedicated to graphics alone. Computer games are popular clients of the graphics system. Many games contain computing engines that handle physics calculations. Physics involves working with vector-valued quantities like velocities and accelerations. These calculations are also a good fit for implementation with the vector processor. Scientific visualization applications also rely heavily on the graphics system and include algorithms that benefit from the vector processor. In many applications, the vector processor is shared between the graphics systems and other computation engines.

Computers with two or more microprocessors often have the added luxury of a second vector processing unit, and applications can employ that to alleviate some of the congestion. Unfortunately, there is a practical limit that prevents computers from scaling performance through the addition of processing units.

The complexity of a general-purpose vector processor means that they also take up quite a lot of space on silicon chips. Correspondingly, the amount of power they require and the amount of heat they generate increases with each additional unit. Issues like these make it very hard to scale the performance of a general vector processor by simply adding additional cores. As will be shown, however, if we reduce the complexity of the core, limit the operations it can perform, and focus it on a specific task, the idea of scaling vector processing power this way actually works quite well.

The Emergence of the GPU

Another common technique to boost the graphics performance of a computer is to augment the CPU with an additional processor that is dedicated graphics. On the personal computer systems of today, that additional processor is usually found on the computer's video card.

Many of the earliest models of graphics coprocessors, particularly in the personal computer space, were simply tools to speed up some very specific parts of the 3D graphics pipeline. The cards had algorithms for applying lighting and shading models to simple geometric primitives like triangles. The algorithms were hard-wired into the video card and could not be changed. Communication with these cards flowed in one direction only, from the main computer to the video card. The cards were useful for rendering 3D graphics efficiently but could not be used in more general graphics applications.

As time progressed, the services provided by the video card's processor expanded to include more general purpose routines. The data path between the main CPU and the graphics card widened and became bidirectional. With those innovations, programs gained the ability to use the graphics processor to perform calculations and retrieve the results to main memory. This allowed the video card to behave as a graphics computation engine, not just a display mechanism.

Collectively, these more powerful processors have come to be known as Graphics Processing Units or GPUs. They play a significant role in boosting the graphics capabilities of modern computers. Along with the GPU, a typical video card will also contain a block of dedicated memory (called Video RAM or VRAM) and some kind of hardware that converts bits in the cards display buffer into video signals for a computer display. In many respects, the video card resembles a self-contained graphics computer. Like other computers, graphics hardware continues to advance. To give you some idea of how rapidly video cards have evolved, consider the graph shown in Figure 2.2.

Figure 2.2. GPU fill rate by year

[View full size image]

Figure 2.2 shows how the fill rate of graphics processors has grown over the years. A graphics card fill rate is roughly the number of pixels that the GPU can draw into video RAM in a single second. While the true processing power of the GPU varies for different tasks, this graph is a dramatic example of the advances that have been made in graphics processing power of GPUs.

The growth of graphics processing power actually exceeds the predictions of industry professionals. In 1965, Gordon Moore made a famous observation that the number of transistors on a single chip would double approximately every 18 months. This prediction has come to be known as Moore's Law. The computing industry has taken Moore's law to imply that the processing capabilities of silicon chips would grow at the same rate.

For many years the transistor counts and performance of CPUs has tracked this prediction with frightening accuracy. The graph in Figure 2.2 shows that the performance of GPUs has been growing faster than Moore's Law would predict. In general terms, this means that the processing power of dedicated graphics processors is growing faster than that of general purpose CPUs like the PowerPC or the Intel x86 family. Applications that tap into that processing power enjoy dramatic performance improvements.

One of the reasons that graphics processors follow this performance curve is because the performance of the processor is easier to scale by throwing more silicon at the problem. Many of the algorithms that the GPU runs are what computer scientists call "embarrassingly parallel." An embarrassingly parallel problem is one in which a computer can easily work up a solution breaking it into smaller pieces and computing each piece along a parallel path.

Astute readers will recognize how a program might apply an SIMD vector processor, like the aforementioned Velocity Engine, to calculate a solution to an embarrassingly parallel problem. But graphics processors don't need to solve the same problems that general purpose vector units must solve. Because it can focus on solving graphics problems, the GPU requires fewer operations. For example, general purpose processors must deal with branches, loops, and error checking. In contrast, the GPU pushes its vectors through sequentially without branches and loops. Each of the parallel units in a GPU is much simpler than its counterpart in a more general vector processor. Hardware engineers can add more vector units, and therefore more parallel computation paths, in the same area. More computation paths mean more operations completed each cycle.

Because the vector units in the GPUs are dedicated to graphics, they don't suffer the resource contention issues that plague general purpose vector processors. There aren't as many parts of applications competing for processor time.

The Programmable Graphics Card

Computers have spent many years sending data to graphics cards, but the ability to send programs to the GPU is a relatively recent innovation. Graphics cards that accept GPU programs from the main computer are known as programmable graphics cards. This ability to program the graphics card is the feature that lends power to graphics systems like Quartz 2D and, in particular, Core Image.

Even though the parallel execution units of graphics cards can only perform a limited number of operations, those operations can be applied to solve problems that have nothing to do with graphics. Some computer scientists are investigating techniques for using the video hardware to run computations that are unrelated to Graphics. The field is known as "General Processing on GPUs," and a number of enthusiasts host a Web site dedicated to the topic at http://www.gpgpu.org/.

At it's heart, Core Image is a system for feeding GPU programs to programmable video cards. The programs it submits usually apply special effects to images. By using the power of the parallel processing paths on the hardware, the computer can calculate those effects much faster than the main CPU could. Another interesting aspect of Core Image is that it can run its effects even if no programmable graphics card is available on the system. This demonstrates another advantage of the Mac OS X graphics architecture. It helps your applications produce improved performance without undue complexity.

Managing Hardware Complexity

The challenge to today's applications is finding a way to conveniently take advantage of the power afforded by modern hardware. For example, application programmers who want to use the Velocity Engine must learn the AltiVec instruction set. They must also develop "vectorized" algorithms for solving the application's problems. The applications often must rearrange data structures so that the vector processor can access them efficiently. All of these issues require specialized knowledge and add complexity to the resulting application.

In a similar way, making direct use of the GPU requires an application to understand some of intimate details about the video card. Some cards accept longer graphics programs than others, and some card have special instructions that simplify GPU code. Writing code that is general enough to support the diverse range of graphics hardware from different vendors is quite difficult. If you're writing your application directly to the hardware, you will have to immerse yourself in the minutiae of every hardware combination, or you must limit your application to only that small set of hardware you are willing to support.

This same problem applies even in computers that don't have video cards. Even tasks that appear simple, like copying pixel buffers in main memory efficiently, require a detailed knowledge of the processors' cache behavior and the system's virtual memory architecture. While these are all interesting topics, writing graphics programs this "close to the metal" can be complicated and error prone. Providing a rich, full-featured graphics system that allows applications to plug into the performance of the computer, while allowing programmers to focus on creating graphics and not hardware issues, is one of the toughest challenges the operating system vendor must face.

Quartz 2D and Core Image are both excellent technologies in this regard. They insulate applications from the complexity of the hardware but take advantage of that hardware in their own implementations. For example, Apple includes a compatibility mechanism inside of Core Image that allows the system to run GPU programs on any system. If a program uses Core Image on a computer without a programmable GPU, the program will continue to run correctly. The effects of applying a filter will take longer to achieve, but the results should be the same even without the dedicated hardware. The application programmer limits his attention to working with the interface of Core Image instead the details of the GPU. Similar arguments can be made with respect to the features of Quartz 2D. The graphics architecture of the system insulates applications from hardware details and is easier to use.


Mac OS X Graphics Architecture

Mac OS X, in general, is built as a layered software system. The lower layers are the ones closer to the hardware. These layers include software like hardware drivers and routines for accessing processor specific features such as AltiVec. The higher layers build upon the functionality beneath them and offer applications services that are easier to use. The challenge of the application designer is to decide at what level he needs to access the graphics system. It is a balancing act between application complexity and performance.

Figure 2.3 illustrates the layers of the Mac OS X graphics system.

Figure 2.3. Mac OS X Graphics Architecture Overview

Figure 2.3 is meant to convey the layers of the graphics system and the ways that they are built on top of one another in the most general terms. Strictly speaking, the QuickTime software layer might interact more directly with the hardware, bypassing the layers beneath it, than this diagram would suggest. Nevertheless, the diagram is useful for describing the Mac OS X architecture. In the following sections, we will describe each layer in the Mac OS X graphics architecture and their roles in creating graphics.

Kernel and Hardware

The kernel and hardware layer represents the lowest levels of the operating system. The hardware includes both the components of the main computer and the chips on the video card. In terms of the main computer, the graphics system must often interact with the CPU, any vector processing units, and the memory system. This layer of the system handles the complexity of processor cache lines and complexities like processor-specific instructions. Hardware on the video card includes the presence of a programmable GPU, the amount of video ram, and the interconnection between the video card and main memory.

The kernel layer includes the video card drivers and other software that interacts directly with the hardware.

The software interfaces exported from the kernel and hardware layer encapsulate a tremendous amount of complexity. Applications that need the absolute highest level of performance may need to connect to the system at this level, but that is likely to be a very rare occurrence.

OpenGL

OpenGL was first released in the early 1990's and is widely known as an industry standard graphics library for creating 3D graphics images. Given its association with graphics accelerators, OpenGL is also a valuable tool for accessing the full capabilities of the video card. OpenGL is a cross-platform standard. A commission known as the OpenGL Architecture Review Board (ARB) oversees and steers the technology's development. The ARB's web site is http://www.opengl.org. It s a valuable repository for resources related to writing OpenGL code. The ARB site also contains innumerable links to other site, which makes it an excellent jumping off point for graphics programmers who want to know more about OpenGL.

Mac OS X includes a terrific implementation of OpenGL. Many 3D games and scientific visualization applications take advantage of the 3D graphics features of OpenGL. Interest in OpenGL in this volume, however, does not focus on its 3D graphics features. Nor will it concern itself with OpenGL's ability to serve as a first-rate 2D graphics library. Instead, the focus is on OpenGL as a rather direct interface to the kernel and hardware layers.

We already mentioned how early graphics cards were little more than shading engines and rasterizers for 2D and 3D graphics primitives. Applications would submit their primitives to the hardware through OpenGL. The library has evolved, in lock-step with the progress of video cards. As video card vendors add new capabilities to their hardware and drivers, the OpenGL community modifies their code to make those capabilities accessible to applications. Likewise, as OpenGL developers discover new and innovative graphics techniques, they first implement them in software. The most useful and popular may be bolstered with hardware implementation on future video cards.

When working with Quartz 2D, the system can use OpenGL to efficiently copy images onto the main display. OpenGL also submits GPU programs to the video card on behalf of Core Image. These are just two examples of how higher layers in the system can turn the features of OpenGL to their advantage. Applications that need to create 3D graphics will use OpenGL as a matter of course. 2D applications with very specific, high performance needs might also use OpenGL to communicate to the video hardware.

Core Graphics

As its name suggests, Core Graphics is one of the fundamental graphics systems on Mac OS X. Core Graphics is the proper name of the system, but it is also known by the marketing friendly term Quartz. Quartz has two primary subsystemsthe window server and the Quartz 2D library.

The window server collects images of all the windows on the system, composites them together, and is responsible for the images displayed on all the computer screens. This system is also responsible for working with the computer hardware to collect user events from the mouse and keyboard and see that they find their way to the proper applications. For example, when you click the mouse, the window server determines which window the mouse is over and dispatches the event to the application that owns the window.

Quartz Extreme is a technology that pairs the hardware of the video card with the functionality of the Core Graphics layer. The Quartz Extreme initiative was originally applied to the window server. The Quartz Extreme compositor takes the window images generated by applications and maps them onto OpenGL textures on the video card. The window server draws upon the power of the GPU to combine the window images on to the display. This saves the main CPU from having to do the alpha blending calculations needed to combine the window images.

The second part of Core Graphics, the Quartz 2D library, will occupy most of this book. Quartz 2D is a high-performance, general purpose library for creating 2D graphics that uses an imaging model very similar to the one used by PostScript and PDF. The library supports a wide variety of output devices and takes advantage of hardware acceleration for improved performance.

Core Video

Core Video, which Apple created after many years of experience with QuickTime, helps applications that want to present motion graphics. In its current implementations, Core Video provides two main services. It handles buffer management and timing services. In presenting movies, a computer typically must decompress the frames and then present those images on screen. In the past, QuickTime would usually complete each of these steps at the same time by decoding the image directly into the display buffer. This limited performance because the computer couldn't decompress an image into the buffer until the previous frame had been displayed. Quicktime's handling of buffers also made it difficult to support video encoding techniques which require the computer to decode several frames of the animation at once.

Core Video assists an application in managing several frames of animation at once. The library efficiently moves the buffers to the graphics hardware where the computer can display them on-screen. This decouples the decoding and display stages of the animation loop, allowing each to run as quickly as it can. This also allows the graphics hardware to handle the display of frames, freeing the CPU to decode subsequent frames at the same time.

Core Video also handles timing services. In the complex graphics environments available on the Macintosh, it can be difficult to get the timing of animation just right so that it looks as smooth as possible on the display. Core Video runs a high-priority thread on behalf of the application and uses a callback mechanism that allows the operating system to request animation frames from the application. By doing so, the computer can get the frames of the animation in such a way that it can present them on screen at the optimal time. Applications that present animations can use Core Video to their advantage in the performance benefits it offers.

Core Image

Core Image is a filter-based image processing API. The system allows applications to build chains of filters (actually a directed, acyclic graph), combine them, and apply them to an image all in a single step. The kinds of filters found in Core Image are also often found in popular image processing applications such as Adobe Photoshop or The GIMP (GNU Image Manipulation Program). Core Image includes dozens of image processing filters with the installation of Mac OS X. Application developers can provide their own image filters and can even package those filters so that other applications can use them.

As with Core Video, one of the attractive features of Core Image is its ability to take advantage of the graphics hardware. Many of the filters in Core Image are implemented as GPU programs, and the library can run those filters on a programmable graphics card if one is available. As has been said, however, Core Image does not require programmable graphics hardware to run; it can run its filter effects on the main CPU. The code will take advantage of other hardware features like AltiVec or SSE if they are available.

Applications can use Core Image to add a variety of effects and transitions to both user interfaces and application content.

QuickTime

QuickTime is a cross-platform architecture for working with a wide variety of media formats. It began as a system for presenting synchronized sound and video. Over the years, however, the extensible architecture of QuickTime has broadened its scope to include quite a lot more. At its heart, QuickTime is an excellent base for presenting any kind of time-based media. With the proper components, for example, QuickTime could even be used to present the experimental data captured from chemistry experiments.

QuickTime is a bit unusual in the way it touches on so many other aspects of the system. At the lowest level, QuickTime includes components for working with the video, audio, and data storage hardware on the system. At the highest level, QuickTime provides routines and components that present user interfaces and interact with the userwith lots of other functionality in between.

Of particular interest to 2D graphics developers is the fact that QuickTime contains components that make it easy to import images from popular file formats such as JPEG, GIF, and TIFF. QuickTime includes image processing filters and transitions that are somewhat similar to the ones found in Core Image, although the architecture is older and doesn't employ hardware technology as effectively. QuickTime also can transform 2D graphics and perform alpha channel compositing. In many respects QuickTime is a jack of all trades.

If QuickTime suffers from anything, it is a disconcerting dependence on QuickDraw. Apple is carefully freeing QuickTime from this anchor over time. Some of the features that have traditionally been the province of QuickTime are emerging as dedicated systems in Mac OS X. Core Image has already been described, which handles functionality analogous to the effects and transitions components from QuickTime. Core Video supplements QuickTime's video presentation abilities, and Quartz 2D can transform graphics and composite images with alpha channels. Image I/O is a new architecture for importing and exporting images to files. I/O is a dedicated system for importing and exporting images designed to take the place of QuickTime's image import and export components.

Application users that want to play movies and sounds or integrate a variety of different media should consider working with QuickTime. Some of the newer technologies, like Image I/O and Core Image are only available on newer versions of the Mac OS X. Applications that want to provide the same functionality on older systems can use the similar functionality in QuickTime.


Other Graphics Libraries

The libraries just discussed form the core functionality of the Mac OS X graphics system. Mac OS X also contains a number of additional libraries that are worth a brief mention here. For more information on these libraries you can consult Apple's developer documentation.

ATSUI and Cocoa Text

The Apple Type System for Unicode Imaging (ATSUI) and Cocoa Text systems play a vital role in the creation of graphics that contain text. Both ATSUI and the Cocoa Text system are graphics libraries for combining a block of characters, text style information related to those characters, and a region in which the text can be drawn to create a complete image of the text. These libraries are vital to the correct layout and rendering of Unicode text on the Macintosh system. While we will touch upon ATSUI and Cocoa Text in this exploration of Quartz 2D, the richness of these text layout engines would warrant an entire book in their own right.

QuickDraw

The same QuickDraw library that was vital to the continued success of the Macintosh is still available on Mac OS X. QuickDraw includes the same drawing capabilities today that it has in the past. While Apple plans to maintain binary compatibility with applications that use QuickDraw, the library is now deprecated technology. Apple is no longer changing the QuickDraw code, and the library may become unavailable in future versions of the operating system.

This fact has a profound implication for developers who use the Carbon API. Their applications are probably using QuickDraw as their primary graphics library. If these applications are to grow with the system, developers will have to convert their drawing routines to use Quartz 2D or one of the other libraries in the Mac OS X graphics system.

In classic Macintosh operating systems, QuickDraw played an important role in the windowing system, not only as a graphics library, but also as a collection of routines for dealing with "graphical situations." For example, QuickDraw could tell you if a given point was to be found inside of a particular pixel region. QuickDraw also managed things like the set of displays on the system or the current system cursor. In general terms, QuickDraw provided several of services that were not related to drawing graphics.

In Mac OS X, these same services have migrated to other libraries that are in a better position to run them efficiently. Quartz 2D is, by-in-large, a drawing API and leaves non-drawing tasks to other systems. If you have code that makes use of any of QuickDraw's non-drawing related services, you may need to look to services other than Quartz 2D to find equivalent functionality on Mac OS X.

vImage

The vImage library is part of the Accelerate framework and is another image processing library on the system. While the focus of Core Image is on visual image effects and transitions, vImage contains routines related to more scientific image processing tasks. Here are some of the features found in vImage:

  • Conversion of image data between several different pixel types including conversion between planar and interleaved pixel formats.

  • General Convolution on image data.

  • Apply Fourier Transforms (one and two dimensional, real and complex valued) to a block of data. (The Fourier Transform is a mathematical operation that occurs often in image and signal processing).

  • Apply "morphological" operations on data. Morphological operations manipulate images so that items pictured in the image change their shapes. Usually the items in the image will expand, shrink, or take on other aspects of a bitmap called the morphological kernel.

  • Perform geometric operations on an image (such as scaling and resampling, rotations, and reflections).

  • Generate histograms for the samples in an image. A histogram tells you how many of the pixels in an image are of a given intensity. Histograms play an important role, for example, in some image enhancement code.

  • Alpha channel compositing. (Combining translucent and partially translucent images together)

Looking at this list, you may see many operations that are found in other graphics libraries in Mac OS X. Both Quartz 2D and QuickTime, for example, can be used to rotate an image or perform alpha channel compositing. vImage is a low-level library upon which some of those higher-level libraries are built. Because of this, using the routines in the vImage requires a bit more care on the part of the programmer. vImage can take advantage of multiple processors if they are available and use the SIMD units in those processors as well if they are available.

Java 2D

Mac OS X is an excellent platform for Java development. The Java system available on Mac OS X includes an impressive collection of technologies including the Java graphics library, Java 2D. Java 2D shares a large number of features with Quartz 2D. Both include graphics services for line art, text, and images and include support for alpha channel compositing. The two libraries have some abstractions in common, and many of the drawing techniques that are effective in one library have close analogs in the other library. With so many similarities, it should not surprise you to learn that the Java 2D implementation on Mac OS X relies on Quartz 2D for much of its functionality. In spite of the similarities in their feature sets, Quartz 2D and the Java 2D API use very different programming interfaces. Because it is a completely separate API, one that happens to be implemented using Quartz 2D, but which does not directly expose the functionality of that library, Java 2D is not explored in this book. However, there are a number of excellent resources available on the web and in books to help Java programmers get the most out of Java 2D.

This chapter has covered a lot of ground. Understanding where Quartz 2D fits into the graphics system, both historically and architecturally can help you make effective use of the system. Some time has been spent discussing other graphics technologies that may be of interest, and we encourage you to explore them at your leisure. Writing good graphics code on Mac OS X is often a question of finding the services on the system that most closely match your needs. Hopefully this brief discussion will help you find the services that benefit you.


Chapter 3. Introduction to Quartz 2D

The previous chapter examined some of the history behind the development of the Quartz 2D graphics library. It was mentioned that Apple based the drawing model of Quartz 2D on the graphics features of the PDF file format standard. Quartz 2D, of course, is not alone in taking this path. The most recent evolution of the Java graphics libraries, Java 2D, also relies heavily on aspects of the Adobe imaging model. The GDI+ graphics library, found on Microsoft Windows operating systems, also incorporates some of the Adobe imaging model's elements. Quartz 2D, as a descendent of Display PostScript, however, provides the closest representation of the Adobe imaging model available.

The Quartz 2D imaging model is flexible and powerful. The library meets the needs of professional and consumer applications alike. The complexity of the graphics that Quartz 2D can create are a stark contrast to the relative simplicity of the imaging model. As an application developer, you can begin to make use of the library after learning about the basic drawing elements in the system and a few simple techniques for working with them.

This chapter describes the features of the Quartz 2D graphics library and offers a first look at its imaging model. It also explores the drawing process and look at some of the models and abstractions that Quartz 2D code uses. This exploration culminates in the first code sample. The program creates a simple image that demonstrates many of the fundamental techniques for working with Quartz 2D.


Features of Quartz 2D

Quartz is a fundamental part of the Mac OS X operating system. It is also, by nature, a highly visible one. You will no doubt recognize many of the features of Quartz 2D as hallmark features of Mac OS X. Quartz 2D extends many of these same features to your application. Incorporating it into your application is a vital part of ensuring that your application provides the finest user experience possible. This exploration begins by looking at the features of Quartz 2D that your application can take advantage of.

Device Independence

The Quartz 2D graphics library exemplifies two of the design goals of the Mac OS X graphics system. The first goal is that the graphics system should support as many different devices as possible. It has to work with printers, displays, offscreen bitmaps, image files, and any of a number of other devices that can reproduce drawings. The second goal of the graphics system is that it should make it as easy as possible for your application to take advantage of the power those devices have to offer.

Quartz 2D addresses both of these design goals. First, the library allows programs to establish connections to the full range of supported graphics devices. This opens those devices to any application that works with the library. The computer maintains the connection between an application and a device through an object called a graphics context.

To simplify the drawing process, each graphics context that Quartz creates understands the same drawing commands. In most cases, your application only needs to know how to use this common, abstract drawing model. It can leave the complexities of reproducing those graphics on the output side to the library.

The abstraction allows the application to treat all of the graphics devices uniformly. A Quartz 2D graphics application uses the same drawing code to draw its document in a window that it uses to print the document on a printer. The fact that you don't have to maintain separate code for each device simplifies programming and improves the consistency of the graphics.

To give you an idea of the device independence of the Quartz 2D library, Table 3.1 gives a list of some of the devices that Quartz 2D supports.

Table 3.1. Some of the Devices Supported by Quartz 2D

Type of Device

Description

Notes

Bitmap

Any pixel drawing environment

Examples of bitmap drawing environments include windows, views, and offscreen bitmaps.

Printing

Printers of various types including both raster and PostScript printers

Quartz 2D and the Mac OS X printing system support a wide variety of printer types. The support includes both raster and PostScript printers.

PDF

A stream of PDF data

PDF is a convenient format for recording Quartz 2D drawing commands. Most of the time, the PDF stream will be stored in a file, but Quartz 2D can also create a PDF stream in memory.

OpenGL

An OpenGL Context

Using CGGLContextCreate, Quartz 2D can draw its graphics on an OpenGL context. This makes it easy to incorporate graphics that have traditionally been difficult to draw through the OpenGL API. A notable example is text which OpenGL does not support directly.

Resolution Independence

To some extent, the resolution independence of the Quartz 2D graphics library follows logically from its device independence. The resolution of an ink-jet printer, for example, is usually much higher than the resolution of a computer display screen and Quartz 2D draws to both.

Like device independence, the resolution independence of Quartz 2D stems from the abstract drawing environment that Quartz 2D sets up when it connects an application to a particular graphics device. The abstract drawing environment accepts primitives that are not tied to any particular resolution. It is the graphics context's responsibility to translate the resolution independent primitives to the device's fixed resolution drawing space.

Chapter 4, "The Graphics Context," examines the characteristics of the graphics context and present short code samples that demonstrate how to create contexts for a variety of devices.

Rich Transformations

One of the features of QuickDraw, a feature that distinguished it from its predecessors, was the ability to record a series of drawing commands into a picture and then play the picture back in an arbitrary rectangle. What made this feature so compelling was the fact that QuickDraw would scale the drawing if the source and destination rectangles did not match.

Because QuickDraw recorded its graphics at a fixed resolution, scaling the graphics larger resulted in a jagged representation of the graphic. Scaling a graphic much smaller lost a lot of the detail in the graphic. In spite of these shortcomings, the ability to reproduce QuickDraw graphics at any scale was quite revolutionary at the time. Scaling was not QuickDraw's only coordinate transformation; it could also translate the origin of the coordinate system to move graphics within a port. These two operations are the only coordinate space transformations in QuickDraw. Notably missing is the ability to rotate graphics.

Quartz 2D provides a much richer environment for transforming graphics, wherein an application is free to apply any of three basic transformations to its graphics, translations (moving a graphic along the coordinate axes, similar to changing the origin in QuickDraw), scaling, or rotation. By combining transformations, an application can create complex effects. You also can use these transformations to greatly simplify complicated drawing tasks. Because the drawing primitives in the Quartz imaging model are largely resolution independent, scaling and rotating Quartz 2D graphics does not yield the ungainly characteristic of scaled QuickDraw graphics.

Chapter 5, "Transformations," discusses transformations in Quartz 2D and the routines to combine them.

Integrated Color Management

When a department store puts out a catalog advertising their latest winter fashions, it is important that the catalog correctly reproduce the exact shade of purple in their finest cashmere sweater. The science that ensures the color of that sweater is faithfully reproduced is called color management. This technology is useful in many different environments, not just in the complex world of professional publishing. Applications like iPhoto apply color science principles to ensure that the photographs of your child's kindergarten graduation look as close as possible to the actual event during a computer slide show.

ColorSync was among the first color management systems to debut on personal computers and is an excellent technology that strikes a good balance between features and performance. Apple originally built the ColorSync functionality into the operating system as technology to complement QuickDraw. The library was built as a separate component in part because it would introduce a large number of changes to the QuickDraw imaging model.

Because Quartz 2D began with a clean slate, the library integrated ColorSync color management directly into its drawing model. Every graphics context includes information about its color characteristics. Colors drawn into the context are carefully matched against those color characteristics.

Quartz 2D offers applications a remarkable amount of control over color. You can decide to what extent your application participates in the color management process. If color fidelity is not a critical feature of your application, you can rely on the default behavior of the operating system to setup and manage the color matching environment. If, however, you need to have fine-grained control over the color matching process, your code can examine or change the color characteristics of the context, the colors you provide for drawing, and the colors of incoming images.

Integrated color management plays a role in simplifying the drawing process while supporting a large number of devices. For example, an application is free to use whatever color model is most appropriate for representing its content. Programs that present their graphics on the screen can specify colors in the RGB space of the computer display and the color management system does its best to ensure that the RGB colors are reproduced as faithfully as possible when printed to an ink jet printer. By the same token, printing applications can draw their graphics using CMYK colors while the application can set up the color environment so that the display screen matches the printed results in spite of the colors being specified in a completely different color space.

Transparency and Antialiasing

Computers spend a good amount of time combining smaller graphics elements into larger compositions. Consider, for example, the way that windowing systems combine the separate images of different windows into a single picture on the display. The process of combining graphics is called compositing and is a fundamental operation of the Quartz 2D imaging model.

A Quartz 2D application creates pictures by drawing individual graphics primitives and then uses compositing to combine those elements into the final presentation. One of the most interesting features of Quartz 2D, a feature that is not found in many of the other graphics libraries that preceded it, is the ability to use transparency when compositing graphics together. Using Quartz 2D, your application can ask that a particular graphics primitive be drawn using a translucent paint. When the computer composites that graphic with other graphics in the context, some of the picture behind the primitive will show through the translucent paint.

If you want to see the results of translucent compositing, you need look no further than the Mac OS X UI. The computer uses Quartz transparency in the backgrounds of menus, in the title bars of inactive windows, and to display the scaled icons of the Dock.

To support transparency, Quartz 2D employs a technique pioneered by Thomas Porter and Tom Duff. They co-authored a paper called "Compositing Digital Images," which was part of the 1984 SIGGRAPH conference. The technique that Porter and Duff developed incorporates transparency into an image by providing an additional component to each color. This component is called the alpha channel. The value of the component is interpreted as the level of transparency in the color.

The Core Graphics system integrates alpha channel compositing into the Quartz 2D graphics model. The API of Quartz 2D requires you to specify both the color components and the alpha channel value for each color you create. The Core Graphics windowing system honors the transparency of window graphics. The software and hardware renderers use that transparency when putting together an image for the computer's display.

Not all of the output devices that Quartz 2D can draw to support transparency as well as computer displays do. For example, the PostScript system does not include direct support for transparency. As a result, PostScript printers might have difficulty with transparent graphics. If your application draws to such a device with Quartz 2D, the computer will do its best to ensure that the image is reproduced as faithfully as possible, but the additional effort may require additional computing resources, compromise in the quality of the image, or a combination of both to reproduce the effect.

The alpha channel also plays a role in another important Quartz 2D feature, antialiasing. Antialiasing refers to a technique that softens the jagged edges of computer graphics when they are drawn on pixel-oriented devices like the computer display. The technique decreases the contrast of the jagged edges and makes graphics look smoother. Antialiasing improves the appearance of line art and can provide a marked improvement to the legibility of text. Figure 3.1 uses some text to convey the difference between antialiased and non-antialiased graphics.

Figure 3.1. Non-Antialiased and Antialiased Text

We've magnified the graphics in Figure 3.1 to exaggerate the pixels of the image and make them easier to discern. The non-antialiased text clearly shows the jagged edges, or jaggies, that are characteristic of computer graphics. In the antialiased text, the jagged edges have been replaced with pixels that appear, in the figure, as different shades of gray. In practice Quartz 2D would not use different colors of gray to achieve the effect. Instead, the computer would use black pixels with different levels of transparency. Those pixels only appear gray in Figure 3.1 because they are translucent black pixels printed on a white background.

While Figure 3.1 uses text to demonstrate the effects of antialiasing, Quartz 2D apples the same technique to all of its drawing. To implement antialiasing, Quartz uses a technique called sub-pixel sampling. The computer breaks each pixel into a number of smaller, sub-pixels and uses information about which of those sub-pixels are touched during drawing to decide the transparency level of the larger composite pixel. The net effect is that of increasing the resolution of the graphics device. More information on sub-pixel sampling and antialiasing can be found in most computer graphics textbooks.

Antialiasing is not the only technique that Quartz can use to improve the legibility of on-screen text. The computer also can employ a technique that Apple calls font smoothing. Font smoothing also employs sub-pixels to reduce the jaggedness of text on-screen. Unlike antialiasing, however, font smoothing is based on the physical sub-pixels of an LCD monitor. On such a monitor, each pixel is physically constructed of separate red, green, and blue sub-pixels. The computer can exploit this fact to smooth the edges of text by applying colors to the edge pixels. The different colors turn on and off the LCD sub-pixels, and your brain ignores the colors in favor of perceiving a smooth edge. While font smoothing works well on LCD screens, it is of limited use on traditional phosphor monitors.

Rich Text Support

Mac OS X supports the Unicode text encoding standard and enables developers to create software that can adapt to the rich set of diverse text systems used by cultures across the globe. In this environment, writing software that supports the variations of the written word is quite a complex process. Mac OS X handles the complexity by breaking the text layout and presentation process into a number of discrete steps.

In the first step, the computer converts the text that a user types into a collection of drawings called glyphs. The computer may use the same character to represent the letter "A," but that A will be represented by a very different glyph when drawn in Times than when it is drawn in Helvetica. Once the computer has converted the characters into a series of glyphs, the software arranges the glyphs so that they occur in the proper order defined by the writing system. In the final step, the computer draws those glyphs on a graphics device.

The graphics system in Mac OS X separates the steps of the text presentation process. The complexities of glyphs selection and layout are handled by text processing libraries such as ATSUI and the Cocoa Text system. When the time comes to draw those glyphs on a graphics device, however, Mac OS X turns to Quartz 2D.

For the most part, the text capabilities in Quartz 2D are limited to the final stages of the text pipeline. The role of Quartz 2D largely is to combine the layout information of higher level libraries, with the graphics information it extracts from fonts to draw glyphs on the graphics device. The Quartz context API does include some routines that can take text, usually text encoded using the MacRoman text encoding, through the entire text processing pipeline. Those routines provide very rudimentary text layout support, and their use is discouraged. Applications that use higher-level text libraries enjoy the added flexibility those library offer. The text layout libraries, in turn, can draw their text using all the advantages Quartz 2D has to offer.

Performance

The previous chapter devoted some time to discussing the performance of modern graphics systems and the increasingly important role of dedicated graphics hardware. One of the advantages of a high level graphics library like Quartz 2D is that it isolates your application from many of the complexities of the hardware. Quartz 2D is carefully crafted to take advantage of hardware features like AltiVec and programmable graphics cards. Your application will enjoy the advantages of those hardware services by virtue of the fact that it draws graphics through Quartz 2D.

That doesn't free your application from all responsibility, however. Choices you make in your application and the way you interact with Quartz 2D can have an impact on the library's ability to make use of the hardware. Quartz 2D can help you balance the efficient use of hardware resources to ensure your application runs at the peak of performance on any machine.


The Quartz 2D Drawing Model

Having examined the features of Quartz 2D, this chapter now turns our attention to the way applications use the library to create graphics. To use a library efficiently you need to understand what real-world objects and processes the library models. This is as true in graphics as it is in any computer discipline. Each graphics library has a different collection of drawing primitives, different ways of handling color, and drawing techniques that are particular to that graphics library. All of these give the library a unique character. They not only establish the capabilities of the library, but they also define its limits. Collectively these abstractions and processes are called the imaging model of the library. The next few sections explore the Quartz 2D imaging model. The goal is to introduce some of the important concepts that you have to understand in order to make effective use of the Quartz 2D API. Later chapters will get into the details of putting the imaging model to work for you.

Drawing Primitives

Raster-oriented libraries like QuickDraw create drawings using metaphors such as the "graphics pen." QuickDraw uses the graphics pen to trace out lines or ovals on the graphics device. Quartz 2D, in contrast, uses object-like drawing primitives. Quartz 2D applications construct shapes, images, or text first, and then they ask the library to draw them. The objects take their attributes, colors and the like, from the graphics environment in which they are drawn.

Given the richness of the graphics that Quartz 2D creates, it may surprise you to learn that Quartz 2D really only supports three different drawing primitives, sampled images, line art, and text. The real flexibility of Quartz 2D is the rich set of drawing attributes at your disposal when you draw these primitives, as well as the flexibility you enjoy to use Quartz 2D to position and transform these primitives.

Line art includes many of the same drawing primitives found in other graphics libraries. Using Quartz 2D, your applications create objects called paths. In a way, paths are similar to the outlines printed in a child's coloring book. Those outlines are really just collections of straight and curved line segments that encircle an area of the page. The colored shapes combine to create a finished image. Quartz 2D uses paths in a similar fashion.

When drawing line art, the application creates paths and then colors those paths to create an image. Unlike the outlines in the coloring book, however, the edges of a path are infinitely thin. This is a crucial aspect of paths that makes them a resolution independent drawing primitive. More will be said about paths and crayons later in this chapter. Chapter 6, "Line ArtBuilding Paths," and Chapter 7, "Line ArtDrawing," explore the topic of drawing with paths in detail.

A sampled image is a pixel-based picture. One way to describe the origin of the term is to consider the way a Digital Camera takes photographs. When you take a picture with a digital camera, the camera takes the light entering through the lens and projects it onto a special computer chip called a Charge-Coupled Device (CCD). This device selects light at various points on the chip and records, or samples it. The camera assigns the pixels in a picture different colors for those samples. The resulting sampled images are stored on the camera, often in JPEG format, until you are ready to transfer them to a computer or print them.

When an imaging device like a camera or scanner samples a scene and generates an image, it usually does it at a fixed resolution. Quartz 2D has the ability to re-sample the pixels. As it does so, it can synthesize new pixels based on the color samples. Using this capability the library treats a sampled image as a resolution independent drawing primitive in spite of its fixed resolution. Chapter 8, "Image Basics," describes the resampling process and describes how Quartz 2D represents images. That chapter also describes how to create images by drawing with Quartz 2D. Chapter 9, "Importing and Exporting Images," explores tools for importing and exporting images into popular image file formats like JPEG, PNG, and TIFF.

The third Quartz 2D drawing primitive is text. Quartz 2D treats works with text from a very low-level, drawing-oriented perspective. The library knows how to work with the Mac OS X font system to retrieve the paths it needs to draw glyphs. Once it has the paths in hand, Quartz draws text using the same facilities it uses to draw other line art.

Because Quartz treats text at such a low level, it is impractical for most applications to use Quartz 2D directly to draw text. Mac OS X offers higher level text services in each of the application frameworks. Applications will typically use these higher level text services rather than draw with Quartz 2D directly. Nevertheless, applications with very special text drawing needs may find Quartz 2D's facilities very helpful. Chapter 11, "Drawing Text with Quartz 2D," discusses the Mac OS X text system and the role of Quartz 2D in that system. It also has an example of creating a text special effect using Quartz 2D.

This small set of primitive drawing types may seem rather restrictive at first, but keep in mind that Quartz 2D's drawing model is a descendent of PDF. PDF in turn is an evolution of PostScript. Computers represent the images for everything from cereal boxes to books (this one included!) using one of these technologies. Even with this small set of simple building blocks, Quartz 2D allows you to create very rich and expressive graphics.

The Graphics Context

The bridge that the computer forges between an application and an output device is called the graphics context. Most libraries have some kind of graphics context. QuickDraw, for example, uses a structure called the GrafPort. The Java 2D graphics context is an instance of the Graphics2D class. The opaque data type that represents a graphics context for Quartz 2D is CGContext. Application code keeps track of CGContexts using the CGContextRef data type.

A CGContext keeps track of all the drawing parameters of the graphics environment. One of the most important is the coordinate system in which your application draws its graphics primitives. The CGContextRef also keeps track of the current drawing colors, the antialiasing settings, and many other drawing options. Chapter 4 provides descriptions of the fields of a CGContext.

The first step in any Quartz 2D drawing is obtaining a CGContext. Each output device has a particular type of context. That context understands how to communicate with the device and is familiar with the peculiarities of the device's graphics engine. Your application will create contexts for different devices using different Core Graphics routines. All CGContexts have a common interface that abstracts away the device details which allows applications to treat all devices uniformly. Quartz 2D code sends graphics to an offscreen bitmap or to a high-resolution printer with equal facility. Chapter 4 discusses CGContexts in more detail and demonstrates how to create contexts on many graphics devices.

The abstract drawing interface defines the character of the Quartz 2D drawing model. The sections that follow describe some consequences of supporting a broad range of graphics devices. They provide a high-level overview of drawing with Quartz 2D.

Where Have the Pixels Gone?

Most graphics devices, from display screens to printers, use pixels at some level. Graphics libraries like QuickDraw on the Macintosh, or GDI on Windows, incorporate pixels directly into their drawing models. For example, QuickDraw expresses the size of its graphics pen in pixels. Their coordinate systems address individual pixels, and much of the legend and lore in learning the libraries concern efficient ways to shuffle pixels around.

Quartz 2D draws to many of the same pixel-based devices that QuickDraw supports. Quartz 2D is not limited to pixel-based devices alone, though. Quartz 2D is device and resolution independent. In addition to pixel-based graphics devices, Quartz 2D also supports drawing destinations like PDF files and PostScript printers where pixels are not part of the device's drawing environment. The Quartz 2D imaging model could drive other devices, like laser cutters or pen plotters, that are not pixel-oriented.

Because it supports such a broad range of devices, the Quartz 2D imaging model doesn't rely on pixels. This fact can be shocking and somewhat disquieting for a novice Quartz 2D programmer. This is particularly true of programmers familiar with raster graphics libraries. Working with Quartz requires them to abandon many of the drawing techniques they have relied on for years.

Instead of drawing pixels, a Quartz 2D application draws the three resolution independent primitives we described earlier in this chapter. Quartz 2D transfers those drawing primitives to a raster device such as a window or bitmap with a three-phase process. The phases are drawing, mapping, and rasterization. Figure 3.2 illustrates all three drawing steps applied to a simple drawing.

Figure 3.2. The Quartz 2D Drawing Process

1. DrawingThe application draws a picture in user space. In this case the application creates a path that outlines a square. The corners of the path are specified in user space coordinates.

2. MappingThe application uses an affine transform to describe a mapping between user space and device space. In the figure, the transformation moves the origin of user space to the right. It also rotates the coordinate system counter-clockwise by 30 degrees. The figure shows how the affine transform maps the square path onto the device's pixel grid.

3. RasterizationIn the third step the computer selects which pixels to color. Figure 3.2 uses an algorithm that colors every pixel the square touches. As we will see, Quartz 2D often uses more sophisticated algorithms.

In the first step, drawing, a program creates graphics primitives and sends them to a CGContext. Line art consists of a number of points that define a path. Images draw in rectangles, and text is drawn by placing glyphs at particular points in the drawing area. All of these require the application to specify a location for the primitive in the context's coordinate system.

That coordinate system maintains the resolution independence that is characteristic of the Quartz 2D library. Graphics devices have their own coordinate systems, but those are typically tied to implementation details of the device. To maintain its device independence, a CGContext does not expose the device's coordinate system for creating graphics primitives. Instead, the CGContext supplies a coordinate system that is only indirectly related to the coordinates of the output device. As a program draws each primitive, Quartz 2D maps that primitive from the context's coordinate space to the device's. In the lingo of Quartz 2D, the abstract coordinate system is called user space. This is the coordinate system in which programs construct graphics primitives. The coordinate system of the device goes by the prosaic name device space.

The figure uses black to represent graphics as they are interpreted in user space. The gray portions of the figure are part of device space. You may note that the graphics drawn in user space, the black portions of the figure, are much smoother than those drawn in device space. Keep in mind that user space is a continuous space. It represents graphics mathematically and does not have discrete pixels or a fixed resolution. The device space we've represented in the figure does have a fixed resolution and discrete pixels. The grid in Figure 3.2 represents the boundaries of those pixels.

In the second step, Quartz 2D maps the graphics from user space to device space with a coordinate transformation. The library represents this mapping using a mathematical tool called an affine transformation. In exploring the features of Quartz 2D, it was mentioned that all affine transformations are combinations of translations, rotations, and scalings. Chapter 5 describes the characteristics of transformations and the API that creates them. That chapter describes process of setting up the transformation between user space and the device. At this point, it is enough to realize that applications build their primitives in a coordinate system that is independent of the device. Quartz 2D moves the graphics from user space to the device's coordinate system and begins the rasterization step.

After moving graphics to the device coordinate system, the library decides how to reproduce the graphics on the device. The actual technique used in this final step depends on the particular device. We're now going to explore the common case of drawing to a raster device.

To generate the final image, Quartz figures out which pixels it needs to color and what color the pixels should be. The computer uses paths as the outlines of areas in the device and colors the pixels inside those regions just like the child filling in the outlines of a coloring book. The computer also resamples images, guided by location of pixels on the device. This entire process of converting the drawing primitives into pixels is known as rasterization.

When working with Quartz 2D, applications concern themselves with setting up the transformation from user space to the device and creating graphics primitives that create the image they want to draw. Code inside the library handles the rasterization step, although applications do have some control over rasterization options.

Drawing graphics in one coordinate system only to have the computer move them to another can be the source of confusion for new Quartz developers. This mapping step, however, is crucial to the device and resolution independence of the graphics library. It also can simplify many drawing tasks. For example, drawing rotated text is as easy as changing the user space transformation and drawing the text. There is no need to draw the text into an offscreen graphics environment and then map the pixels back to the display.

This chapter just described the drawing steps that Quartz 2D goes though to create graphics on a raster device. The first two steps of the drawing process, drawing and mapping, are largely device independent operations. The third step, rasterization, really only applies for raster devices like windows and off-screen bitmaps. For output devices that don't have pixels, this third step might proceed differently. For example, a vector plotter or laser cutter might replace the rasterization step with commands that move the plotters pen or the cutter's laser. The advantage of the Quartz model is that the application need only concern itself with how to draw its graphics properly in user space and can use Quartz to handle the details of converting drawings to specific devices.

Drawing graphics with Quartz 2D involves laying down primitives in user space. The final image depends on the order that primitives are drawn in that coordinate system. Quartz 2D handles the layering of graphics images using a technique called the Painter's Model. We discuss this aspect of the drawing model next.

The Painter's Model

An application can easily arrange graphics horizontally and vertically in user space simply using the coordinate system of that drawing environment. To establish the back to front ordering of graphics primitives, the z-order of the elements, Quartz 2D uses an drawing technique known as the Painter's Model.

When an artist is working with oil paints on canvas, they have to be very careful with the order that they lay paint down. For a landscape scene, that painter must first paint the blue sky in the background. He layers in the mountains that form the skyline at the back of the scene. Next, he would carefully add the trees of a forest in the middle depths of the scene. The last elements are the flowers, field, cabin and windmill that live in the picture's foreground.

Quartz 2D works in much the same way. A newly created graphics context is a blank canvas. Applications build graphics by layering graphics primitives onto this canvas. Compositing is the process of combining new graphics primitives with the graphics that are already in the context. Quartz 2D's compositing integrates direct support for alpha channel transparency. Drawings can combine translucent and opaque graphics primitives in the same graphic.

To continue the analogy, you can think of colors in Quartz as the paint used to create the image. Quartz allows each color to have an alpha channel that describes the translucency of that color. Primitives drawn with opaque colors completely replace the drawing context beneath them. Paints with more transparency, corresponding to lower alpha channel values, allow more of the graphics already drawn into the context to show through.

When layering line art into a context, Quartz has to convert paths into a set of pixels to color. This process has been touched upon briefly a couple of times but will now be looked at in more depth.

The Crayon Model

Painters are not the only artists who have inspired the Mac OS X graphics system. Earlier paths were described in terms of a child's coloring book, so why not call the technique of working with paths the Crayon Model?

When a young child uses a crayon to draw a Valentine's Day card, he draws the outline of a heart first and then colors it in. Quartz 2D uses similar steps to draw a heart into a graphics context. In the Crayon Model metaphor, user space plays the role of the Valentine's Day card. Drawing code outlines a heart shape with a path and asks the library to color in that outline.

Chapter 6 discusses paths and how to construct them. For now, you should know that Quartz defines paths using a mathematical construct called a bezier curve. Unlike the outlines drawn in a coloring book, bezier curves describe shapes with infinitely thin boundaries. This means that the curves are resolution independent. The computer can rasterize the curve at any resolution.

After your application creates a path in user space, it can ask Quartz 2D to either fill the inside of that path with a color or trace its outline with a line. Filling the path creates a solid shape. This is the closest analogy to the Crayon Model as it involves coloring between the lines just as you would do in a coloring book. Tracing the outline of the path is called stroking the path. It would be like emphasizing the lines of the coloring book by tracing them with an extra wide crayon.

Quartz 2D uses paths for more than just drawing. Programs can use a path to define a clipping area for a CGContext. Once the application sets up the clipping path, the computer will clip any subsequent drawing in that context to the interior of the path. The computer will even antialias the drawing against the edges of the clipping path.


A Simple Drawing Example

This chapters first code sample demonstrates some of the Quartz 2D concepts discussed. The code sample draws the logo for the Ace of Hearts Bakery, an imaginary establishment renowned this chapter over for its fine artisan breads and pastries. The Ace of Hearts is looking to update its corporate image with a new logo. As you might imagine, the bakery's napkin artwork will be a vital part of the branding initiative. The sample code generates a logo for the baker's approval. Figure 3.3 is the image created by the sample code.

Figure 3.3. The Ace of Hearts Napkin Artwork

For this sample look at the source code that draws the image. The discussion that follows glosses over some of the details in the code to highlight the overall drawing process rather than individual routines. The fundamental techniques used in the code demonstrate how to use the Quartz 2D imaging model.

The sample code draws the image into an offscreen bitmap and generates a PNG file from the results. The code is as simple as the graphic. However, even in this illustration there is occasion to use line art and text, the Painter's Model, the Crayon Model, and some simple transformations. Listing 3.1 is the complete source of our drawing sample.

Listing 3.1. Python Code To Draw The Ace Of Hearts Napkin Artwork

#include <Carbon/Carbon.h>

const float kBlackColor[] = { 0.0, 0.0, 0.0, 1.0};
const float kRedColor[] = { 1.0, 0.0, 0.0, 1.0};
const float kWhiteColor[] = { 1.0, 1.0, 1.0, 1.0};

// Handy routine for centering some text on the origin.
void CenterTextInContextAtOrigin(
           CGContextRef cgContext,
           const  char *fontName,
           float textSize,
           const char *text)
{
   CGContextSaveGState(cgContext);
   CGContextSelectFont(cgContext, fontName,
          textSize, kCGEncodingMacRoman);

   // Save the GState again for the text drawing mode
   CGContextSaveGState(cgContext);
   CGContextSetTextDrawingMode(cgContext, kCGTextInvisible);
   CGContextShowTextAtPoint(cgContext, 0, 0, text, strlen(text));

   CGPoint destination = CGContextGetTextPosition(cgContext);
   CGContextRestoreGState(cgContext);

   // Draw the text
   CGContextSetFillColor(cgContext, kWhiteColor);
   CGContextShowTextAtPoint(cgContext,
           -destination.x / 2.0, -textSize / 2, text, strlen(text));

   CGContextRestoreGState(cgContext);
}
int main(int argc, char* argv[])
{

    const int kImageSize = 512;
    const int kHalfSize = kImageSize / 2;
    const CFIndex kBytesPerRow = (CFIndex)kImageSize * 4;
    const CFIndex kDataLength = kBytesPerRow * kImageSize;

    CGColorSpaceRef kGenericRGBColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    UInt8 *bitmapData = (UInt8*) malloc(kDataLength);
    CGContextRef cgContext = CGBitmapContextCreate(
                   bitmapData,
                   kImageSize, kImageSize,
                   8, kBytesPerRow,
                   kGenericRGBColorSpace, kCGImageAlphaNoneSkipFirst);

    // Set up the color environment
    CGContextSetFillColorSpace(cgContext, kGenericRGBColorSpace);
    CGContextSetStrokeColorSpace(cgContext, kGenericRGBColorSpace);

    // Draw a white background
    CGContextSetFillColor(cgContext, kWhiteColor);
    CGContextAddRect(cgContext,
            CGRectMake(0, 0, kImageSize, kImageSize));
    CGContextFillPath(cgContext);

    // put the origin at the center of the context
    CGContextTranslateCTM(cgContext, kHalfSize, kHalfSize);

    // draw the checkered background
    CGContextSetFillColor(cgContext, kBlackColor);

    CGContextBeginPath(cgContext);
    CGContextAddRect(cgContext,
            CGRectMake(-kHalfSize, -kHalfSize, kHalfSize, kHalfSize));
    CGContextAddRect(cgContext, CGRectMake(0, 0, kHalfSize, kHalfSize));
    CGContextFillPath(cgContext);

    // use a stroke to frame the entire image
    CGContextSetStrokeColor(cgContext, kBlackColor);
    CGContextSetLineWidth(cgContext, 4);
    CGContextAddRect(cgContext,
            CGRectMake(-kHalfSize + 2, -kHalfSize + 2,
                   kImageSize - 4, kImageSize - 4));
    CGContextStrokePath(cgContext);

    // construct and draw a heart shaped path
    CGContextSaveGState(cgContext);
    CGContextTranslateCTM(cgContext, kHalfSize / 2, kHalfSize / 2);

    CGContextMoveToPoint(cgContext, 0, 21);
    CGContextAddCurveToPoint(cgContext, -16, 49.8,
            -53.2, 41.0, -49.6, 5.8);
    CGContextAddCurveToPoint(cgContext, -46, -29.4,
            -9.4, -53.4, 0, -69.8);
    CGContextAddCurveToPoint(cgContext, 9.4, -53.4,
            46, -29.4, 49.6, 5.8);
    CGContextAddCurveToPoint(cgContext, 53.2, 41, 16, 49.8, 0, 21);
    CGContextClosePath(cgContext);

    // ask the computer to fill the path
    CGContextSetFillColor(cgContext, kRedColor);
    CGContextFillPath(cgContext);

    // draw some text on top of the path.
    CenterTextInContextAtOrigin(cgContext, "Zapfino",
            24, "Ace of Hearts");
    CGContextRestoreGState(cgContext);

    CGContextSaveGState(cgContext);
    CGContextTranslateCTM(cgContext,
           -kHalfSize / 2.0, -kHalfSize / 2.0);
    CenterTextInContextAtOrigin(cgContext, "Zapfino",
           18, "Fine baked goods");
    CGContextRestoreGState(cgContext);

    CGContextRelease(cgContext);
    // Create an image from the bitmap data
    CGDataProviderRef dataProvider =
            CGDataProviderCreateWithData(NULL, bitmapData,
                   kDataLength, NULL);

    CGImageRef image = CGImageCreate(
                   kImageSize, kImageSize,
                   8, 32, kBytesPerRow,
                   kGenericRGBColorSpace, kCGImageAlphaNoneSkipFirst,
                   dataProvider,
                   NULL, true,
                   kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);

    // Find the user's desktop folder and create a
    // URL to a file named AceOfHeartsBakery.png on the desktop
    FSRef usersDesktop;
    FSFindFolder(kUserDomain, kDesktopFolderType, false, &usersDesktop);
    CFURLRef desktopURL = CFURLCreateFromFSRef(NULL, &usersDesktop);
    CFURLRef destinationURL =
            CFURLCreateWithFileSystemPathRelativeToBase(NULL,
                   CFSTR("AceOfHeartsBakery.png"),
                   kCFURLPOSIXPathStyle, false, desktopURL);
    CFRelease(desktopURL);

    // Export our image into that png file.
    CGImageDestinationRef exportDestination =
            CGImageDestinationCreateWithURL(destinationURL, kUTTypePNG,
                    1, NULL);
    CGImageDestinationAddImage(exportDestination, image, NULL);
    CGImageDestinationFinalize(exportDestination);

    CFRelease(exportDestination);
    CFRelease(destinationURL);
    CGImageRelease(image);
    free(bitmapData);
    CFRelease(kGenericRGBColorSpace);

    return 0;
}

The source code begins by defining some color constants. Note that each color is specified using four floating point numbers. This sample uses RGB colors, so the four components are the Red, Green, Blue, and Alpha (transparency) components for each color. The valid range for color components is between 0 and 1.0.

Following this is the definition of a short routine called CenterTextInContextAtOrigin. We'll look at the details of this routine's implementation shortly, but the name is fairly descriptive. The routine draws a text string with a given font and size centered on the origin (of user space). To use this routine to draw text at an arbitrary location on the device, we have to change the location of user space using transformations.

Creating the CGContext

As mentioned before, Quartz 2D requires your application to create a CGCon-text, the drawing space that will receive drawing commands. This code sample creates its image in an RGB bitmap. After defining some constants that describe the bitmap you want to create, the code creates an object that identifies color space of the bitmap. This code is designed to save its image into a file on disk and later to display it on screen. An RGB color space is used. To do that, use the routine CGColorSpaceCreateWithName and the name constant kCGColorSpaceGenericRGB. This creates an instance of the generic Macintosh RGB color space. From here the sample creates a bitmap graphics context using CGBitmapContextCreateWithColor. The code looks like this:

CGColorSpaceRef kGenericRGBColorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
UInt8 *bitmapData = (UInt8*) malloc(kDataLength);
CGContextRef cgContext = CGBitmapContextCreate(
                           bitmapData,
                           kImageSize, kImageSize,
                           8, kBytesPerRow,
                           kGenericRGBColorSpace,
                           kCGImageAlphaNoneSkipFirst);

// Set up the color environment
CGContextSetFillColorSpace(cgContext, kGenericRGBColorSpace);
CGContextSetStrokeColorSpace(cgContext, kGenericRGBColorSpace);

This code uses malloc to obtain memory for the offscreen bitmap and then creates the Quartz drawing context on that memory. For more information on drawing into off-screen bitmaps, see Chapter 12, "Drawing Offscreen." That chapter also explains the various parameters to the CGBitmapContextCreate routine.

The last two lines of this section of the code establish the color environment for subsequent drawing. In this case Quartz 2D is told to use the generic RGB color space for both lines and fills in the graphic.

The First Bit of Drawing

The first bit of drawing in this code sample paints the entire context with a white rectangle. That code looks like this:

// Draw a white background
CGContextSetFillColor(cgContext, kWhiteColor);
CGContextAddRect(cgContext, CGRectMake(0, 0, kImageSize, kImageSize));
CGContextFillPath(cgContext);

Recall that in the Painter's Model graphics are constructed from the back to the front. This code is the first to draw and fills the entire background with opaque white. To do that, it first builds a rectangle that outlines the entire drawing surface and then asks the context to fill that rectangle. More information about constructing paths, like the rectangle in this system, is covered in Chapter 5. Chapter 7 discusses the primitive drawing operations, fill and stroke, along with the context parameters that affect them.

Setting up the Coordinate System

When Quartz 2D creates a drawing context on a device, it sets up the coordinate system of user space so that its origin is in the lower left corner of the device. From that origin, the positive X axis points to the right, and the positive Y axis points upward. When working with a bitmap context, Quartz 2D will scale the coordinate system so that one unit of user space covers the same distance of one pixel on the output device. This location and orientation is illustrated in the left half of Figure 3.4.

Figure 3.4. Changing the Location of User Space on the Bitmap

Certainly you could draw the napkin artwork with the coordinate system in this location. The artwork, however, has a symmetry about the center of the drawing that might be exploitable. With that in mind, use the ability of Quartz 2D to change the location of user space, relative to the destination coordinate system. The sample code accomplishes this with this code:

// put the origin at the center of the context
CGContextTranslateCTM(cgContext, kHalfSize, kHalfSize);

The end result of this line is that the origin of user space is moved to the point (256, 256) in its current coordinate system. In this case, the end result is that the origin of user space moves to the center of the bitmap.

Drawing the Checkerboard

Now continue to apply the Painters Model to the artwork. To draw the image properly, you need to draw the graphics elements in the proper order. We begin with the rest of the background consisting of the checkerboard motif and the black frame that surrounds the image. These elements are line art, and they'll be created using the Crayon Model. The first step is to specify a path in user space to color. Secondly, you ask Quartz 2D to either stroke it or fill it. Here is the code that draws the background:

// draw the checkered background
CGContextSetFillColor(cgContext, kBlackColor);
CGContextBeginPath(cgContext);
CGContextAddRect(cgContext, C
    GRectMake(-kHalfSize, -kHalfSize, kHalfSize, kHalfSize));
CGContextAddRect(cgContext, CGRectMake(0, 0, kHalfSize, kHalfSize));
CGContextFillPath(cgContext);

// use a stroke to frame the entire image
CGContextSetStrokeColor(cgContext, kBlackColor);
CGContextSetLineWidth(cgContext, 4);
CGContextAddRect(cgContext,
    CGRectMake(-kHalfSize + 2, -kHalfSize + 2,
            kImageSize - 4, kImageSize - 4));
CGContextStrokePath(cgContext);

This code first stores the color chosen for the fill in the graphics state of the context. For the background of the napkin, both the frame and the checkerboard motif are drawn in black. This equates to the RGB color (0, 0, 0), and given that our paint is intended to be completely opaque, we also use 1.0 for the alpha channel. Next, the code begins the path that will draw both squares of the checkerboard in the background.

Recall from the brief discussion that a path can contain disjoint areas. A CGContext contains a couple of paths. One of these, called the current path, is simply the path under construction in the context. The call to CGContextBeingPath clears the current path in the context and prepares it to receive new drawing commands. Each of the calls to CGContextAddRect adds a rectangular area to the current path. The utility routine CGRectMake simply fills out a CGRect structure that Quartz 2D uses to represent rectangles. The four parameters to this routine are the X and Y locations of the rectangle's origin (the lower left corner of the rectangle), followed by its height and width.

Once the code constructs the path, it calls the CGContextFillPath context method. This routine fills the current path with the current fill color. The fill method also empties the context's current path. Because the path has disjoint sections, both of the filled background rectangles are drawn in one drawing operation.

To demonstrate the use of a stroke, the code also adds a frame around the entire image. The width to use when stroking a path is part of the graphics state in a drawing context, and the code sets it to four units wide by calling CGContextSetLineWidth. The computer centers the stroke path with half of the stroke width on either side of the path, so the rectangle used to define the path must be inset from the edge of the image by half the stroke width. Again CGContextAddRect is used to put a rectangle into a current path. The call to CGContextStrokePath draws the frame and, of course, clears the current path of the context.

Drawing the Heart

By now the process the code uses to draw the heart of the napkin should be familiar to you. The next step is to create a heart shaped path in user space and then ask the computer to fill it. Up to this point, the paths shown have been formed from simple rectangles. The heart path is a bit more complex than that. It consists of four curved segments, two for each side of the heart. Bezier curves will be used to construct the heart. The discussion of bezier curves and how they define the outlines of paths is given in Chapter 6. The following illustration shows the path that the points trace out.

In Figure 3.5 the coordinate axes have also been drawn. Notice that the curves that define the heart path define it centered at the origin. You could move the heart to a different location by adjusting the points of the path so they define a heart some distance away from the origin. Another option, however, is to simply move the origin before drawing the heart, which is the approach the source code takes. The code that draws the heart and the text on top of it looks like the following.

Figure 3.5. The Heart Shaped Path All Alone

// construct and draw a heart shaped path
CGContextSaveGState(cgContext);
CGContextTranslateCTM(cgContext, kHalfSize / 2, kHalfSize / 2);
CGContextMoveToPoint(cgContext, 0, 21);
CGContextAddCurveToPoint(cgContext, -16, 49.8, -53.2, 41.0, -49.6, 5.8);
CGContextAddCurveToPoint(cgContext, -46, -29.4, -9.4, -53.4, 0, -69.8);
CGContextAddCurveToPoint(cgContext, 9.4, -53.4, 46, -29.4, 49.6, 5.8);
CGContextAddCurveToPoint(cgContext, 53.2, 41, 16, 49.8, 0, 21);
CGContextClosePath(cgContext);

// ask the computer to fill the path
CGContextSetFillColor(cgContext, kRedColor);
CGContextFillPath(cgContext);

    // draw some text on top of the path.
CenterTextInContextAtOrigin(cgContext, "Zapfino", 24, "Ace of Hearts");
CGContextRestoreGState(cgContext);

This code indirectly introduces another important aspect of the CGContext, the graphics state stack. Some of the fields of the graphics state of a CGCon-text have already been shown. The current fill and stroke colors, for example, are part of the graphics state. The fields of the graphics state are stored in a stack, and the call to CGContextSaveGState pushes a copy of the current values of those fields onto the stack. The application can then modify the settings to do some drawing. To restore the old settings the code pops the stack using CGContextRestoreGstate, and the older settings are restored.

The current mapping between user space and the device is also part of the graphics state of a CGContext. When the heart's code snippet begins, the origin of user space is still set to the center of our bitmap. The code saves all the fields in the graphics state, including the location and orientation of user space with the call to CGContextSaveGState. It then changes the origin with a translate call and draws both the heart and the text. When that is done, the code calls CGContextRestoreGstate. This returns the origin of user space back to the center of the bitmap.

The actual drawing of the heart path begins with a call to CGContextMoveToPoint. This call identifies the starting point of our heart-shaped path and corresponds to the pointy corner at the top of the heart. The following calls to CGContextAddCurveToPoint append curved segments to the path. The first two points in each call define the shape of the curve, and the last point is that segment's end point. Notice that the last point of the path matches the first point. From there it is simply a matter of setting the current fill color to red and using CGContextFillPath to paint the heart.

Drawing the Text

The sample code draws text by calling a sub-routine defined at the beginning of the source file:

CenterTextInContextAtOrigin(cgContext, "Zapfino", 24, "Ace of Hearts");

This call tells the routine to draw the text "Ace of Hearts" using 24 point Zapfino. The font name, in this case, is actually the PostScript name of the font. In the case of Zapfino, the PostScript name and the family name of the font happen to be the samebut that will not be the case for all fonts. For example, if you wanted to change this code so it draws in the plain style of the Times font, you would have to use that font's PostScript name, Times-Roman.

The Font Book application on Mac OS X can tell you the PostScript name of any font that is active on your system. Select a font and choose "Show Font Info..." from the Preview menu.

The routine that draws the text looks like the following:

void CenterTextInContextAtOrigin(
           CGContextRef cgContext,
           const  char *fontName,
           float  textSize,
           const  char *text)

{

   CGContextSaveGState(cgContext);
   CGContextSelectFont(cgContext, fontName,
           textSize, kCGEncodingMacRoman);

   // Save the GState again for the text drawing mode
   CGContextSaveGState(cgContext);
   CGContextSetTextDrawingMode(cgContext, kCGTextInvisible);
   CGContextShowTextAtPoint(cgContext, 0, 0, text, strlen(text));

   CGPoint destination = CGContextGetTextPosition(cgContext);
   CGContextRestoreGState(cgContext);

   // Draw the text
   CGContextSetFillColor(cgContext, kWhiteColor);
   CGContextShowTextAtPoint(cgContext, -destination.x / 2.0,
           -textSize / 2, text, strlen(text));

   CGContextRestoreGState(cgContext);
}

In describing the text capabilities of Quartz 2D, it was noted that Quartz 2D does not have very advanced text handling capabilities itself. Applications would normally use higher-level utilities like ATSUI or the Cocoa text system to draw text through Quartz 2D. For this simple example, however, the rudimentary capabilities of Quartz 2D will suffice. The call to CGContextSelectFont tells Quartz to draw text using Zapfino and that the text specified will use the MacRoman encoding system.

To center the string, you need know how long the text is. In a more sophisticated application the measuring of text would best be left to a higher level text library. For this simple program, Quartz is asked to draw the text using the invisible text drawing mode. The context keeps track of the "next" location where it will draw text, which you use to your advantage.

When the sample draws the text using the invisible text mode, the computer calculates a point at the end of the text but does not draw anything. A width measurement is obtained for the text by taking note of how far the text position changes. This offers an approximate measurement for the width of the text. A more sophisticated text system could calculate a more accurate width for our text. This measurement technique is sufficient for our needs here.

The sample code saves the graphics state so it can restore the text drawing mode after measuring the text; it then sets the current text drawing mode to use invisible text and draws the text string at the origin. This adjusts the current text position, which the code retrieves from the context. The code restores the graphics state, thereby returning the text drawing mode to its previous value. Finally, the code draws the text again but at a point that is shifted to the left by half the text width. This centers the text on the origin!

The second set of text is drawn with this same routine after the origin is adjusted to a new portion of the napkin graphic.

Writing a PNG File

The final lines of the source code create a CGImage out of the graphic and then save that image into a PNG file on the desktop, as follows:

// Create an image from the bitmap data
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(
    NULL, bitmapData, kDataLength, NULL);
CGImageRef image = CGImageCreate(
           kImageSize, kImageSize,
           8, 32, kBytesPerRow,
           kGenericRGBColorSpace, kCGImageAlphaNoneSkipFirst,
           dataProvider,
           NULL, true,
           kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);

// Find the user's desktop folder and create a
// URL to a file named AceOfHeartsBakery.png on the desktop
FSRef usersDesktop;
FSFindFolder(kUserDomain, kDesktopFolderType, false, &usersDesktop);
CFURLRef desktopURL = CFURLCreateFromFSRef(NULL, &usersDesktop);
CFURLRef destinationURL =
           CFURLCreateWithFileSystemPathRelativeToBase(NULL,
                   CFSTR("AceOfHeartsBakery.png"), kCFURLPOSIXPathStyle,
                   false, desktopURL);
CFRelease(desktopURL);

// Export our image into that png file.
CGImageDestinationRef exportDestination =
    CGImageDestinationCreateWithURL(
            destinationURL, kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(exportDestination, image, NULL);
CGImageDestinationFinalize(exportDestination);

Chapter 8 discusses all the details of creating a CGImage. The basic idea in the code is to tell the image where to get its data using an instance of the CGDataProvider opaque data type. The image is then created by supplying many of parameters that define how the image data is laid out in memory.

The code in the middle, which determines the destination URL for the image, is Carbon framework code, not Quartz 2D. The call to FSFindFolder locates the user's desktop folder, and he converts the FSRef returned by that routine into a CFURL. We then tack the file name AceofHeartsBakery.png to the end of that URL to create the destination for our image.

To export the image, we use the techniques described in detail in Chapter 9. This sample uses Image I/O to export the image. The code creates an instance of the CGImageDestination opaque data type and indicates that the destination is used to store a single image. It then adds the image we've created to that destination and writes the image file with a call to CGImageDestinationFinalize.

The remainder of this code sample cleans up the various objects created as part of drawing the image.

All in all, this is a rather simple drawing example. Perhaps it is a bit more complex than it strictly needs to be to get the resulting image, but the code does demonstrate many of the techniques outlined in this chapter. It offers a glimpse into the device independence of the API, the use of color and color spaces, the Painter's Model for layering, and the Crayon Model for drawing. Understanding this drawing model is essential to using Quartz 2D effectively. This concludes the overview of the drawing model, and attention now turns to examining the Quartz 2D graphics model in detail.


Chapter 4. The Graphics Context

A Core Graphics drawing context, or CGContext, is the fundamental connection that Quartz 2D forges between your application and a graphics device. The system creates contexts for devices as diverse as an inexpensive black and white inkjet printers to a large, high resolution, color displays. In spite of the differences between the devices, the CGContexts provide a single drawing API that is common to both contexts. In most instances, your application need only concern itself with how to use this abstract drawing API and can leave the details of the underlying drawing destination to the system.

The graphics context is a central player in the Quartz 2D API and we spend quite some time in later chapters discussing specific functionality your application accesses through the CGContext API. The context maintains a complete drawing environment including the coordinate space and a large number of settings that act as parameters to drawing commands.

Your application will send all of its drawing commands to the graphics context. The previous chapter introduced the abstract drawing environment, user space, in which your application builds its graphics. The context is the object that defines user space and manages the mapping between its own drawing environment and the physical drawing mechanics of the output device. Programs change the mapping between user space and the device by calling routines in the CGContext API.

Understanding the role of the graphics parameters that make up the graphics state of a context is essential. Quartz 2D uses these parameters to manipulate basic drawing and to create special effects. Equally important, however, is understanding how to change those values efficiently. Quartz 2D manages its graphics state with a stack. This may seem unusual if you are used to using other graphics libraries.

This chapter provides a broad reaching overview of the functionality in a CGContext. Toward the end, you will see the different ways your application might create or receive a graphics context. The graphics state stack is discussed and a brief glimpse at some of the parameters found in the graphics state is provided. Let us start by exploring the features of the context's drawing environment, user space.


User Space and the Page

An important feature of Quartz 2D is its device and resolution independence. To provide those features, Quartz 2D defines a drawing environment called user space.

User space is an idealized drawing environment that provides a level of indirection to the drawing process. Your application creates its graphics in this idealized drawing environment, and the computer transfers those drawings to the output device as optimally as possible. Two features that demonstrate how this is an idealized graphics environment are the environment's independence in both resolution or color space. Neither the drawing space itself nor the primitives you place into it are tied to a particular resolution. Applications specify colors from a virtually limitless number of color spaces. In contrast, computer displays and printers have both fixed resolutions and set color spaces.

Because an abstract drawing environment has a nice name, you might also like to have a nice name for the abstracted drawing destination. For this, Quartz 2D draws upon its legacy. As the descendent of an imaging system, which laser printers used to describe drawings destined for paper, the generalized name for the drawing destination is called the page. In actuality, the page could be a window, an offscreen bitmap, or (heaven forbid) even an actual page on a printer! Regardless of the destination, however, Quartz 2D literature will often speak of drawing on the page as a means of sending graphics to any of a number of graphics devices.

User Space Coordinates

Like most other graphics libraries. Quartz 2D allows you to create and position graphics primitives with a Cartesian coordinate system. In fact, when working with graphics primitives, the coordinates you provide most often identify locations in user space. However, there are some aspects of this coordinate space that may seem unusual to someone familiar with traditional screen-based graphics libraries.

The first unusual feature is that the coordinate system of user space does not have a fixed orientation, or scale, relative to device space. When Quartz creates a context on a given device, it puts user space into a default orientation on the page. From there, programs are free to change that orientation to suit their drawing needs.

Most screen-based computer graphics libraries place the origin of their coordinate system in the upper left corner of the display. This was done to make computer hardware easier to implement. With the coordinates in the upper left corner, subsequent bits in the frame buffer would fall in the same order that the CRT monitor's electron beam would scan the screen. Because user space is an idealized drawing environment, however, its default orientation is a coordinate orientation based on traditional mathematics. Figure 4.1 demonstrates this difference.

Figure 4.1. Orientations of Quartz and Traditional Coordinate Systems

The diagram illustrates the location of the origin of the coordinate system and the direction of the positive x and y axes. The orientation of the typical screen graphics library is shown on the left side of the figure. Graphics systems like QuickDraw place their origin, the point (0, 0), in the upper left corner of the device. From that vantage point, increasing y coordinates extend down the screen as shown in Figure 4.1. This axis orientation is often called a left-handed coordinate system.

In Quartz 2D, in contrast, the default location of the origin is the lower left corner of the page. From there the increasing the y coordinates extend upward on the device. This is illustrated in the right half of Figure 4.1. This is commonly referred to as a right-handed coordinate system.

To understand the difference between left-handed and right-handed coordinate systems, imagine a third axis, the z coordinate axis, extends from the screen to the tip of your nose. If you were to grasp this axis with your hand so that your thumb points away from the screen, with either hand. your fingers would curl in opposite directions. With your left hand, your fingers curl in a clockwise direction. In a left-handed coordinate system you turn the x axis clockwise to make it meet the y axis. In gripping the z axes, the fingers on your right hand curl counter-clockwise. In a right handed coordinate system, you have to rotate the x axis counter-clockwise for it to meet the y axis.

The difference in the default orientations of these two coordinate systems can be a real source of confusion for programmers making the transition from other graphics libraries. When porting code from Windows, or Carbon, programs have to take the different y axis into account.

Keep in mind, however, that this is only the default orientation for the coordinates. This is the orientation of the axes when the Quartz creates a new context and attaches user space to a device. Programs can use the CGContext API to reorient the coordinate space at any time. Chapter 5, "Transformations," focuses on the ability to reorient user space and describes how applications can turn that ability (no pun intended) to their advantage.

This ability to reorient the coordinate axes can be a handy technique to apply when changing code from QuickDraw to Quartz 2D. The basic idea is to reorient the Quartz coordinate axes so that they match the default location and orientation of the QuickDraw coordinate system. Your drawing code then uses the same coordinates with Quartz that it used with QuickDraw.

The HIView system, for example, employs this technique so that Carbon programmers can reuse drawing code from their custom controls when implementing modern HIViews to replace them.

Changing the coordinate axes like this does introduce a few challenges, however. Quartz 2D relies on orientation of the axes when it draws text and images. Those graphics may draw upside down without careful planning. The HIView system actually includes a routine, HIViewDrawCGImage, to handle this very situation. Should you decide to port your QuickDraw code by changing the axes, keep in mind that Images and Text may require special attention.

The relationship between user space and the page is not defined by the orientation of the coordinates alone. In considering the way graphics move from user space to the page, you also have to consider the scale of the coordinate system.

Coordinate axes usually have some unit of measurement associated with them. In pixel-based graphics libraries like QuickDraw, the unit of measurement is the pixel. Each unit along the coordinate axis corresponds to the number of pixels between that coordinate and the origin. When Quartz 2D creates a context on a bitmap device, be it a window or an offscreen bitmap, it sets up user space so that its scale corresponds to the underlying pixel coordinate system of the bitmap. In other words, the computer scales user space so that one unit in user space corresponds to one pixel in the bitmap.

Quartz, however, is a device independent API. Mac OS X can create a CGCon-text for a device that doesn't even have pixels. This leads to a bit of a quandary. When working with a device that doesn't have pixels, say a PostScript laser printer, there is no fundamental unit to apply to the coordinates of user space. There is no obvious measurement standard.

The Quartz imaging model descends from a graphics system used to print on paper. It follows, therefore, that a natural scale for the imaging model was the same scale used to describe paper. The Quartz imaging model, therefore, defines the units of user space in terms of inches.

Quartz doesn't use the inch as the basic unit of user space, however. Instead, it uses a unit of measurement called the Point. The naming of the Point is rather unfortunate as it is easy to confuse it with the term "point" referring to a location in the coordinate system, a point in space. More accurate terms for this unit of measurement might be the Typographer's Point or Printer's Point.

"Printer," in this case, doesn't refer to the modern electronic device. The term actually hearkens to the hearty craftsman of old who printed pages one at a time by inking cast lead blocks and then feeding single sheets into a manual printing press. The Printer's Point evolved as a unit of measurement describing the relative sizes of different typefaces. Modern computers uphold this tradition to this day. When you are working in your favorite word processor and want to select a font size, you might choose a 12-point font for body text, but a 24-point font for a title.

Over the years the point has referred to any of several different measurements. There have even been regional variations and competing standards that each defined a point to be a different unit of measurement. Quartz 2D follows a precedent set by PostScript and defines a Point to be exactly [image] of an inch.

When the computer initially creates a CGContext on a device that is not a bitmap device, it scales the coordinate system so that one unit along the coordinate axes in user space corresponds to one point on the graphics device. That means that if you drew a line in user space from (0, 0) to (0, 72) you would have a vertical line that is one inch long.

Floating Point Geometry

The positions in user space use floating point values to represent the coordinates. The locations your application works with can include fractional positions. You can, for example, draw a line from (1.23957, 23.3338) to (39.4727, 8.8282). The computer will do its best to represent that line on whatever device it is drawn.

The resolution independence of user space is, in part, taken from the use of floating point coordinates. User space is a "dense" coordinate space where the distance between coordinate positions the computer can represent is very small. This does have some consequences that may not be immediately apparent. For example, when working with floating point values, the x coordinates 1.000 and 1.001 are very close together but not equal. The program logic of graphics code that used to work fine for integer coordinates may have to change to accomodate floating point coordinates.

As an idealized drawing environment, user space extends to the limits of the computer's floating point number representation. For practical purposes this means that user space is, in essence, unbounded. When Quartz 2D maps a graphic from user space onto the destination device, any drawing that falls outside the imaging area of device space is simply clipped away.

The fundamental structure for storing a location in a Quartz coordinate system is the CGPoint structure. There is also the CGSize structure for storing width and height dimensions, and the CGRect structure, which combines a point and size to represent a rectangle. The declarations of these structures as well as routines for creating and manipulating them can be found in the CGGeometry.h header file.


The Graphics State

The CGContext is responsible for keeping track of a number of variables that affect the way the computer draws graphics. Collectively these variables are known as the context's graphics state. The drawing contexts of most graphics libraries maintain some kind of state information: the current pen color, pixel pattern blending modes, or even the bounds of the drawing surface are all examples of graphics state used in pixel drawing libraries. The information in the CGContext state is very similar.

The CGContext, however, does not store the fields of its graphics state in the same way that other systems store theirs. Most graphics libraries maintain a single set of state variables. If you want to change one of those variables temporarily, you have to store the old value, change the field, draw your graphics, and restore the old value from your saved copy. As you draw your graphics, if you have to change many different state variables, this process can become tedious and error prone.

If you look through the CGContext API, there are not many methods that retrieve the values of fields in the graphics state. Instead of relying on accessor routines, Quartz uses a stack to save and restore the values of most of the fields in a context's graphics state.

The Graphics State Stack

The CGContext maintains a stack. Each frame of the stack contains some of the fields in the graphics state of the context. When an application draws into the context, Quartz 2D retrieves its drawing parameters from the topmost frame. When the application changes the value of one of the parameters, the library only changes that value in the frame at the top of the stack.

The Quartz 2D API includes two routines that manipulate the graphics state stack. The routine CGContextSaveGState asks the computer to create a new stack frame and push it onto the stack. When Quartz creates the stack frame, it duplicates the frame at the top of the stack. The new frame contains the exact same values as the previous one. This new frame is pushed onto the top of the stack.

In the course of drawing, the application is free to modify the variables in the new stack frame to suit its purposes. Only the fields in the topmost stack frame will change while the code issues drawing commands. To retrieve the old values for fields in the graphics state, the application calls CGContextRestoreGState. This routine pops the topmost frame off the stack and discards it. The net effect is that the graphics state that was in force when CGContextSaveGState was called becomes the current graphics state again.

Consider the following snippet of pseudocode which demonstrates this technique:

// Set the stroke color to red
CGContextSetRGBStrokeColor(cgContext, 1, 0, 0, 1);

// Save the current graphics state
CGContextSaveGState(cgContext);

// change the current stroke color to blue
CGContextSetRGBStrokeColor(cgContext, 0, 1, 0, 1);

// ... drawing here would use the blue stroke color

// restore the old graphics state settings
CGContextRestoreGState(cgContext);

// ... drawing here would use the newly restored red stroke color

This code changes the current stroke color to a device RGB red then calls CGContextSaveGState. After doing so, both of the frames at the top of the stack indicate that the current stroke color should be red. Next, the code changes the current stroke color to blue. Now the topmost stack frame indicates that Quartz 2D should draw with blue while the second frame still calls for strokes to be drawn in red. Any drawing that happens at this point would use the blue color at the top of the stack for stroked paths. Finally, the code calls CGContextRestoreGState. This pops the stack frame with the blue color off the stack and discards it. This returns the frame with the red stroke color to the top of the stack. Because drawing commands use the parameters in the topmost stack frame, any strokes drawn after restoring the GState will draw in red.

There are a number of variables that make up the graphics state stack. Subsequent chapters will explore the details of how various fields in the graphics state affect drawing. The remainder of this chapter, provides an overview of the fields of that stack, as well as fields of the context that are not stored as part of the graphics state stack.

The Current Transformation Matrix (CTM)

As discussed in the previous chapter, your application draws its graphics in user space, and Quartz 2D maps those graphics onto the drawing device. The CGContext stores the information necessary for Quartz to properly perform this mapping as part of the graphics state that Quartz stores on the stack.

The computer converts points from user space to device space with a mathematical construct called an affine transformation. We discuss the details of affine transformations in Chapter 5. In understanding the role of that transformation in the graphics state, all you really need to know is that Quartz represents affine transformations using a collection of six floating point numbers in a matrix.

The matrix that stores the current mapping from user space onto the destination device is called, appropriately enough, the Current Transformation Matrix or CTM. In truth, the current matrix is the matrix in the graphics state at the top of the graphics state stack.

The application can change the CTM by calling methods on the CGCon-text. The three most common routines are CGContextTranslateCTM, CGContextScaleCTM, and CGContextRotateCTM. As discussed in Chapter 5, all the affine transformations Quartz 2D supports can be built from these three primitives. The CGContext also has methods for concatenating a new transformation onto the CTM or even replacing the CTM wholesale with a self-made matrix.

Clipping Information

The current clipping settings are also part of the graphics state. The CGContext has methods to change the current clipping area. Your application can change this area by constructing a path or by providing an image mask. If you specify a path, then Quartz will limit drawing to the interior of the path. If you specify an image mask then the computer will map that image onto the destination device and use the pixels of the mask as a screen for subsequent drawing.

In either case, Quartz treats your application's new clip settings a bit differently than it does the other fields of the graphics state. Each new clipping area, be it a path or an image, does not replace the clipping area in the graphics state. Instead, Quartz 2D intersects the new area with the clipping area already established in the context. The new clipping area is set to those areas where the new clip and the old clip intersect.

To put it more simply, if you only use the methods of the context that specify the new area, your application can only shrink the clipping area. The only way to get back to the larger area is to take advantage of the fact that the clipping area is part of the graphics state. If an application wants to temporarily clip out some drawing, it must save the current clipping state by saving the GState on the graphics stack. After changing the clip and drawing, recovering the old clipping area is as simple as popping the graphics state stack back to its original position.

Text Settings

The CGContext maintains a number of variables in its graphics state related to text drawing. The text state includes the usual suspects: the current font, text size, and the current text position. An interesting field seen in the sample code of the previous chapter was the text drawing mode that controls whether the text your application draws is filled, stroked, used to change the clipping area, or even not drawn at all.

Another interesting text-related field is the Text Matrix. The text matrix is an additional transformation that Quartz applies only to text. The font defines each glyph in its own space. The text matrix is the transformation that Quartz uses to map the glyph from its definition space to user space. The current text position, which played a role in the previous chapters sample code, is part of the text matrix.

The graphics state contains a field that controls whether or not Quartz should apply font smoothing to text you draw in your context. See the topic "Antialiasing and Font Smoothing" later in the chapter for more details.

Mac OS X also allows the user to set font smoothing settings through the Appearance system control panel. If the user has chosen to turn off font smoothing in that control panel, Quartz ignores the smoothing settings of your CGContext and will not apply font smoothing at all.

Line Art Settings

There are a large number of settings maintained by the graphics state that come into play when drawing line art. The following table offers a list of settings and a short description of each. More information on the individual settings can be found in the chapter on drawing line art.

Table 4.1. Line Art Graphics State Fields

Category

Graphics State Field

Description

Drawing Color

Stroke Color

The color the computer uses when drawing a stroke along a path.

Fill Color

The color the computer uses when filling a path.

Stroke Pattern

This field sets the pattern to use when stroking along a path.

Fill Pattern

As with the stroke pattern, Quartz allows you to set the current fill field to a pattern as opposed to a color.

Pattern Phase

A pattern is a repeating series of pictures. The phase describes the extent to which that pattern is shifted relative to the origin.

Stroke Attributes

Line Width

This is the width of the line used when stroking a path.

Line Dash

The line dash is a dotted line pattern used with a stroked path.

Line Cap

Quartz can add optional square or round caps to the ends of stroked paths.

Miter Limit

When two segments of a stroked path meet, the computer can create a miter join between the two segments. This parameter controls the maximum length that the miter will extend from the center of the stroke. (For more information on miter joins, see Chapter 7, "Line ArtDrawing.").

General Drawing

Flatness

When drawing line art, flatness controls how accurately the shapes drawn will match the underlying curve. For more information on flatness, see Chapter 7.

Image Settings

The only field in the graphics state that has an impact on drawing images is the interpolation quality setting. When Quartz is drawing an image into some graphics contexts, it must sometimes synthesize pixels on the destination by interpolating color values from the original image. This is often the case when drawing the image with a scale or rotation transformation applied to the context.

There are a number of different algorithms that the computer can use to select colors for the interpolated pixels. In general these algorithms make trade-offs between the quality of the resulting image versus the amount of time spent calculating the interpolated pixels. The interpolation quality setting allows your computer a measure of control over that trade-off and gives the context some guidelines to use when selecting an algorithm.

Not every context supports image interpolation. Moreover, since the interpolation quality is a suggestion, the context is free to ignore any setting your application gives this parameter. The actual choice of an interpolation algorithm is left to the context itself, and your application has no direct control over that choice through the generic CGContext API.

General Settings

Also found within the graphics state are a number of drawing parameters that affect all of the objects drawn into a context. There is not a lot to tie these options together other than the fact that they affect every object that is drawn on the context. The following table summarizes these fields.

Table 4.2. General Graphics State Settings

Graphics State Field

Description

Alpha

This alpha combines with the alpha values of the current drawing color and sets the overall alpha of any objects drawn into the context.

Color Rendering Intent

The Rendering Intent is a color management parameter that affects the way colors are mapped from their source color space into the color space of the context.

Shadow settings

Quartz can add a drop shadow to each object as you draw it into a context.

Blending Mode

Quartz supports the blending modes defined by the PDF standard and will apply the blending mode to each object.

Antialiasing

Your application can instruct Quartz as to whether or not it should antialias graphics that are drawn in the context. As with the image interpolation settings, not all contexts may support antialiasing. For those that do, however, your application may enjoy a slight performance improvement when drawing with antialiasing turned off. Antialiasing can also be disabled using a setting that is not part of the graphics state and that overrides this field. See "Antialiasing and Font Smoothing" later in this chapter.

While there are relatively few drawing primitives, it is the fields of the graphics state that really embody the flexibility of the Quartz library. Drawing with Quartz 2D largely comes down to the tasks of selecting the proper settings for the fields of the graphics state and then specifying the primitives that you want to draw. The technique of manipulating the graphics state stack to save and restore values can be an invaluable tool and greatly simplifies management of the fields in the graphics state. While the graphics state stack is an important aspect of the CGContext, the context does contain other information that is not part of the graphics state that is stored on the stack. Having covered the stack's fields, it is time to refocus attention to other parts of the graphics context.

The Current Path

In discussing the drawing model in the previous chapter, the concept of the path was briefly introduced. In essence, a path is a resolution independent tool for describing an area of user space or a trajectory you want to stroke. Each context maintains one special path, known as the current path. The CGContext API contains routines to construct the current path. After building a path, applications can fill or stroke the path to create line art. The drawing methods consume the current path, perform the requested operation, and leave the context ready to accept a new path.

In addition to asking Quartz to draw a path, you also can ask it to combine that path with the current clipping area of the context.

It is important to realize that in Quartz 2D the current path is not part of the graphics state. This is just the opposite of the way PostScript handles its current path. In PostScript the current path is part of the graphics state.

In Quartz 2D, as in PDF files, if the code constructs a path in the context, saves the graphics state, fills the path, and then restores the graphics state, the path will not restore along with the graphics state fields. When translating PostScript code to Quartz 2D, you may have to modify the logic of that code to account for this behavior.

Color Space Settings

The CGContext plays a role in the Color Management that is integral to the Quartz 2D imaging model. Some graphics devices, like display screens and printers, have color spaces of their own. Other devices, like offscreen bitmaps rely on the application that creates them to specify a color space for them. Regardless of the source of the color space information, when Quartz creates a context it associates a destination color space with it. The color matching code works to ensure that the colors in a drawing are reproduced as faithfully as possible in that color space.

When you specify a color that your application would like to use as a stroke or fill color, you can identify the color space that the color comes from as well. Quartz will perform whatever calculations are necessary to map the color from its source color space to the color space of the graphics context on which you are drawing.

When an application is working with a context that is attached to a device, say a printer or a display, the color space of that context is taken from the device itself. For offscreen bitmap contexts, the application specifies a color space for the context. Programs also can retrieve the color space of a bitmap context using the CGBitmapContext opaque data type API.


Other Context Functionality

The graphics context provides some additional functionality that is not directly related to drawing and state management. Many of these settings are device dependent drawing options. Others allow your application to take more control over the drawing process.

Transparency Layers

A transparency layer gives you a convenient tool for collecting a bunch of graphics primitives together into a separate drawing space and then compositing them onto the context all at once. Transparency layers are particularly useful for creating drop shadows and transparent graphics.

You begin a transparency layer by calling the routine CGContextBeginTransparencyLayer. At that point, the computer creates a separate buffer, with a transparent background, for the context. All of the drawing that you send to the context at that point goes to the transparency layer instead of being drawn in the context. The transparency layer honors the clipping area and the graphics state of the context with two exceptions. When you begin a transparency layer, the current context-level alpha is set to be fully opaque (an alpha value of 1), and shadow settings, if any, are turned off.

At this point, your application is free to draw whatever you direct it to, and the graphics will be captured on the transparency layer. When you have finished drawing, call CGContextEndTransparencyLayer and Quartz composites the entire contents of the transparency layer onto the context at once. Before doing so, however, the computer restores the shadow and alpha values in the graphics state. The end result is that the entire contents of the transparency layer are composited on the context with a single alpha value and a single shadow.

Antialiasing and Font Smoothing

Antialiasing and font smoothing are drawing techniques that Quartz uses to improve the appearance of its resolution independent graphics when they are drawn on bitmap displays. When antialiasing, the computer uses the alpha channel to shade pixels along the edges of graphics to reduce their jagged appearance.

When drawing text on a context attached to a color LCD display, Quartz takes advantage of the nature of LCD monitors to improve the legibility of text. This technique is called font smoothing. The pixels of an LCD monitor are made up of red, green, and blue sub-pixels. If you take these sub-pixels into account, the screen appears to have three times the resolution commonly attributed to it, at least in one dimension. Font smoothing takes advantage of this increased resolution to improve the rendering of text. Quartz turns different sub-pixels off and on by changing the color of a pixels along the edge of letter shapes. Because your eye expects to see a hard line at the edge of the glyphs, the computer tricks it into ignoring the color in favor of perceiving a smooth edge. One disadvantage of font smoothing is that it relies on the fixed ordering of the sub-pixels of an LCD display. That makes the technique of limited use on other types of monitors. Font smoothing is also of limited use on offscreen bitmaps.

Antialiasing and font smoothing do not make sense for every graphics context. Nevertheless, the routines that control whether or not Quartz applies antialiasing are part of the generic CGContext API. There are three routines that control antialasing and font smoothing.

The first routine, CGContextSetAllowsAntialiasing is a master switch for the context. This routine turns off antialasing and font smoothing for the context as a whole. The field value controlled by this routine is not part of the graphics state. Use this routine when you want to disable any antialiasing that Quartz might apply to a context.

The next two routines are CGContextSetShouldAntialias and CGContextSetShouldSmoothFonts. These routines manipulate fields that are saved and restored as part of the graphcis state. They change the behavior of the context temporarily. Quartz applies antialiasing when the value in the graphics state and the antialasing master switch are both true. Fonts are smoothed only when antialiasing is enabled and the font smoothing setting in the graphics state is true.

Coordinate Transformations

Because the graphics context contains all the information needed to convert coordinate information between user space and device space, it is uniquely positioned to aid with the reverse transformation as well. The CGContext interface contains routines that transform points, sizes, and rectangles between the two coordinate systems.

CGContextGetUserSpaceToDeviceSpaceTransform returns an CGAffine transformation that converts coordinates between user space and device space. You can use this transform with the techniques described in Chapter 5 to move geometries from user space to the raw coordinate space of the device. Quartz also offers the following methods as convenience routines for transforming common geometries:

CGContextConvertPointToDeviceSpace

CGContextConvertPointToUserSpace

CGContextConvertSizeToDeviceSpace

CGContextConvertSizeToUserSpace

CGContextConvertRectToDeviceSpace

CGContextConvertRectToUserSpace

You might use these routines, for example, if you wanted to draw a box that was exactly 32 pixels square into a graphics context attached to a window. To determine how big a rectangle you would have to draw in user space to achieve that effect, you could pass the size 32x32 to CGContextConvertSizeToUserSpace and then draw a rectangle of the resulting size in user space.

Synchronization and Flushing

Mac OS X uses double buffering to draw the contents of its windows. When your application draws to a context attached to a window, the pixels are not actually sent to the screen. Instead, the computer puts your drawing into an offscreen pixel buffer, called the back buffer or backing store, and copies those pixels to the screen at a later time. The act of copying the pixels from the back buffer onto the screen is called flushing the buffer.

Flushing the entire back buffer to the screen with each change is very expensive, particularly for large windows. Additionally, if the computer flushes a lot of little areas, the overhead needed to set up the flushes would be a performance limitation as well. This means that the computer can't flush the entire window each time, nor is it efficient for it to flush each little area of the window as it is drawn. To get the best performance, the computer collects a lot of little changes and copies them to the screen all at once. To do that, the window server keeps track of the dirty areas of each window, those areas where the application has drawn graphics that need to be copied to the screen.

It is up to the application to tell the window server when the program damages an area of the window. Most of the time, this task is handled by the application frameworks, Carbon and Cocoa. On occasion, however, your application may need to forward the dirty areas to the window server explicitly. The Quartz context provides two routines for this task, CGContextSynchronize and CGContextFlush. The proper use of these routines is one of the great mysteries of Quartz programming. Hopefully we can clear up some of that mystery here.

The first thing to realize about CGContextSynchronize and CGContextFlush is that these routines are only useful for CGContexts that are attached to windows. You can call these methods with other graphics contexts, but they don't do anything useful.

The graphics context keeps track of the areas where you draw, and the CGContextSynchronize routine asks the window server to add those areas to the dirty region for the window. Calling CGContextSynchronize is a reasonably fast operation because it doesn't actually cause the window server to flush the back buffer to the screen. It's just a notification that an area of the window is dirty and needs to be updated. The actual flushing will occur at a later time.

If your application creates a CGContext on a window, draws into that context, and then destroys it with CGContextRelease, use CGContextSynchronize before the release to ensure that the dirty areas of the context are sent to the window server.

Compared to CGContextSynchronize, CGContextFlush is a comparatively heavyweight operation. When your application calls CGContextFlush, it forces the computer to take whatever steps are necessary to get the drawing that you've done in the window all the way to the screen. When your application calls CGContextFlush, it will not regain control until the graphics in the context are copied all the way to the computer's display. At first glance this may seem like a good routine to call as often as possible, but this is not the case. If misused, CGContextFlush can be a terrible performance problem.

On systems prior to Tiger, the computer would flush your graphics to the screen almost immediately. Tiger includes a feature that delays the winodw flushes of all applications so that they occur at once. Quartz 2D tries to synchronize its flushes with the refresh rate of the monitor. This refresh usually happens about 60 times a second. If you draw into a graphics context, call CGContextFlush, and then try to draw into that context again. The computer will block your application until the flush occurs. That means that the computer may not return control to your application for as long as one-sixtieth of a second. That is wasted time that your application could probably spend on more valuable computations. When a program calls CGContextFlush and draws into the same context at a rate faster than the computer can complete the flush, the net effect is wasted cycles that could be better spent on other processing. The average person can only perceive screen updates at about 30 frames per second anyway. Calling CGContextFlush more often than that is not useful and can degrade performance.


Obtaining CGContexts

Now that the functionality that the CGContext provides is clear, it is now time to look into where CGContexts come from. The way your application gets its hands on a CGContext changes depending on what kind of device it wants to draw onto. The following section examines different techniques for obtaining CGContexts for different drawing surfaces. The code samples presented may not be complete drawing examples, but hopefully you can use them as templates within your own code.

QuickDraw Ports

QuickDraw has been deprecated and should not be used as the basis for any new code. Nevertheless, as legacy applications make the transition to Mac OS X, they often have to mix Quartz and QuickDraw graphics in the same QuickDraw port. This technique can also be very valuable for incorporating Quartz 2D graphics into QuickTime movies on versions of Mac OS X prior to Mac OS X 10.4 where a QuickDraw port is the only way to get graphics into QuickTime.

Unfortunately, Quartz 2D does not support ports that use color table indexes as pixel values. Nor does it support black and white QuickDraw ports. The upshot is that Quartz 2D cannot draw to ports that use 1, 2, 4, or 8 bits per pixel. It does support ports that use 16 and 32 bits per pixel. When creating a CGContext on a 32-bit offscreen GWorld, Quartz will ignore the high order byte of each pixel (the byte usually reserved for the alpha channel). If you need to create an offscreen drawing environment that honors the alpha channel, you should use a CGBitmapContext.

The fundamental routines for mixing Quartz 2D and QuickDraw are QDBeginCGContext and QDEndCGContext. Your application should use these routines as a pair. QDBeginCGContext will return a context that your application can use to draw into the QuickDraw port. After you are done using the context, you can return it to the operating system with QDEndCGContext.

There is an older routine for creating a CGContext on a port called CreateCGContextForPort but Apple does not recommend you use this routine. It is particularly important if you are trying to create a CGContext for use with a QuickDraw printer port. You should use QDBeginCGContext and QDEndCGContext instead. If you do create a CGContext for a port using CreateCGContextForPort, you are responsible for disposing of the context (using CGContextRelease) when you are done drawing.

There are some caveats to using QDBeginCGContext/QDEndCGContext. We've already mentioned that these routines can only be used on contexts with certain pixel formats. Also when you first obtain a context on a given port, that context is set to the default state of all CGContexts. In particular the context will not share any of the drawing state of the underlying CGrafPort. The computer does not transfer any of the port's graphics state to the context. The two represent completely independent drawing environments that happen to share the same memory space. Finally, the computer diables QuickDraw commands for the port inbetween the QDBeginCGContext and QDEndCGContext calls.

If you need to interleave QuickDraw drawing and Quartz drawing, you can call QDBeginCGContext and QDEndCGContext on a port multiple times. The system caches the context associated with the port between subsequent calls. While you are free to call these routines as many times as necessary, you should not nest calls for the same port. That means that after calling QDBeginCGContext, you must call QDEndCGContext before calling QDBeginCGContext again.

There are two other utility routines for working with the CGContexts associated with a QuickDraw port. These routines are part of QuickDraw (found in the QuickDraw.h header) and not part of the CGContext interface. Your application can only use these routines with a CGContext it has attached to a QuickDraw port.

The first utility routine is SyncCGContextOriginWithPort. The purpose of this routine is to match the transformation of the CGContext to the transformation of the QuickDraw port. It does this by replacing the current transform of the CGContext with a new transformation that puts the origin of the CGContext in the lower left corner of the port. The coordinate axes of the context reorient to their default directions with the x axis pointing to the right and the y axis pointing upward. The scale of the context changes so one point in user space is equivalent to one pixel in the graphics port.

SyncCGContextOriginWithPort also takes the transformation of the port's origin into account. If the port's origin setting is offset to the right by 30 pixels and down by 10 pixels (a port origin of (-30, -10)), the CGContext's origin will also reset to a position to the right and downward of its normal location.

It is important to realize that SyncCGContextOriginWithPort will completely replace the transformation in the current context. It will destroy any transformations your code may have applied to the context.

The second utility routine for working with QuickDraw ports is ClipCGContextToRegion. This routine replaces the clipping path in the CGContext with a path that traces the outline of a region from the port. An application usually uses this routine to replicate the QuickDraw port's clipping region on the CGContext.

ClipCGContextToRegion is part of QuickDraw and does not play by the same rules as the clipping methods of a Core Graphics context. This routine completely replaces the clipping information in the context with the new clipping path. The routine even loses the clipping information in older stack frames so you cannot use CGContextSaveGState and CGContextRestoreGState to save and restore clipping area around a call to ClipCGContextToRegion.

Remember that CGContexts, in general, don't really understand the concept of pixels. Regions are, by their very nature, pixel-based entities. It follows that ClipCGContextToRegion does not really transfer the region to the context. Instead, it creates a path that traces the outside of the region and uses the interior of that path as the clipping area of the context.

The paths that result from tracing the outlines of a clipping region can be very complex. Clipping drawings against that path can, likewise, be an expensive operation. Your application will enjoy better performance if it relies on the Core Graphics clipping routines and uses ClipCGContextToRegion sparingly.

Listing 4.1 ties many of these concepts together into a single routine. The code in the listing is from a Carbon application that is slowly adopting more modern Mac OS X technologies. The routine is a handler for the [kEventClassWindow, kEventWindowDrawContent] Carbon event. A more modern application would not use this event. Instead, it would use a compositing window and would draw all the window's content using HIViews and Quartz. The intrepid developer of this code is trying to make the transition in stages and is looking to update their drawing code first.

Listing 4.1. Draw Content Handler that Mixes Quartz and QuickDraw

OSStatus HandleDrawContent(
    EventHandlerCallRef inCallRef,
    EventRef inEvent,
    void *userData)
{
    WindowRef      window;
    CGrafPtr       windowPort, savedPort;
    CGContextRef   cgContext;
    Rect           portBounds;
    bool           wasSwapped;
  GetEventParameter(inEvent, kEventParamDirectObject,
          typeWindowRef, NULL, sizeof(window), NULL, &window);

  windowPort = GetWindowPort(window);
  GetPortBounds(windowPort, &portBounds);

  wasSwapped = QDSwapPort(windowPort, &savedPort);

  /* ... QuickDraw Drawing Commands ... */

  QDBeginCGContext(windowPort, &cgContext);
  SyncCGContextOriginWithPort(cgContext, windowPort);
  ClipCGContextToRegion(cgContext, &portBounds, clippingRegion);
  DisposeRgn(clippingRegion);
  clippingRegion = NULL;

  /* ... Quartz Drawing Commands ... */

  QDEndCGContext(windowPort, &cgContext);

  if(wasSwapped) {
         QDSwapPort(savedPort, &savedPort);
  }

}

After retrieving the window from the event and the port from the window, the code draws some QuickDraw graphics. After the QuickDraw commands, the code uses the QDBeginCGContext routine to obtain a CGContext. This context passes to both ClipCGContextToRegion and SyncCGContextOriginWithPort. This code sets up the coordinate system and clipping of the CGContext environment so they closely relate to the QuickDraw environment. From that point until the call to QDEndCGContext, QuickDraw has been disabled for the port, but the code can use Core Graphics to draw into the same memory. The routine could continue to use QuickDraw after the call to QDEndCGContext and could use additional QDBeginCGContext/QDEndCGContext pairs to interleave Quartz and QuickDraw graphics.

HIView Contexts

HIView is the modern replacement for the classic Macintosh Control Manager. The technology incorporates a number of improvements over the older Control Manager. Of particular interest to us here, however, is the mechanism that HIViews use to draw their custom contents. The HIView system supports a well-defined control compositing model with built-in support for Quartz 2D. You can draw HIViews using QuickDraw, but Apple recommends that applications use Quartz 2D for HIView graphics as well.

Getting a CGContext for an HIView is an easy task. When the system wants you to draw your HIView, it will send you the [kEventClassControl, kEventControlDraw] Carbon event. The CGContext you should use to draw your control is stored in the kEventParamCGContextRef parameter of that event. Listing 4.2 shows how an application can extract a CGContextRef from a kEventControlDraw Carbon event.

Listing 4.2. Obtaining a CGContext for an HIView

OSStatus HandleControlDrawEvents(
    EventHandlerCallRef inCallRef,
    EventRef inEvent,
    void *userData)
{

    CGContextRef cgContext;

    OSStatus errStatus = GetEventParameter(inEvent,
           kEventParamCGContextRef,
           typeCGContextRef, NULL, sizeof(CGContextRef),
           NULL, &cgContext);

    if(noErr == errStatus && NULL != cgContext) {
            /* ... Quartz drawing commands ... */
    }
}

Notice that the code carefully checks to see if it can retrieve the CGContextRef from the event. Your code may, or may not, need to take this step depending on its needs. The kEventParamCGContextRef is a relatively new addition to the kEventControlDraw Carbon event. It was first introduced on Mac OS X 10.2 and is only be available for controls that are drawn in compositing windows. If you want to use Quartz 2D to draw controls on older operating systems, you can still use the QDBeginCGContext/QDEndCGContext routines discussed in the QuickDraw Ports section.

Another interesting fact to notice about this code is that it does not dispose of the context returned by GetEventParameter. You do not own the context that you retrieve from the Carbon event, so you should not dispose of it. What's more, once you are outside of the draw event handler, the computer may need to change the characteristics of the context, or even destroy it, without your knowledge. For this reason, your code should not cache the context it retrieves from the event and try to use it outside of the handler.

It would also be unwise to assume that your HIView code is always drawing to the display screen. Routines like HIViewCreateOffscreenImage may direct the drawing code to a device other than the screen.

HIView is a bit unusual in the way it sets up the coordinate system when it passes a CGContext. The HIView system was designed as a replacement for the classic Control Manager. The Control Manager, in turn, was well integrated with QuickDraw and QuickDraw-style coordinates in particular for much of its functionality.

To help HIViews and Control Manager Controls to coexist on the window, the HIView system also bases its coordinate system on the upper left corner of the window, or view. Before the system passes a CGContext for an HIView, it sets up the coordinate system for that view so that it has a QuickDraw-like orientation. The origin of the context is set to the upper left corner of the view. The y axis points downward, and the x axis extends to the right (the orientation illustrated in the left half of Figure 4.1). This leads to a bit of difficulty with Quartz 2D routines that draw images or text.

Figure 4.2 demonstrates the effect of drawing text and images with Quartz 2D while the coordinate axes are in the default HIView orientation. In the figure, the image of the Roses was drawn using CGContextDrawImage. When Quartz draws the image, the bottom-to-top axis of the image is oriented up the y axis. Because the HIView hands you a coordinate system where the y axis points downward, the image draws upside down! A similar observation can be made about the way that Quartz 2D draws text.

Figure 4.2. Effect of HIView Coordinates on Images and Text

In the case of drawing images, the HIViewDrawCGImage routine can correct this problem. It accepts as parameters the CGContext you want to draw into, the CGImage you want to draw, and an HIRect that tells Quartz where to draw the image. The routine scales the image to fit into the rectangle and adjusts the coordinate system, for the duration of the call, to ensure that the image draws the right way up.

Unfortunately, in the case of drawing text, there is not a simple, one-routine solution. Carbon applications have a couple of text drawing technologies available to them. ATSUI is the fundamental technology behind most of them. A full discussion of ATSUI is outside of the scope of this book. Chapter 11, "Drawing Text with Quartz 2D," discusses techniques for drawing text with Quartz in more depth.

One technique to solve the problem is to simply return the coordinate axes to their default Quartz 2D orientation. Coordinate system changes are examined in more detail in Chapter 5. In short, the origin of the coordinate needs to be moved from the upper left corner to the lower left. This is done by translating the origin along the y axis by the height of the view. Once the origin is in place, we need to reflect the coordinate system so the y axis points upward. Scaling the coordinate system with a negative number also flips the orientation of the axes. The code to accomplish this is given in Listing 4.3.

Listing 4.3. Code to Flip the HIView Coordinate System

void FlipHIViewCoordinates(
    HIViewRef inHIView,
    CGContextRef inContext)
{
    HIRect viewBounds;

    HIViewGetBounds(inHIView, &viewBounds);

    CGContextTranslateCTM(inContext, 0, viewBounds.size.height);
    CGContextScaleCTM(inContext, 1.0, -1.0);
}

To restore the coordinate system you could execute the routine again; however, you should undo coordinate system changes by saving and restoring the graphics state around them. This prevents small round off errors from accumulating and causing inaccurate transformations.

NSView Contexts

Cocoa includes quite a few utility classes that allow your application to draw using Core Graphics without having to use the Core Graphics API directly. The frameworks of Cocoa include NSBezierPath for creating line art, NSImage for drawing images, NSTextView (and related classes) for drawing text, and NSAffineTransform for handling coordinate transformations.

In some cases, however, you may want to obtain, through the code, a CGGraphicsContext for a view and make direct calls into Core Graphics. To obtain a CGGraphicsContext inside the drawRect: method of a view, you can use the NSGraphicsContext class and the graphicsPort method. This is demonstrated in Listing 4.4.

Listing 4.4. Obtaining a CGContext Inside of an NSView's Draw Method

- (void)drawRect:(NSRect)rect
{
    NSGraphicsContext *currentGraphicsContext =
            [NSGraphicsContext currentContext];
    CGContextRef cgContext =
            (CGContextRef)[currentGraphicsContext graphicsPort];

    /* ... Quartz drawing commands ... */

}

As with the case of obtaining context from an HIView, your code should not try to hold on to the graphics context it obtains in this way outside of the bounds of the drawRect: method. Also you cannot make assumptions about the device the context draws on. You should not, for example, assume that the context is a bitmap context and typecast it as such. Cocoa uses the drawRect: method to implement features like printing or the dataWithPDFInsideRect: method call. You could be called with a PDF context or a printing context if the computer invokes features like these on your view.

When you obtain a context inside of a drawRect: method, you cannot make any assumptions about how the context is initially oriented relative to the device coordinate system. Through routines like setBoundsOrigin:, setBoundsRotation:, and scaleUnitSquareToSize:, the NSView interface allows an application to transform the drawing space of the view independently of its frame. When the view is asked to draw, these transformations are incorporated into the CTM of the context.

PDF Contexts

A hallmark of the Quartz 2D imaging system is its support for the PDF file format. In addition to being able to draw many PDF files, Quartz uses the PDF file format as a metafile for storing drawings. Your application can use Quartz to record a series of drawing commands and play them back later.

A metafile is a file that contains the instructions to draw an image instead of the image itself. Other graphics systems have their own individual metafile formats. For example, the QuickDraw metafile format is PICT. Windows uses a file format called the Enhanced Metafile Format (EMF). Because the Quartz imaging model is based on the imaging model of PDF files, it was a natural choice for the libray's metafile format.

It's important to realize, however, that Quartz is not a generic tool for creating arbitrary PDF files. The PDF file format includes a broad set of features that are not directly supported by Quartz 2D. To put it another way, Quartz 2D metafiles are PDF files, but not all PDF files are Quartz 2D metafiles.

To create PDF data from your drawings, you have to create a context using the either the routine CGPDFContextCreate or CGPDFContextCreateWithURL. After you have created the context, you can draw into the context just as you would into any other context. The computer will record the drawing commands you send to the context and write a PDF file that can reproduce those drawings.

You provide the routine CGPDFContextCreateWithURL with a file URL. Quartz will try to create a PDF file on disk at that URL. The routine CGPDFContextCreate, in contrast, accepts an object known as a CGDataConsumer. A data consumer holds a number of callbacks that pass PDF data to your application as Quartz generates it. By implementing your own callbacks, your application is free to redirect the PDF data to any location you choose.

We explore data consumers and their role in exporting image files, including PDFs from Quartz 2D in subsequent chapters. We also discuss the options and features that Quartz 2D provides for creating PDF files in Chapter 14, "Working with PDF." Listing 4.5 provides a short preview and demonstrates one way to create a PDF context that writes a PDF file on disk.

Listing 4.5. Creating a Context for Drawing a PDF File

void DrawGraphicsToPDFFile(CFURLRef pdfFileRef)
{

    CGRect letterSizePage = CGRectMake(0, 0, 8.5 * 72.0, 11.0 * 72.0);

    CGContextRef pdfContext = CGPDFContextCreateWithURL(
            pdfFileRef, &letterSizePage, NULL);

    for(unsigned int pageCounter = 0; pageCounter < numberOfPages;
            pageCounter++) {

            CGContextBeginPage(pdfContext, &letterSizePage);

            /* call your routine which draws the contents of a page */
            DrawPage(pdfContext, letterSizePage, pageCounter);
            CGContextEndPage(pdfContext);
    }

    CGContextRelease(pdfContext);

}

The code begins by defining the size of the drawing area for the PDF document. All of the drawing that goes into the PDF must occur on a page. While a PDF file can contain pages of different sizes, the drawing area of the document serves as the default page size. In this case, our PDF file is going to use 8.5x11 inch pages, so we define a rectangle with those dimensions (remember one point is [image] of an inch). We create the PDF context with that rectangle. Then, for each page, the code uses the routines CGContextBeginPage and CGContextEndPage to bracket the drawing commands for each page. After drawing all the pages, the code calls CGContextRelease to finalize the PDF file and dispose of the context.

The call to CGContextRelease is an important part of working with the PDF graphics context. The computer allows you to add any number of pages to a PDF document and, therefore, does not know when to complete the PDF and end the file without your aid. Your code gives the system that opportunity by releasing the context.

Printing Contexts

Quartz 2D is as effective at drawing to printers as it is at drawing to other graphics devices. Your application need only create a graphics context for the printer and draw into it as it would any other graphics context.

Printing in Cocoa applications is integrated into the view system. To use Quartz to draw in a Cocoa application, you can obtain a graphics context inside of the drawRect: method and draw into that context.

For more information on printing in Cocoa applications visit the Apple documentation at

http://developer.apple.com/documentation/Cocoa/Conceptual/Printing/index.html

Printing in Carbon applications is a bit more involved. When a Carbon application prints, it has the option of using Quartz 2D or QuickDraw to create graphics on the printer. Fortunately, Quartz 2D is the default printing mechanism, so an application doesn't have to go through as many steps to use Quartz 2D as it might have to go through to use QuickDraw.

When your application is within its printing loop and within the context of a page, you can obtain a CGContext for a printer by calling the print manager routine PMSessionGetGraphicsContext. This routine has the ability to return either a QuickDraw Port or a Core Graphics port. Your application sets up a printing session using the routine PMSessionSetDocumentFormatGeneration. However, given that Quartz 2D is the default printing path for Carbon applications, you will not usually have to use that routine. The code fragment in Listing 4.6 outlines the process for printing a page using the Carbon printing services and Quartz 2D.

Listing 4.6. Obtaining a Printing Context During Carbon Printing

void PrintQuartz2DPage(
    PMPrintSession printSession,
    PMPageFormat pageFormat,
    unsigned int whichPage)
{

    PMRect                 pageRect;
    CGContextRef   printingContext;

    PMSessionBeginPage(printSession, pageFormat, NULL);
    PMSessionGetGraphicsContext(printSession,
           kPMGraphicsContextCoreGraphics,
           (void **) &printingContext);

    PMGetUnadjustedPageRect(pageFormat, &pageRect);
    CGRect cgPageBounds = CGRectMake(0, 0,
            pageRect.right - pageRect.left,
            pageRect.bottom - pageRect.top);

    /* call your routine which draws the contents of a page */
    DrawPage(printingContext, cgPageBounds, whichPage);

    PMSessionEndPage(printSession);
}

This routine is only one part of the Carbon printing loop. Prior to calling this routine, your application should have accomplished a number of tasks. The focus of this routine is the portion inside of the PMSessionBeginPage and PMSessionEndPage calls. After beginning a page, the code asks for a Core Graphics context. The PMSessionGetGraphicsContext routine returns a generic, pointer-sized value so a little typecasting is used to disguise the pointer value. Next, you ask the printing system to provide the rectangle of the page and convert it to a CGRect that you can pass on to the drawing routine. After drawing the contents of the page you end printer's page and exit the routine. Note that after leaving the end page routine, the context may not point to a valid object any longer.

For more infomation on the Carbon Printing Manager, visit Apple's documentation at

http://developer.apple.com/documentation/Carbon/Conceptual/CPM_Concepts/cpm_chap1/chapter_1_section_1.html

Bitmap Contexts

If your Quartz 2D application needs to draw off-screen, it has a couple of options. First is to explore the ability to create a graphics context on an off-screen bitmap. The context you create will be able to draw Quartz graphics into an off-screen pixel buffer that your application owns. That off-screen buffer can be in any of a number of pixel formats that Quartz 2D supports including grayscale, alpha channel only, and floating point pixel types. With all this flexibility, there are a number of steps involved in creating a bitmap context. We take the opportunity to explore the options available in creating bitmaps in the chapter on off-screen drawing. Listing 4.7, however, offers many of the basic steps involved in creating an offscreen bitmap.

Listing 4.7. Creating a Simple Offscreen Bitmap

void OffscreenBitmapExample()
{

    const int k32ARGBPixelSize = 4;       // ARGB8888 Pixels

    CGSize offscreenSize = CGSizeMake(128, 128);
    unsigned long rowBytes = offscreenSize.width * k32ARGBPixelSize;

    // Create a color space for our offscreen pixels
    CGColorSpaceRef offscreenColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);

    // allocate enough memory to hold all the pixels
    void *offscreenBuffer = malloc(rowBytes * offscreenSize.height);

    // Create the bitmap context
    CGContextRef offscreenContext = CGBitmapContextCreate(
            offscreenBuffer, offscreenSize.width, offscreenSize.height,
            8, rowBytes, offscreenColorSpace,
            kCGImageAlphaPremultipliedFirst);

    // Two imaginary routines to make use of the offscreen context
    DrawIntoOffscreen(offscreenContext, offscreenSize);
    UseOffscreenPixels(offscreenBuffer, offscreenSize, rowBytes)

    // Clean up
    CGColorSpaceRelease(offscreenColorSpace);
    CGContextRelease(offscreenContext);
    free(offscreenBuffer);
}

Listing 4.7 begins by defining some of the desired context characteristics. The off-screen is going to be 128x128 pixels and use 32-bit ARGB pixels. To tell Quartz 2D about the color space for the off-screen, you also have to create a color space. In this case, the generic RGB color space is used, created by calling CGColorSpaceCreateWithName. To end the preliminaries, allocate a memory buffer large enough to hold the pixels Quartz will draw on. This routine uses the malloc system routine to allocate a buffer just large enough to hold all of the pixels.

With the preliminaries out of the way, you can create the actual CGBitmapContext instance using CGBitmapContextCreate. This routine can be a bit intimidating simply by virtue of the large number of arguments it accepts. Most of these simply describe aspects of the pixel map you want to draw on and much of this information is taken from the setup that the routine has already done. This call also tells the system to set the pixels to 8 bits per component and to use the first byte of each pixel for the alpha channel.

From there Listing 4.7 goes on to use the specified context and then cleans up after itself and returns.

This concludes our brief look at many of the different ways an application can obtain a CGContext. Many of the graphics attributes that are common to all CGContexts regardless of their origin have been explored. The important thing to note about CGContexts and their state variables is that most of the attributes in a context are found in all CGContexts, regardless of what kind of device they are connected to. In most cases, this simple fact allows your application the freedom to ignore exactly what type of graphics device it is drawing to and concentrate, instead, on what it would like to draw.


Chapter 5. Transformations

The separation of user space from device space is the source of much of the power of the Quartz 2D graphics library. As we've already seen, your application constructs its graphics in the resolution independent graphics environment of user space, but before the computer can reproduce the graphics on a device, it must first move the graphics to device space. Transformations are the tools that the computer uses to move graphics from one coordinate system to another.

Quartz 2D includes a set of tools for creating and combining transformations. Your application can take advantage of these tools and use them to simplify many of your drawings. The general strategy is to select a convenient coordinate system in which to draw your graphics. You can then use the tools in the Quartz 2D API to create a transformation that establishes that coordinate system. It takes some time to get the hang of using transformations in this way, but once you do, you'll wonder how you ever drew graphics without them.

Many computer graphics libraries represent transformations using a matrices. Quartz 2D is no exception, and like those other libraries it uses linear algebra to apply transformations with geometric objects like points, rectangles, and paths. You don't have to understand the mathematics behind transformations to use them effectively, but if you are familiar with the math, there are some Quartz 2D APIs that allow you to use the explicit matrix representation of transformations. At the end of this chapter we take the briefest of looks at the math behind transformations. If you want more information on the matrix representation of transformations and how you can use Linear Algebra to combine them, you can consult any modern computer graphics textbook.

The rest of this chapter looks at fundamental building blocks of Quartz 2D transformations and also examines the tools your application uses to combine the basics and create more complex transformations. The drawing sample demonstrates an instance of using transformations to simplify a complex drawing. The chapter ends with looking at some of the mathematics behind Quartz 2D transformations.


The Basic Transformations

When you are thinking about transformations, you can think about them in two different ways. On one hand you can think about applying a transformation to a set of objects to actually change the geometry of those objects. Drawing the object with the modified geometry redraws the object in a new location. This concept is illustrated in Figure 5.1.

Figure 5.1. Transforming Objects

In Figure 5.1, the staff is defined using some simple geometrical shapes. The shapes in the left side of the window represent the untransformed version of this particular geometry. The transformation chosen rotates the objects counterclockwise then moves them upward and to the right. The right side of Figure 5.1 shows the shapes after applying this transformation to them. In this case, the coordinate system has remained fixed, and the graphics have changed. We used a transformation to change the points that define the shape.

In addition to transforming geometry, you also can transform the coordinate system. This idea is a bit more difficult to understand. In the physical world, everyone is used to manipulating objects, we're not as accustomed to changing coordinate systems.

When transforming a coordinate system, you must have another coordinate system to compare it to. The transformation provides a relative mapping from one coordinate system to the other. When you draw in the transformed coordinate system, the transformation maps the graphic back to the first coordinate system. The appearance of that graphic in the fixed coordinate system is affected by the transformation. Figure 5.2 illustrates the results of using a transform to define a new coordinate system.

Figure 5.2. Transforming Coordinates

In the left half of Figure 5.2, is a graphic whose geometry defines shapes that are close to the origin. A transformation is then used to define a new, relative coordinate system. The transform rotates the coordinate axes 30 degrees clockwise and then shifts the origin upward and to the right. The transformations are relative to the original coordinate frame. Now you can redraw the exact same graphic in this new coordinate system.

The geometry that defines the shape of the graphic does not change. The points that define the shape still draw close to the origin and at the same orientation relative to the coordinate axes. However, when viewed from the perspective of our fixed coordinate system (represented in the gray arrows in Figure 5.2), the graphic appears to have changed both location and orientation.

Quartz 2D allows you to use transformations in both of these capacities. You can construct geometric objects and then modify the points that define those shapes by applying transformations to them. Alternatively, you can create transformations that help you define alternate coordinate systems and apply those transforms (e.g., to user space) to set up convenient drawing environments.

An important, albeit difficult, thing to realize about these two perspectives on transformations is that they are just two ways of looking at the same thing. Your application can create transformations that it applies to the coordinate system to get the exact same end results it might get from transforming the geometry that makes up the graphic. As a programmer you have the flexibility to choose the particular perspective on a transformation that simplifies the drawing task at hand. By the same token if you find yourself struggling to create a transformation to achieve a particular effect, you might try looking at the transformation from the other perspective and see if the new eyes help.

Affine Transform

Up to this point we have been talking about general transformations. Quartz 2D does not support arbitrary 2D transformations. Instead, the library supports a very particular sub-set of those transformations. The set of transformations Quartz 2D supports are known as affine transforms.

Affine transformations have a very rigorous and very specific meaning to mathematicians. We won't bore you with that definition here, as understanding the definition would probably not help you understand how to use transformations in Quartz 2D.

There are some interesting properties of affine transformations that are worth mentioning. First of all, if you have a set of points that all lie on the same line (they are colinear) and you apply an affine transformation to those points, you will find that the transformed points are also colinear. The affine transformation will also preserve the relative distances between the points. If you have any lines in your geometry that are parallel to one another, the lines will still be parallel after you transform them.

The fact that affine transformations preserve the parallel relationships between lines makes them unsuitable for general perspective transformations. In other words, the transformations in Quartz 2D cannot be used for the perspective projections commonly associated with rendering 3D graphics. This also means that you cannot use Quartz 2D graphics to achieve effects that involve destroying parallelism. You cannot, for example, use an affine transformation to taper a rectangle at one end.

Affine transforms have another useful property. You can combine simple affine transformations to create more complex ones. If you transform a set of points with the combined transformation, the net effect will be the same as if you had transformed those points with one of the component transformations first and then passed the results through the second component transformation. The result of combining any two affine transformations is also an affine transformation.

Quartz 2D exploits this fact by providing simple transformations whose effects you can visualize easily. The library also gives your application the tools it needs to combine the simple transformations together. The three basic transformation types that Quartz 2D supports are Translations, Rotations, and Scalings. We will demonstrate each transformation shortly and then show you how to use the Quartz 2D APIs to combine them.

Translations

Translation is a very fancy term for moving a thing from one location to another. When you translate your geometry, you move the points in the geometry to somewhere else in the coordinate space. From the perspective of mapping the coordinate system, a translation offsets the origin of the coordinate system to a new location. Figure 5.3 illustrates the effects of applying translations.

Figure 5.3. Translations Viewed in Two Ways

In the left side of Figure 5.3 a translation has been applied to the geometry of a star-shaped graphic. The grey version of the star is the geometry before the translation is applied, and the black star is the geometry after translating it. In the left side of the figure, we have translated a coordinate system. The origin of the transformed coordinate system is offset from the origin of the reference coordinate system by a bit.

To specify a translation, your code specifies the offset you want to apply in the x and y coordinate directions.

Rotations

Rotations allow you to change the orientation of a graphic or to change the relative orientations of two coordinate systems. The fundamental rotational affine transform is a rotation around the origin. If you want to rotate around a different point in space, you have to combine the rotation with a transformation. The idea is revisited later in the discussion about how to combine the primitive transformations. Figure 5.4 demonstrates the effects of applying a rotation.

Figure 5.4. Rotations Illustrated

In the figure a counter-clockwise rotation of 60 degrees has been applied to both a graphic, on the left, and a coordinate system, on the right. The gray illustration shows the original position of the graphic and the coordinate axes. The black graphics are the locations after rotation. The pivot point of each rotation is the origin. Because the hand graphic in the left side of the figure is not centered on the origin, the hand not only turns, but it also moves laterally through space.

To specify a rotation, you simply provide the computer with the angle through which you would like to rotate the object or axes. In Quartz 2D angles are measured according to the diagram in Figure 5.5.

Figure 5.5. Measuring Rotations

Figure 5.5 shows that positive angles are measured in the direction from the positive x axis to the positive y axis. Negative angles, of course go the other way. You should avoid thinking that positive angles turn things counter clockwise and negative angles rotate them clockwise. Quartz can orient the coordinate systems arbitrarily. If an application reverses the direction of either axis (using a scaling transform with a negative scaling factor, see below), you will also reverse the relationship between the angle's sign and the clockwise/counterclockwise directions. Carbon programmers working with the coordinate system passed to them by HIView are working with a coordinate system where the y axis has been reversed in this fashion.

Degrees versus Radians

When supplying the angles used to specify rotations, you need to pay attention to the different angle measurement units. Core Graphics routines accept angles in radians, while the Cocoa routines typically accept angles in degrees.

As a quick refresher, a full circle is 360 degrees (360°) which is equivalent to 2p radians. If you want to convert between degrees and radians, you can use the formula

Scaling

Scaling a graphic makes it larger or smaller. Scaling one coordinate system relative to another changes the relative distances covered by one unit along each of the axes.

Like rotation, the computer applies scales relative to the origin. If you scale objects that are not centered at the origin, the objects will move as well. This happens when the computer scales the space between the object and the origin in addition to the objects themselves. The effects of a scaling transformation are illustrated in Figure 5.6.

Figure 5.6. Scaling Objects and Coordinates

In Figure 5.6 the axes have been scaled independently of one another. When you specify a scale in Quartz 2D, you provide the scaling factor for each of the axes separately. In the figure, we used a scale of 2.25x (or 225%)2.25x (or 225%) on the y axis and a scaling factor of 1.5x (150%)1.5x (150%) to the x axis. On the left side we see the effects of scaling a graphic by our scaling factor. As in the previous figures, the gray object represents the object prior to applying the transformation, and the black object shows the transformed object.

The right half of Figure 5.6 shows the effect of scaling a coordinate system and deserves some explanation. Scaling a coordinate system changes the distance covered by each unit of the coordinate system. To illustrate this, the right side of Figure 5.6 includes marks at one unit intervals along each of the axes. The gray marks show the unit marks for the unscaled coordinate system. The black marks, of course, show the unit marks of the coordinate system after scaling. Note that along the y axis, the first scaled tick mark draws after two and 1/4 marks on the unscaled axis. Similarly, the first x tick mark on the scaled interface is found after one and a half tick marks on the unscaled axis.

Scaling an axis by 1.0 has no effect on the axis whatsoever. Scaling by a value larger than 1 increases the distance between coordinate units. This makes objects drawn in that coordinate system larger. Scaling by a value greater than 0 but less than 1 shortens the distance between coordinate units. This shrinks objects that are drawn to the fixed coordinate system. Scaling by a scale factor of 0 is meaningless, and the behavior of the system (as well as the math) is not well defined.

Interesting things happen when you scale an axis by a negative value. The scale not only changes the relative lengths of units, but it also reverses the direction of the corresponding axis. This can be very convenient if you want to convert the Quartz 2D coordinate system so that it matches the coordinate system of other graphics libraries such as QuickDraw or GDI. To accomplish this transformation, however, you have to combine this scaling factor with a translation. An example of this kind of transformation appears later in the chapter.

Inverse Transformations

Before discussing combining transformations, the inverse of transformations should be considerd. If you think of transformations as an operation that has an particular effect on a graphic, then the inverse transformation would be a transformation that exactly undoes that effect.

The inverse transformation of a translation by 100 pixels on the x axis and 50 pixels on the y axis would be a translation of -100 pixels on the x axis and -50 pixels on the y axis. If you moved a graphic using the first set of offsets and then moved it again by the second set, you would be putting the graphic back in its original location.

The inverse of rotation by an angle is rotation by the same angle in the opposite direction. This is equivalent to using an angle of the same magnitude, but opposite sign. If you rotate a graphic by 30 degrees, the inverse transformation is a rotation by -30 degrees.

The inverse of scaling the coordinate system by the factor S is scaling the coordinate system by the factor [image]. If you scale a coordinate system by 2, the inverse transformation will be a scale by ½.

You can use these facts to create your own inverse affine transformations. Quartz 2D also includes a routines to invert affine transformations.

Concatenating Transformations

Combining transformations is little more than applying one transformation after another to either a graphic or coordinate system. If you were to take your geometry and pass it through a series of transformations individually, you would not only incur the computational expense of each transformation, but you would be more likely to find that small mathematical errors would creep into the calculations. To avoid this, Quartz 2D uses matrix multiplication to concatenate a long chain of transformations into a single matrix.

As an example of this process consider the task of rotating a graphic about its center when the graphic is not originally located at the origin. This process is illustrated in Figure 5.7.

Figure 5.7. Rotating a Graphic About its Center

In this case we're going to accomplish our rotation by combining three different transformation steps. Figure 5.7(a) shows the graphic, an umbrella, in its initial position. The goal is to rotate this graphic about its center by 30 degrees. Recall, however, that the rotation primitive only rotates objects around the origin. What is needed is to center the umbrella on the origin by using a translation (Figure 5.7(b)), rotate it (Figure 5.7(c)), and then move it back to it's original location with another translation (Figure 5.7(d)).

If the umbrella figure has its center at (100, 100), then the following code snippet represents the steps needed to transform the umbrella:

CGAffineTransform affineTransform = CGAffineTransformIdentity;
affineTransform = CGAffineTransformConcat(
                   affineTransform,
                   CGAffineTransformMakeTranslation(-100, -100));
affineTransform = CGAffineTransformConcat(
                   affineTransform,
                   CGAffineTransformMakeRotation(pi / 6));
affineTransform = CGAffineTransformConcat(
                   affineTransform,
                   CGAffineTransformMakeTranslation(100, 100));

CGPathRef transformedPath = CopyPathWithTransformation(
                   umbrellaPath, affineTransform);

This code snippet begins by creating a CGAffineTransform structure and initializes it with the identity transform. The identity transform is just a starting place. If you take a graphic and transform it by the identity transform, you simply get back the original graphic.

From there it concatenates together the three transformations we want to apply to our umbrella. The first is the translation by (-100, -100). This moves the center of the umbrella to the origin. Next is the rotation by 30 degrees that turns the umbrella about the origin. Finally the object is put back where it was found by translating by (100, 100) a copy of the transformed umbrella is then created using a self-devised utility routine called CopyPathWithTransformation.

This is a fairly simple example of combining transforms. Your application can create any affine transformation that Quartz 2D supports by combining the three basic transformations in this fashion. Learning to do this effectively takes a little imagination and a lot of practice. When you grasp the concepts, however, it becomes much easier to use transformations to your advantage.

If you concatenate the inverse of a particular transform with the transform itself, you should get back the identity transform. In a computer with infinite floating point precision, if you were to apply a particular transform to the graphics context, you would be able to undo that transformation by applying the inverse transformation to the context as well.

Unfortunately, computers represent floating point numbers with limited precision. As a result, combining a matrix with its inverse does not always result in an identity matrix. Small errors can creep into the mathematics. These errors can affect the graphics you draw with the resulting transformations.

Instead of calculating inverse transformations, you should use the CGContextSaveGState and CGContextRestoreGState routines to save and restore the graphics state (which will also save and restore the CTM). This will ensure that the old transformation matrix is retrieved exactly as it was.

The Order Matters

When you are concatenating transformations together, you have to combine them in the proper order. This can be a bit tricky because the Quartz 2D API includes a couple of different sets of routines for combining transformations, and you may have to modify the order of your routine calls depending on which set of routines you are using.

The order that you use transformations is important because scalings and rotations happen relative to the origin. To illustrate this difference, let us look at what happens if we reverse the first two steps of our umbrella's journey. Figure 5.8 shows the umbrella if we first rotate it and then translate it.

Figure 5.8. Applying Transforms in the Wrong Order

To make Figure 5.8, we started with the figure in 5.7(a) and then applied the first two transformations in the opposite order. That means that Figure 5.8(a) shows the effect of rotating the umbrella by 30 degrees. Figure 5.8(b) shows the graphic if we subsequently offset the umbrella by (-100, -100). Compare these two pictures with the graphics of Figure 5.7(b) and 5.7(c). Because you cannot reverse the order of transformations without affecting the final image, Figure 5.8(b) is very different from 5.7(c).

The reason for the discrepancy is the fact that the basic rotation transform uses the origin as its pivot point. That means that when the umbrella is not centered on the origin, any rotation also moves the umbrella laterally across the canvas. When you combine that lateral motion with the transform, you find that the umbrella is in a different position. Scaling transformations would have a similar effect. If you apply a scaling transformation, you scale not only the object itself, but the space between the object and the origin. That means that scaling the object also moves it.

This leads to the observation that you have to be very careful about ordering your transformations when you try to combine scaling and rotations with translations. The order in which you apply your transformations can also be important depending on which Quartz APIs you are using to combine the basic transforms. In our previous code snippet, we used the Quartz 2D routine CGAffineTransformConcat. This routine works well for combining two existing transformations but requires us to create a transformation at each step. In that example CGAffineTransformConcat was chosen because it allowed demonstration of creating a transform from basic transforms by combining the individual steps in a logical order. The same transformation, however, can be accomplished with a bit less code. Consider the calling sequence

CGAffineTransform affineTransform = CGAffineTransformIdentity;
affineTransform = CGAffineTransformTranslate(affineTransform, 100, 100);
affineTransform = CGAffineTransformRotate(
                          affineTransform, pi / 6);
affineTransform = CGAffineTransformTranslate(
                          affineTransform, -100, -100);

CGPathRef transformedPath = CopyPathWithTransformation(
                                  umbrellaPath, affineTransform);

This calling sequence accomplishes the exact same transformation that the previous code snippet accomplished and does not require the CGAffineTransformMakeXXX routines. What you should notice, however, is the fact that this code reverses the order in which the transformations are combined. This is a consequence of the way mathematics combines the transformation. When looking at this code, you might choose to think that the last transformation applied to the affineTransform structure is the first transformation applied to the object.


Transformations in the Context

The previous chapter made brief mention of the Current Transformation Matrix (CTM) that is part of the graphics state of a CGContext. The CTM is a coordinate system transformation that maps from user space to device space.

Before you can draw any of the graphics primitives supported by Quartz, you have to first place the primitive into a graphics context. If you want to apply a transformation to the graphics you draw, you have two choices. You can either transform the graphic primitives before you draw them, or you can transform the coordinate axes of user space and then draw the graphics. The difference between these two approaches is illustrated in Figure 5.9.

Figure 5.9. Geometry Transforms versus Coordinate System Transformations

Figure 5.9 illustrates the process of drawing a heart in the middle of the graphic of a card. The heart path is similar to the one used in Chapter 3, "Introduction to Quartz 2D." The geometry of the heart path is centered on the origin as shown on the left side of Figure 5.9.

The first approach to centering the heart is to transform the geometry and then draw it in the context. This approach is shown in the center of Figure 5.9. The transformation moves the heart away from the origin.

The second approach is to transform the coordinate system first and then draw the geometry with the new coordinate location. This technique is the one we used to position the heart in the sample code of Chapter 3, which is also illustrated on the right side of Figure 5.9. In this case, the origin has been translated to the center of the card, and when the geometry is drawn (centered around the origin), the heart shape comes along for the ride.

Transforming the geometry of the path is a fairly destructive operation. You are actually changing the path itself. If you are trying to draw the path in multiple locations, this can be a problem. Consider if you wanted to use the same path to draw each of the hearts on the image of the playing card in Figure 5.9. If you were transforming the geometry each time you drew a heart, you would have to either copy the path and make changes to the copies, or you would have to carefully calculate relative transformations needed to position the heart each time. If you transform the coordinate system instead, you can reuse the same geometry many times.

Quartz 2D provides routines that allow you to directly transform paths. You can use the text matrix to modify text as you draw it. When Quartz draws images, however, it always draws them oriented to the coordinate axes. If you want to transform an image, you will have to modify the coordinate system.

Taking all of these facts into consideration, it should be no surprise changing the relationship between the user space and device space coordinate systems, by modifying the CTM, is the most popular technique Quartz 2D applications use to transform graphics.

Transforming coordinate systems is not nearly as intuitive as transforming objects. We are all conditioned by our experience with the physical world to think, "I want to 'grab' this graphic and move it here, then twist it like so... and stretch it just a bit." It's easy for us to think about graphics as objects and intuit the proper way to manipulate them to achieve a desired effect. Apart from Star Trek fans, or the occasional astrophysicist, however, we rarely think of achieving the same effects by manipulating the space that our graphics live in.

With that in mind, let us explore a model of user space that may make it a bit easier to visualize how working with the CTM affects your graphics. To do that, let's try and create a model for manipulating user space that might make it appeal a bit more to our physical model of the world. Figure 5.10 is a sketch of just such a physical model.

Figure 5.10. Illustration of the Physical Model of Transforming the Coordinate System

Imagine for a moment that user space is a boundless, exceedingly flexible sheet. The sheet in question has a horizontal (x) axis and a vertical (y) axis that are marked with a particular scale. In the diagram shown, the coordinate axes and a dotted rectangle helps you see the sheet, but the actual sheet is boundless.

When you apply a transformation to the user space coordinate system, you are manipulating the flexible sheet. Translating the coordinate system is the same as sliding the flexible sheet a certain number of rule marks in the direction of its horizontal and vertical axes. Scaling the coordinate system up will stretch the sheet so, moving its rule marks farther apart. Scaling down the coordinate system compresses the flexible sheet, which moves the rule marks closer together. Rotating the user space is the same as turning the sheet about its origin.

On top of the sheet sits a viewing window, a hole cut into cardboard if you will, through which you can view user space. This is illustrated in the figure by the rectangular hole in the solid white rectangle. This viewing window represents the graphics device. When the computer first sets up the graphics context, it establishes a relationship between the viewing window and the coordinate axes on the sheet. (Usually the computer will set things up so that the lower left corner of our "viewing window" is over the origin of the user space coordinate system).

When you apply transformations to user space, the masking window stays in place, and the flexible sheet "moves" underneath it. After you have finished making your transformations, you draw onto the flexible sheet. You use the rule markings of the sheet's coordinate system as a guide and plot points in that coordinate system to create a graphic. When you have finished drawing, the computer takes any of the graphics you've created that are directly underneath the viewing window and reproduces them, as faithfully as possible, on the device.

This model of the user space transformation may help you understand how transformations in user space affect the graphics you draw. The CGContext opaque data type contains three routines that allow you to apply the basic transformations to the user space coordinate system:

CGContextTranslateCTM Moves user space a certain number of rule marks along its x and y axes. This is equivalent to sliding the sheet along its coordinate axes. Note that if user space has rotated, relative to the viewing window, then the direction of movement may not appear to be horizontal or vertical (again, relative to the viewing window). Your application provides separate x and y distances for the move.

CGContextRotateCTM Rotates user space about its origin by a specified angle (expressed in radians). This can change the orientation of the user space coordinate axes relative to the apparent orientation of the viewing window.

CGContextScaleCTM Scales the user space coordinate axes by a particular amount. Your application provides two different scaling factors, one for the x axis and one for the y axis. The scaling is specified relative to the current user space scale. In other words, scaling both axes by a factor of two will make the distance between rule marks appear to be twice as large though the viewing window regardless of how large they might have appeared before the transformation.

In addition to the three basic routines, the CGContext API also includes the method CGContextConcatCTM, which allows you to take a pre-constructed transformation and concatenate it on to the transformation already in the context. Should you ever need to retrieve the transformation in the CTM, you can use the CGContextGetCTM routine.

Examining how an application can use these routines when drawing into a context is covered shortly. Before getting to that, however, following is a more in-depth look at what the computer can do with transformations that are not associated with a graphics context.


Transformations Unleashed

Earlier in the chapter, when presenting the way that your application can combine the basic transformations to create more complex ones, the code snippets also broached the subject of Quartz 2D transformations not tied to the CTM of a graphics context.

CGAffineTransforms

The Quartz 2D API represents a transformation using the CGAffineTransform. CGAffineTransform is a structure, not an opaque data type (and, therefore, is not referenced through data fields whose type includes Ref as a suffix). The fields of the CGAffineTransform structure are related to the matrix representation of the transformation, which is discussed a bit later.

If your application needs to keep track of a particular transform, all it needs to do is create a variable whose type is CGAffineTransform. From there it can populate the fields of the transform using the routines that appear in the code snippets. The three easiest routines are

CGAffineTransformMakeTranslation

CGAffineTransformMakeRotation

CGAffineTransformMakeScale

No surprisingly, these three routines create the basic transformations that have been discussed throughout the chapter. In the code snippets you also saw the routine CGAffineTransformConcat, which combines any two affine transform structures and returns a new structure with the results.

The other routines in previous discussions were

CGAffineTransformTranslate

CGAffineTransformRotate

CGAffineTransformScale

The three routines take an existing transform and apply the specified transformation to it. As noted before, when discussing the ordering of transformations, these routines combine the transformations in the opposite from as CGAffineTransformConcat.

We've also already seen the identity transform represented by the symbolic constant CGAffineTransformIdentity. The identity transform gets its name from the fact that if you apply the identity transform to anything (an object, a coordinate system) what you get back out is exactly what you put in. The effect is exactly the same as multiplying any number by one. The transform preserves the "identity" of whatever you provide to it. This makes the identity matrix a good starting point for any other transformations you want to apply.

If you create a CGAffineTransform whose fields are all zeros and use that transformation, the results are undefined. This is a bit like multiplying any number by zero. The entire coordinate system collapses a single point at the origin. Graphics that have been reduced down to nothing with a zero transform cannot be seen and aren't very interesting. Your code should start its transformation work with the identity transformation instead.

Another useful routine is CGAffineTransformInvert which calculates the transformation needed to undo the transformation that you pass to it. All of the affine transformations matrices you create with Quartz 2D should be invertible as long as you construct them using the routines in the Core Graphics APIs and avoid filling in the fields of CGAffineTransform structure directly.

The last three API routines that we will mention here are

CGPointApplyAffineTransform

CGSizeApplyAffineTransform

CGRectApplyAffineTransform

In spite of the fact that these routines apply to geometric shapes, these three are defined in the CGAffineTransform.h header file. Your application can uses these three transformations to manipulate basic geometry. The CGRectApplyAffineTransform may require a bit of explanation. If you rotate a rectangle 47 degrees, you no longer have a rectangle. You have a shape that probably resembles something like a diamond. Because the computer cannot represent the resulting, diamond-shaped object as a rectangle, what it can do is calculate the bounding box of such a shape and return that. If you use CGRectAffineTransform, what you get back is the smallest rectangle that could hold the four transformed corners of the original rectangle.

NSAffineTransform

When working through Cocoa, your application can represent an affine transformation using an instance of the NSAffineTransform class. Looking through the interface of this class you will find routines that very closely match those found in the CGAffineTransform interface. When you first create an instance of NSAffineTransform, you will receive an object that represents an identity transformation. You can then modify that transformation using the following methods:

- (void) translateXBy: (float) deltaX yBy: (float) deltaY;
- (void) rotateByDegrees: (float) angle;
- (void) rotateByRadians: (float) angle;
- (void) scaleBy: (float) scale;
- (void) scaleXBy: (float) scaleX yBy: (float) scaleY;
- (void) invert;

Using these methods, you can create the basic transformations. When you want to combine the transformations, there are additional methods:

- (void) appendTransform: (NSAffineTransform *) transform;

- (void) prependTransform: (NSAffineTransform *)transform;

These two routines allow you to combine the transformation passed as a parameter with the current object by multiplying in either direction. This can make it easier for your application to select the multiplication order that makes the most sense for the effect you are trying to achieve.

Transforming a Path

Transforming an entire path using the Cocoa APIs is a very straightforward operation. The NSBezierPath class contains a method, transformUsingAffineTransform:, which applies a transformation to all the points that make up a path.

Notably missing from the Quartz 2D API is a single routine that can transform a path using a particular CGAffineTransform. As a general rule, the absence of this routine is not a problem. Most of the time your application will probably construct paths in the context and the computer will apply the CTM to the path before drawing it. In addition, if you build a path using the construction routines of the CGPathRef obscure data type, you will find that each of those routines accepts a CGAffineTransform. If you specify a transformation in that parameter, the computer will apply that transform to each point as it moves into the path.

For the umbrella example, however, a goal was to create a copy of a path with an arbitrary affine transform already applied to it. A placeholder routine was used to represent that operation without any discussion of why it was necessary or how to implement it. The sample code for Chapter 5, "Transformations," includes the transformPaths code sample. This code sample includes a tool, or a class, for applying transformations to a path called, simply enough, the PathTransformer.

The PathTransformer, in turn, is a subclass of a path known as the PathApplier. A PathApplier object is simply a wrapper around the CGPathApply routine. In the next chapter the way an application can construct a path is discussed. The important thing to know about CGPathApply is that this routine allows you to visit each of the parts in the path and examine them. The PathApplier simply takes each of those pieces and passes them to a virtual method. Because the methods are virtual, you can change the behavior of the applier simply by overriding the virtual methods. This is precisely what the PathTransformer object does.

The PathTransformer keeps track of a transformation and the path that it is constructing. The virtual methods of the PathTransformer object use the transformation and the geometry information provided by the CGPathApply routine, to construct a new, transformed path.


Drawing with Transformations

The key to using transformations effectively is working with them to simplify the drawing process. There are two jobs a Quartz 2D application needs to accomplish that transformations are particularly good at simplifying. The first job is when the application needs to construct the geometry that defines the graphics it wants to draw. The second job is positioning the individual graphic elements relative to one another. These are not the only two uses for transformations, but they are useful techniques to apply in your own code.

To demonstrate these techniques in a code setting, please refer to the graphic of a roulette wheel shown in Figure 5.11. The code sample that generated this figure is written in Python. You can view snippets from the code sample that show how the code uses transformations to simplify the construction of such a complex graphic in the following sections.

Figure 5.11. The Roulette Wheel

For this graphic, no transformations are applied to the geometric objects that make up the figure. Instead the methods of the CGContext that allow us to transform user space are used. As you read through the code sample, you may find it easier to follow the transformations if you keep the idea of user space as a flexible sheet in mind.

The first thing to note about the figure is that it has a circular symmetry. The first transformation that the code applies to the graphic is to move the origin of user space to the center of the graphics context. The code that initializes a number of code variables, creates the graphics context, and moves the user space origin to the center of that context is shown in Listing 5.1.

Listing 5.1. Initializing the Roulette Wheel Context

# initialize some of the variables that will help us draw the graphic
# and create the context.
fullCircle = 2 * math.pi;
imageSize = 512;
imageBounds = CGRectMake(0, 0, imageSize, imageSize);
imageMidpoint. = CGPointMake(imageSize / 2, imageSize / 2);

rgbSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
cgContext = CGBitmapContextCreateWithColor(imageSize, imageSize,
                   rgbSpace, (0,0,0,0))

# exploit the circular symmetry of the graphic by
# translating the coordinates
# to the center of the context.
cgContext.translateCTM(imageMidpoint.x, imageMidpoint.y)

The roulette wheel code tries to create a graphic that the code can scale to any size. To do this, the code defines all of its geometry in a fixed size rectangle and uses a scale transformation to match that rectangle to the size of the context. For the roulette wheel, the code draws its graphics in a 256x256 rectangle. To scale the coordinate system so that the graphic will fill the context, the code uses the command

cgContext.scaleCTM(imageSize / 256.0, imageSize / 256.0)

From here the code goes on to draw the green area in the center of the roulette wheel. To do this it simply creates circular paths and asks the computer to fill them in with green.

The next use of transformations in the code is to draw the radial lines in the green area. In this case the code simply draws lines and relies on a rotational transformation to position those lines in a circle. The code that accomplishes this is in Listing 5.2.

Listing 5.2. Drawing the Central Radial Lines

numSlots = 38;
slotAngle = fullCircle / numSlots;
cgContext.saveGState();
cgContext.setRGBStrokeColor(0, .38, 0, 1.0)
cgContext.rotateCTM(-slotAngle / 2);
for index in range(numSlots) :
    cgContext.moveToPoint(innerGreenRadius + (2 * greenWidth / 5), 0);
    cgContext.addLineToPoint(innerNumberRadius, 0);
    cgContext.strokePath();
    cgContext.rotateCTM(slotAngle);
cgContext.restoreGState();

After calculating the angle that the computer uses to position the lines properly, the code saves the current graphics state. We save and restore the graphics state around the code that draws the lines to ensure that the CTM makes it back where it started after drawing the lines. The code rotates the context by half of the slot angle so that the first line matches up with the edge of the first slot. It next enters a simple loop that adds a line to the graphic and then rotates the context. Note that the line the computer draws lies along the x axis of the coordinate system. The lines end up in a circle because the orientation of that axis, relative to the device context, changes with each rotation.

The code uses a similar technique to draw the red and black backgrounds of the numbers. The code knows how to create the semi-circular wedges that make up these backgrounds. It simply uses a rotational transformation to repeat that graphic at fixed intervals around the circle. In this case the code is complicated a bit by the need to change the color of the shape depending on its position. Because the transformations involved are the same as those demonstrated in Listing 5.2, that code is not repeated here.

Drawing the numbers on the wheel uses this same rotational trick again, but it adds a slight twist. In the code sample of Chapter 3, was a routine that centers a particular text string around the origin. To get the numbers centered in their carefully constructed backgrounds, you not only use a rotation to orient the axes in the proper direction, but you combine that with a translation that moves the coordinate system away from the center of the figure and out to the band of alternating backgrounds. The code that draws the numbers can be found in Listing 5.3.

Listing 5.3. Combining Rotations and Translations to Draw the Numbers

# Draw the numbers
fontSize = 8;
textRadius = innerNumberRadius + (outerNumberRadius - innerNumberRadius)
/ 2 + (fontSize / 2);
cgContext.selectFont("GillSans-Bold", fontSize, kCGEncodingMacRoman)
cgContext.setRGBFillColor(1.0, 1.0, 1.0, 1.0);

cgContext.saveGState()

# rotate the y axis to point to the right
cgContext.rotateCTM(-math.pi / 2);

for index in range(numSlots) :
    angle = index * slotAngle
    cgContext.saveGState()
    cgContext.translateCTM(0, textRadius - fontSize / 2);
    CenterTextInContextAtPoint(cgContext, 0, 0, numberSequence[index])
    cgContext.restoreGState()
    cgContext.rotateCTM(slotAngle);

cgContext.restoreGState();

This section of the code begins with some simple calculations to find the radial distance at which the text should be drawn. The first number that the code draws is the "00" slot of the wheel. Because Quartz always draws text so that "up" on the text points in the direction of the positive y axis, the first transformation rotates the coordinate system so that positive y points to the right. The code then enters a loop that contains two transformations. The inner transform moves the origin from the center of the figure, up the y axis, to the radial distance for the text that we calculated earlier. This puts the origin on the eventual baseline for the text, in the proper position within the number's field. After calling the routine that draws the number, the code restores the graphics state, which undoes the translation but leaves the y axis oriented through the center of the particular field we are drawing. The second rotation in the loop turns the y axis so that it points to the next slot along the wheel.

The code sample finishes the drawing with some simple embellishments that don't require much in the way of transformations. The final touch is the addition of the four diamonds on the outside edge of the wheel. The code that draws the diamonds uses the same technique that we used to draw the numbers. The code defines a simple routine that understands how to draw the diamond-shaped path centered on the origin. To position the individual diamonds, the code combines a rotation and a translation to move the origin to the right location and then draw the diamond.

Hopefully this code sample has given you some ideas about how you can use transformations to simplify the drawing process. This code sample understands how to draw a few basic shapes, lines, semi-circular wedges, and some basic text. It uses transformations to both help define these shapes, and position them relative to one another. Learning to apply transformations in the proper order requires a bit of practice and a healthy dose of creativity. Once you have the knack of how it is done, however, transformations can greatly simplify the drawing process.


Transformations as Matrices

If you are not familiar with the mathematics behind transformations and have not examined the fields of a CGAffineTransform and NSAffineTransformStruct structures, the technology of transformations may strike you more like smoke and mirrors than hard science. The computer represents transformations using matrices. A matrix is a compact way for representing transformations (more specifically linear transformations), and matrices have a corresponding mathematics, Linear Algebra, that makes it easy to combine transformations and apply them to points.

Quartz 2D uses matrices to represent its affine transformations. The fields of the CGAffineTransform structure refer to specific portions of the matrix that represents the transformation. In this section we will very briefly discuss the matrix representation of transformations and how the fields of the CGAffineTransform structure relate to that representation. Unfortunately, this is not the proper context to present an extensive introduction to matrix mathematics and the way it relates to linear transformations of 2D graphics. Most modern introductory graphics texts cover matrices and transformations in some detail, and the curious reader should seek one of those for more information.

In its simplest form, a matrix is simply a sequence of numbers. When you write down a matrix, you usually arrange the numbers into a rectangular array with a fixed number of rows and columns of numbers. In representing the affine transformations for 2D graphics, the matrices contain three rows and three columns (a 3x3 matrix). One such matrix is the "identity matrix." The identity matrix looks like the following:

To combine transformations, the computer multiplies the matrices that represent the transformations together using the rules of Linear Algebra. The identity matrix is unique because if you take any matrix and multiply it by the identity matrix, you get back the original matrix. This is analogous to taking a real number and multiplying it by one.

Homogeneous Coordinates

When the computer wants to apply a transformation to a point, it interprets the point as a matrix and multiplies the point's matrix by the transformation. The result of this multiplication is a matrix that you can reinterpret as a new point. If you have a point (x,y), you can reinterpret it as a matrix of the following form:

The additional 1 at the end of the matrix may seem a bit unusual. This is a necessary addition to the matrix representation of a point. A three element matrix of this type represents a point in a homogeneous coordinate system. The details of what makes a homogenous coordinate system are largely irrelevant to this discussion other than to say this additional component allows you to use represent transformations using 3x3 matrices (if Quartz only had to support scaling and rotation, you could actually get by with just 2x2 matrices and without the additional component in the point matrix).

Matrix Representations of Transforms

The CGAffineTransform structure consists of six fields, a, b, c, d, tx, and ty. Putting these fields into their corresponding space in a 3x3 transformation matrix would look like this:

The fields of an NSAffineTransformStruct are similar except the fields m11, m12, m21, m22 are substituted for fields a through d. The numbers refer to the positions of those matrix elements in the matrix. m11, for example is the matrix element in row 1, column 1. The discussion continues using the a through d notation.

If you were to take the matrix representation of a point and multiply it, this matrix would result:

From this information we see that the a, b, c, and d entries in the matrix are multiplied by the x and y coordinates of our point. These fields correspond to the scaling and rotation potions of the transformation. The tx and ty fields are added to the results. These fields represent the translation portion of the transformation.

The third column of the transformation matrix ends up not contributing anything to the final point matrix. In representing affine transformations, this third column always has the values shown. That is why the CGAffi neTransforamation structure only contains six elements but represents a 3x3 (or nine element) matrix.

We can represent the three basic transformations using matrices as well. Here is the matrix that represents a translation:

The tx and ty elements of the matrix are the x and y offsets that are applied to the point. Substituting this matrix into the preceding matrix multiplication equation, you can see that sending a point through this matrix yields the point (x + tx, y + ty)(x + tx, y + ty).

The matrix for a scale transform looks like this:

The sx and sy entries represent the x and y scaling factors respectively. If this matrix is substituted into the point multiplication equation, the resulting point is (sx*x, sy *y)(sx*x, sy *y).

The rotation matrix requires augmentation of Linear Algebra with a touch of trigonometry. The matrix that represents a rotation incorporates the angle that we want to use in our rotation by using the sin and cos functions of trigonometry. The matrix that represents a rotational transformation is

Mulitplying this matrix by a point matrix yields a longer expression than the previous matrices:

If your application contains its own matrix mathematics, you can perform the calculations needed to construct a matrix yourself and simply fill in the fields of a CGAffineTransform structure. The Quartz 2D API provides a convenience routine, CGAffineTransformMake, to aid you in this task. Once you have an affine transformation that you've constructed this way, you can use it anywhere you need a transformation.

The transformations in Quartz 2D are one of the most pervasive and powerful features of the library, and using them effectively can greatly simplify the drawing process. More importantly, however, using transformations can add a lot of interest to your graphics. Prior to Quartz 2D, creating dials with spinning indicators or tabs with vertical orientations was difficult because QuickDraw did not make it easy to rotate graphics or text. With Quartz 2D and its transformations, these kinds of graphics are not only possible, but rather simple to produce. Keep your eyes peeled for opportunities to use transformations as the discussion of drawing with Quartz 2D continues.


Chapter 6. Line ArtBuilding Paths

Many of the graphical elements commonly found in graphics libraries, like lines, rectangles, simple polygons, and ellipses all fall into the line art category. With the line art system of Quartz 2D you can create these basic shapes and enjoy all the resolution independence of the library as well as modern graphics features like antialiasing and hardware acceleration. At the heart of the Quartz 2D line art system is a concept called the path. The overview of the Quartz imaging model in Chapter 3, "Introduction to Quartz 2D," contained the briefest glimpse of what paths are. This chapter takes a much closer look.

Figure 6.1 illustrates a number of path-based shapes and will serve as an excellent platform for an overview of paths and how Quartz 2D uses them. Each of the seven motifs in the figure is built from a different path, and the figure as a whole demonstrates many characteristics of paths. For example, you can see that paths are constructed from straight and curved segments and then colored using two different primitive drawing operations.

Figure 6.1. A Collection of Shapes Made from Paths

In Figure 6.1, the line in the upper left corner and the star on the lower left are both drawn by stroking a path. The other shapes in the figure are drawn by filling paths. The blob in the lower center and the outline of the State of Texas have been stroked and filled. The Texas path uses a relatively thin stroke while the blob uses a thicker line on its stroke.

The circular shape in the top center, the blob, and the letter A are all paths with holes in them. In fact, a path can contain more than just holes; a path can describe disjoint areas. The three small circles in the lower right corner of the figure are actually a single path formed of three disjoint, circular areas.

Stroking and filling are the two primary drawing operations for line art. The fact that there are only two drawing operations might lead you to conclude that the line art in Quartz 2D is not very sophisticated. This couldn't be further from the truth. Although there are only two drawing operations, Quartz 2D offers a large number of parameters that govern the way they draw. Those options and the flexibility of the path give the library a remarkable depth when creating graphics.

This chapter provides the first opportunity to explore the line art features of Quartz 2D and begins with a discussion of paths. To understand how Quartz 2D creates curves, you must learn about bezier curves, the mathematical structure that it uses to represent curved segments. From there you'll be ready to explore the tools and techniques that a Quartz 2D application can use to create paths.

In the next chapter, the discussion of creating line art continues by exploring the two fundamental drawing operations, filling and stroking. That chapter will demonstrate how to take the paths created using the techniques of this chapter and translate them into drawings.


On Paths

Chapter 3 discussed a drawing idiom that this book calls the Crayon Model. To briefly recap that idea, drawing line art with Quartz 2D involves outlining an area of user space and then either filling in that area or drawing a line that runs along its edge. Paths are the tools that you use both to outline an area before filling it and to define a line that you wish to stroke.

If you're using the Core Graphics APIs, paths are stored in instances of class with the rather unimaginative name CGPath. You can either construct a path using the methods of that opaque data type or by using the methods that a CGContext supplies for constructing its current path. With this API you can draw a path by storing it in the current path of a context and calling method on that context to effect the actual drawing.

If you are using the Cocoa frameworks, there is a Cocoa class called NSBezierPath you can use to create, store, and draw a path. In Cocoa, therefore, the path itself and not the graphics context becomes the primary interface for drawing.

The paths constructed through both these API are made from the same components. Exploration of building paths continues in this chapter by examining the components that go into creating a path and then by looking at some of the characteristics of the kinds of paths used in Quartz 2D that make them attractive for drawing.

Components of a Path

Consider the simple path shown in Figure 6.2. For this illustration we have filled the path with gray. Everywhere else, including the white area in the center of the rectangle is actually outside of the path. You might also say this path has a hole punched out of its center.

Figure 6.2. The Different parts of a Path

This particular path is made up of two subpaths, or contours. The first contour, labeled the Outer Contour in the figure, defines the outside edge of the path. The second contour, the Inner Contour, is the boundary of the rectangular hole in the center. Each of these contours is made up of a number of segments. The segments are the straight and curved lines between the dots on the figure. Labels point out one straight segment and one curved segment in the figure. Each contour in Figure 6.2 is made from eight segments.

Both contours are composite curves, that is, curves made up of smaller curves. The contours are created by chaining the end point of one segment onto the starting point of the next segment. This is precisely the way that a Quartz 2D application builds paths. Your application tells Quartz where to start the path and then adds curved segments, one after the other, to create a contour. After completing one contour, you tell Quartz 2D where to start the next contour and repeat the process.

The computer keeps track of the endpoint of the last segment you've added to the path you are working on, and this point becomes the starting place for the next segment. To continue that segment, you supply one or more new points. The number of points you have to supply to define a segment depends on the type of segment you want to add. Straight segments require you to supply only one point, the endpoint of the line. To create curved segments, however, you supply one or two intermediate points in addition to the endpoint. The computer uses these intermediate points to draw the curve, a bezier curve, that connects the two endpoints. Taken together the endpoints and the intermediate points are called the control points of the curve.

There is one more feature of the path contours illustrated in Figure 6.2. The order in which the application adds points to the contour imparts an intrinsic direction for that contour as well. The figure illustrates the direction of the contours with little arrowheads. The inner contour travels in a clockwise direction, and the outer contour turns counterclockwise. Contour directions can come into play when Quartz 2D is trying to determine what areas of the coordinate system are inside the path and which are outside. For more information on this, see the section on winding rules in the next chapter.

Bezier Curves

The technique of using bezier curves in computer graphics was pioneered by a gentleman named Pierre Bezier in the late 1960's. Mr. Bezier was a mathematician working for the Renault car company in France. Researchers at Renault were looking for a way to represent the curved surfaces that made up the bodies of their cars using mathematical techniques that were suitable for implementation on computers. Prior to that the curves were stored as unreliable hand-drawn sketches or as clay models. The result of that research was a simple, yet elegant mathematical model for representing curves that has become the foundation of many computer graphics technologies since.

Quartz 2D allows your application to use two different types of bezier curves as the segments of a path, quadratic and cubic. The terms quadratic and cubic refer to the degree of the mathematical equations that define the curve. For our purposes here, it is not important that we understand the mathematics of the equations involved, but we do need to understand the consequences of selecting one type of curve or the other. Rather than examine the mathematics of the curves, therefore, we look at them as design elements and discuss the advantages and disadvantages of each.

To begin our exploration we will look at individual curve segments and then expand that knowledge to encompass compound curves. Figure 6.3 illustrates both a quadratic and a cubic curve segment. In the figure, the curves themselves are the black lines. Each curve is annotated with small square markers that coincide with the endpoints of the segments. Also shown are small circular markers that denote the locations of additional points called bezier control points (BCPs). The BCPs, along with the endpoints, define the location and shape of the curve. The dashed gray lines connecting the BCPs to the endpoints help to illustrate how the BCPs affect the curve.

Figure 6.3. Bezier Curve Segment Types

Quadratic curves have only one BCP as illustrated in Figure 6.3. Cubic curves have two BCPs, one corresponding to each end of the curve. Your application shapes the curves by selecting the location of these control points.

You might think of it this way... from its the starting point, the curve starts out traveling along the straight line segment that connects the end point to the next BCP. However, as the curve moves along, the other BCPs (or the other end points) each draw the curve toward them in turn. By the end of the trip, the curve has reached the second endpoint. When it reaches the end, it is traveling in the direction of the line that joins the last BCP to the end point.

From this description, you might conclude that having more control points allows a curve to be more flexible. This is indeed the case. As you trace a quadratic curve from its start point to its ending point, there is only one BCP to attract the curve. That means that the curve is only allowed to bend once. The quadratic curve, in contrast, has two BCPs, and it can bend twice.

Mathematically, it is possible to create bezier curves in which a single curve bends as many times as you like. As you increase the number of control points in the segment, however, the calculations needed to draw the curve become very expensive. In addition, the intermediate control points of such a curve do not have the same intuitive relationship to the curve that the BCPs of quadratic and cubic curves have. Graphics libraries like Quartz 2D use quadratic and cubic curves because they strike a good balance between graphic flexibility and the cost of computing with them.

Because they contain fewer points, quadratic curves require less storage space for each segment than cubic segments. The mathematics of quadratic curves also involves a lot of dividing by two, which makes it easier for a computer to crunch the numbers as well. The greater flexibility of the cubic curve, however, means that it takes fewer cubic curves, on balance, to describe a complex shape.

Composite Curves

The path building routines in Quartz 2D focus on the task of creating a composite curve from individual quadratic and cubic segments. The segments join together simply by virtue of the fact that each bezier segment defines a curve that passes both endpoints. When you build a curve using a Quartz 2D method, the endpoint of one segment curve becomes the starting point of the next. This guarantees that the two segments will touch.

As you work with composite curves, the way that the joint between one segment and the next looks can be an important aspect of getting the drawing just right. Except in degenerate cases, the segments themselves are smooth curves. If you want sharp corners in your path, it is usually easier to locate the corner at the joint between two segments. By the same token, you might also want to ensure that the end of one segment flows smoothly into the next. All of these effects are controlled by the placement of the BCP handles on the segments on either side of the joint.

Figure 6.4 shows a compound curve constructed from cubic curve segments. It illustrates some of the techniques your application can use at the joints to achieve some common effects.

Figure 6.4. Different Ways of Joining Curve Segments

An interesting thing to note about Figure 6.4 is the addition of a line segment between the BCPs of each segment. The collection of line segments connecting the endpoints to the BCPs and the BCPs to each other forms a polygon called the control polygon.

The figure illustrates the joining points between four different curve segments. At point (a), the joint between the first two segments, the curve makes a smooth transition from one segment to the next. Notice that the last BCP of the first segment, the end point, and the first BCP of the second segment all lie in a straight line (they are colinear). Because the first segment ends on the line connecting the last BCP and the endpoint, and the second segment begins in the direction from the start point to the first BCP, when all these points line up, you get a smooth joint.

At point (b), the two curves meet because their endpoints coincide, but the BCPs on either side of the shared on-curve point are not colinear. The resulting curve shows a bend or discontinuity that forms a sharp cusp.

At point (c) the curve turns smoothly into the line because the control point to the left of the curve is colinear with the line. Lines are really just special cases of curves with the BCPs and the endpoints at the same location.

Finally, point (d) shows that the joint between a curve segment and a straight line need not be a smooth and orderly affair. This segment's first control point is not lined up with the previous segment, so the path draws with a slight corner at this joint.

Features of Bezier Curves

Bezier curves have some additional features that make them attractive for use in computer graphics. Mathematicians like to give these properties complex names, but the ideas behind them are pretty straightforward.

One of the primary benefits of bezier curves is the fact that they are smooth, continuous structures. A computer can take a bezier curve and break it into two or more segments that are also bezier curves. Mathematicians call this process subdivision, and it is an important feature of the curves that Quartz 2D exploits to provide the resolution independence of its line art.

Mathematicians also like to talk about the convex hull property of bezier curves. Basically what the convex hull property tells you is that a curve segment (and by extension a composite curve) will never go outside of the convex hull of its control polygon. An intuitive way to think about the convex hull of a set of points is to imagine putting little pegs in a board at each of the points and stretching a rubber band around the outermost pegs. The shape that the rubber band would form is the convex hull of the curve. Figure 6.5 shows the heart-shaped path from the Chapter 3 sample code with its control polygon and the convex hull of that polygon.

Figure 6.5. Heart-Shaped Path with Convex Hull and Bounding Box

The convex hull property makes it easy to find a bounding rectangle for a bezier curve. Because the curve is completely contained inside of its convex hull, any bounding rectangle of the convex hull is also a bounding rectangle of the curve. That bounding box might not be the smallest bounding box for the curve, but getting a more accurate box requires the computer to calculate points on the curve. Computing the bounds of the convex hull doesn't require any fancy math and is close enough for many applications. Figure 6.4 also illustrates the bounding box of the convex hull.

A third useful feature of bezier curves is what mathematicians call affine invariance. To cut a long story short, affine invariance means that if you want to transform a bezier curve using the kinds of transformations Quartz 2D supports, you can transform the points of the control polygon and then recompute the curve. If bezier curves did not have this property, then you could only transform the curve by calculating a number of points in the curve and transforming the larger set of points. Affine invariance allows the computer to efficiently apply Quartz 2D's affine transformations to curves.


Building Paths

Having discussed many of the properties of paths, it is time to examine the API that applications use to construct paths. If your application wants to create a stand-alone instance of a path using the Quartz 2D APIs, it can call the routine CGPathCreateMutable and keep track of the object returned using a variable of the CGMutablePathRef type. Once you've created an instance of CGPath, you can call methods of the class to construct the path it represents.

It was mentioned in Chapter 4, "The Graphics Context," that the CGContext stores a path, called the current path, as part of its structure. The interface for the CGContext class contains many of the same methods for constructing a path that CGPath provides. These methods make changes to the current path implicitly. Although the methods are very similar to those found in the interface of the CGPath, you should watch the parameter lists carefully. The CGContext also maintains a transformation that is applied to the path as you build it. The CGPath methods accept the transformation as an additional parameter.

If you are working with a Cocoa application you construct a path in an instance of NSBezierPath. The NSBezierPath class has methods that perform the same basic path construction that CGPath and the CGContexts do. The names are slightly different because they follow Cocoa naming conventions, but the basic behavior is the same.

Within all of these interfaces, there are low-level routines that allow you to construct the path by directly adding individual curve segments, and there are higher-level routines that allow you to construct a path using complete shapes like lines, rectangles, and arcs. In every case the routine adds segments, or contours, to the path for you. We'll look at both the low-level and high-level routines in the following sections.

Building from Segments

When a path is first created, it is empty and it contains no points. You can empty the current path of a CGContext using the CGContextBeginPath routine. You can clear out an NSBezierPath using the removeAllPoints method.

An empty path is a clean slate; it contains no points. From this staring point there are a number of fundamental operations from which paths are constructed.

These fundamental operations are common to all the path construction interfaces. The actual routine names, and the parameters passed to them are slightly different but the fundamental operations are the same. A discussion of the operations is next, and then an examination of the differences in the methods follows.

These path construction interfaces allow you to add both straight and curved segments to a path. As the routines add each segment, that segment's endpoint becomes the starting point for next segment. The endpoint of the last segment is called the current point of the path. The methods use the current point to start the segment and accept the other control points of the new segment as parameters.

Move To

Because the path starts out with no points, but the operations that add segments expect the path to have a current point. The first, fundamental operation for constructing a path, therefore, is the one that establishes a new starting point for the contour that follows. The path construction operation that does this is the Move To operation.

The Move To operation takes a point in user space as a parameter. It begins a new contour at that point. The point you pass in also becomes the current point for the path. Because the operations that follow rely on the fact that the path has a current point, it is illegal to try and add a segment without first having established a current point using the Move To operation.

Line To

The Line To operation adds a straight line segment to the current path. This operation takes a single point as a parameter and adds a line segment from the current point of the path to the new point. The endpoint of the segment becomes the new current point for the path.

Curve To

The Curve To operation adds a cubic curve segment to the current path. You pass in three points. The segment starts with the current point of the path. The first parameter is the first BCP of the bezier segment. The second parameter is the BCP that corresponds with the endpoint of the segment. The last parameter, of course, is the endpoint itself. This last point becomes the current point of the path.

Quad Curve To

To add a quadratic curve segment, you use the Quad Curve To operation. This operation accepts two points, the first of these being the quadratic curve's BCP and the second point, the endpoint of the segment. As in the other fundamental operations, the endpoint becomes the current point of the path.

Close Subpath

The final path primitive is Close Subpath. Subpath, in this case, is synonymous with contour. When closing a contour, if the endpoint does not match the starting point, the computer adds a line segment between them. The contour now forms a complete circuit and has no endpoint. In fact, after a close subpath operation, the path itself no longer has a current point. To begin a new contour, you start over with the Move To operation once again.

The importance of properly closing a path cannot be overemphasized. There is a significant difference between a closed contour and a contour whose staring and ending points happen to coincide. Closing a contour can have a profound impact on the way that contour is drawn. For example, when stroking a path, the computer uses different techniques to draw the endpoints of the path than it uses to draw the joining points between segments of the path. If you don't close a path, the drawing may not look the way you expect it to. Figure 6.6 illustrates the kind of effect you might see if you do not close a path properly.

Figure 6.6. The Effect of Closing a Stroked Star Path

The star-shaped path in Figure 6.6 has a wide stroke applied to it. In both halves of the figure, the single contour of the path starts and ends at the apex of the star. The path on the left is an open path whose starting and ending points are at the same location. In contrast, the path on the right is closed.

The two endpoints of the open path coincide but are not joined. The computer draws them as the ends of the segments. As a result, the apex of the star is missing. Contrast this with the figure on the right where the point at the top of the star is a joint between the first and last segments of the path. The computer draws this as a joint, instead of two end caps, and the star is complete.

API Differences

It has already been mentioned that each of the path construction interfaces, CGContext, CGPath, and NSBezierPath, use the fundamental drawing operations just discussed to create their paths. There are, however, some slight differences in the APIs that are worth mentioning.

The routines that perform the basic operations for a CGPath are

CGPathMoveToPoint

CGPathAddLineToPoint

CGPathAddQuadCurveToPoint

CGPathAddCurveToPoint

CGPathCloseSubpath

Where these basic operations call for points, the method calls accept the x and y components for the path instead. Each of the routines in CGPath that accept points also allow you to pass in a transformation. The computer will apply this transformation to the points before it adds the new segment to the path.

The routines that construct the current path of a CGContext using segment operations are very similar:

CGContextMoveToPoint

CGContextAddLineToPoint

CGContextAddQuadCurveToPoint

CGContextAddCurveToPoint

CGContextClosePath

The CGContext itself already has a transformation associated with it, so these routines do not have the transformation parameter found in the CGPath interface. Other than that small change, however, the routines are very similar to those found in CGPath. Note that the Close Subpath operation is simply called ClosePath in the CGContext API. Its behavior, however, is the same.

NSBezierPath extends the basic path construction interface just a bit. The fundamental routines are named

moveToPoint:

lineToPoint:

curveToPoint:controlPoint1:controlPoint2:

closePath

The NSBezierPath doesn't contain a method for adding a quadratic curve segment to the path. If you want to work with quadratic curves, you will have to use the Core Graphics API directly.

These routines accept NSPoints as parameters instead of using individual components. The routines also do not accept a transformation on the parameter list. Instead, you can construct an entire path and then use the transformUsingAffineTransform: method to change all the points of the path.

In addition to the basic methods just mentioned, the NSBezierPath interface also contains the routines

relativeMoveToPoint:

relativeLineToPoint:

relativeCurveToPoint:controlPoint1:controlPoint2:

The names of these routines point out the operations they relate to. The difference between these and the previous routines is the way that the parameters are interpreted. Rather than treating the points as absolute locations in user space, the components of the paths are interpreted as component-wise offsets from the current point. You can achieve this same effect in the CGPath and CGContext APIs by retrieving the current point from the path (using CGPathGetCurrentPoint or CGContextGetPathCurrentPoint) and performing the addition yourself.

Segment Construction Sample Code

Constructing paths using the segment routines is pretty straightforward. Nevertheless, it doesn't hurt to see at least one example of the APIs at work. This sample creates a path that is one-half of the silhouette of a fancy bottle. This image might be a small part of a larger image of a wizard's workshop. The image that this sample creates is not complex, but it does demonstrate the use of both straight and cubic curve segments. The bottle also contains some smooth transitions and some transitions that create sharp corners. The image is little more than a path that is stroked to make it visible. Figure 6.7 shows the path generated by the code sample.

Figure 6.7. The Bottle Profile from the Code Sample

Listing 6.1 presents the sample code that generates the bottle profile path.

Listing 6.1. Routine to Generate the Bottle Profile Path

void DrawBottleProfile(
    CGContextRef cgContext,
    const HIRect &viewBounds)
{
    CGContextBeginPath(cgContext);
    CGContextMoveToPoint(cgContext, 333, 0);
    CGContextAddCurveToPoint(cgContext, 333, 0, 332, 26, 330, 26);
    CGContextAddCurveToPoint(cgContext, 330, 26, 299, 20, 299, 17);
    CGContextAddLineToPoint(cgContext, 296, 17);
    CGContextAddCurveToPoint(cgContext, 296, 17, 296, 19, 291, 19);
    CGContextAddLineToPoint(cgContext, 250, 19);
    CGContextAddCurveToPoint(cgContext, 250, 19, 241, 24, 238, 19);
    CGContextAddCurveToPoint(cgContext, 236, 20, 234, 24, 227, 24);
    CGContextAddCurveToPoint(cgContext, 220, 24, 217, 19, 216, 19);
    CGContextAddCurveToPoint(cgContext, 214, 20, 211, 22, 207, 20);
    CGContextAddCurveToPoint(cgContext, 207, 20, 187, 20, 182, 21);
    CGContextAddLineToPoint(cgContext, 100, 45);
    CGContextAddLineToPoint(cgContext, 97, 46);
    CGContextAddCurveToPoint(cgContext, 97, 46, 86, 71, 64, 72);
    CGContextAddCurveToPoint(cgContext, 42, 74, 26, 56, 23, 48);
    CGContextAddLineToPoint(cgContext, 9, 47);
    CGContextAddCurveToPoint(cgContext, 9, 47, 0, 31, 0, 0);

    CGContextStrokePath(cgContext);
}

The code constructs the path from the right-hand side of Figure 6.7 to the left (the top of the bottle to the bottom). The locations of segment transitions are carefully placed to correspond to portions of the path that have sharp bends. The code demonstrates that even complex shapes can be built from simple curves and straight segments.

Constructing Common Shapes

The segment construction APIs can create any path that Quartz 2D is capable of working with. There are, however, some basic shapes, like rectangles, that are pervasive in computer graphics. Quartz 2D provides a number of routines that make it easy for applications to construct paths for common shapes. These routines are built on top of the basic path construction APIs. They construct the shapes using one or more of the fundamental segment types and add the resulting contours to the path. This section explores the higher-level path construction routines and the services they provide.

Lines

If you need to add a single line to the current path, you can simply use Move To and Line To in the API of your choice.

If you need to add a long string of lines (sometimes called a polyline), you can use CGContextAddLines, CGPathAddLines, or the NSBezierPath method appendBezierPathWithPoints:count:. These routines are also convenient mechanisms for creating polygons and accept an array of points (and the number of points in the array). If the current path is empty, the routine simply creates a path consisting of line segments that connect the points in the path. If the path has a current point, however, the computer adds a line segment from the current point to the first point in the array and then appends the additional segments to the end of that. The computer will not close the path at the end, though you are welcome to do so yourself.

Rectangles

Rectangles are a popular graphics primitive found in all but the most basic graphics libraries. In Quartz 2D, you could easily construct a rectangular path using line segments, but that practice would soon become very tedious. You can add an entire rectangular contour to a path using the Core Graphics methods CGContextAddRect and CGPathAddRect or with the NSBezierPath method appendBezierPathWithRect:. These routines add the rectangle just as if you had executed the segment construction methods to build them directly.

If you are working through the Core Graphics APIs, you can use the routines CGContextAddRects and CGPathAddRects to build a large number of rectangular contours into the path at once. The routines accept C-style arrays of CGRect structures and add all of those rectangles to the path in one efficient routine call. NSBezierPath does not have the same functionality, but you could get the same effect by calling appendBezierPathWithRect: repeatedly.

Ellipses (Ovals)

If you want to add an elliptical contour to your path, you can use the Core Graphics routines CGContextAddEllipseInRect and CGPathAddEllipseInRect. NSBezierPath users can call the routine appendBezierPathWithOvalInRect:. The ellipse that you add is a complete, closed contour with a clockwise direction and is inscribed inside the rectangle you provide with the center of the ellipse coincident with the center of the rectangle. If you use a square instead, the routine will add a circular contour to the current path.

Circular Arcs

If you don't want an entire circle, you can ask Quartz 2D to add a circular arc to your path. Quartz 2D offers two different techniques for specifying the shape of the circular arc you want to add. Each technique uses slightly different parameters, and you may find, based on the information you have on hand, that one technique makes it easier for you to add an arc than the other.

The first technique uses the center, radius, and sweep angles to construct an arc. This technique is illustrated in the following figure.

Figure 6.8. Specifying a Circular Arc Using Sweep Angles

This figure shows two arcs that start at 0° and sweep around the circle to 120°. The starting and ending angles are parameters you pass to the appropriate method. The figure also shows the difference between the clockwise and counterclockwise arcs for those two angles. To generate this kind of arc you can use the routines CGContextAddArc, CGPathAddArc, or appendBezierPathWithArcWithCenter:radius:startAngle:endAngle:clockwise:, depending on which API you are working with.

The second building technique builds an arc based on the current point of the path, two points passed in as parameters, and a radius. It uses the points to define two directions that become the incoming and outgoing directions for the resulting curve. This technique is particularly good at creating a circular arc that rounds off a sharp corner. This includes creating rounded rectangles with a particular corner radius. Figure 6.9 illustrates the second circular arc technique.

Figure 6.9. Specifying a Circular Arc Using Start and End Directions

When using this technique, you supply the routine with two points (which we'll call p1 and p2), and a radius. Those points, in conjunction with the current point of the path, define two directions. The circular arc will start along the line that passes through the current point and p1. The curve will also start so that the line is just tangent to the curve. From there the arc will curve around with the specified radius until it meets the line between p1 and p2. The curve leaves in the direction from p1 to p2.

Another way to think of this is to imagine the angle formed by the current point, p1, and p2. The computer is going to nestle a circle into this angle so that it just touches each of the lines that form the angle. It then adds the circular arc that connects the two lines to the current path.

When you call the routine, if the current point does not coincide with the starting point of the circular arc, the computer will add a straight segment from the current point to the start of the circular arc for you and then draw the arc.

To create a circular arc using this technique, you use the Core Graphics routines CGContextAddArcToPoint or CGPathAddArcToPoint. If you are using Cocoa you can use the NSBezierPath method appendBezierPathWithArcFromPoint:toPoint:radius:.

A more concrete example may help you to understand the different circular arc construction methods. Suppose you want to create a path that outlines a the shape of a circular cog. This cog is illustrated in Figure 6.10.

Figure 6.10. The Circular Cog Path

The code that generates the circular cog path (and strokes it) is shown in listing 6.2.

Listing 6.2. Routine to Draw the Circular Cog Path

void DrawCircularCog(
    CGContextRef cgContext,
    const HIRect &viewBounds)
{
    const float radius = 100;
    float halfAngle = 22.5 * pi / 180.0;
    CGPoint midPoint = CGPointMake(
            radius * cos(halfAngle),
            radius * sin(halfAngle));
    float curveRadius = radius * sin(halfAngle / 2);

    CGContextMoveToPoint(cgContext, radius, 0);
    for(short ctr = 0; ctr < 8; ctr++) {
           CGContextAddArcToPoint(cgContext,
                   0, 0,
                   midPoint.x, midPoint.y,
                   curveRadius);
           CGContextAddArc(cgContext,
                   0, 0,
                   radius, halfAngle, pi / 4, false);
           CGContextRotateCTM(cgContext, pi / 4);
    }

    CGContextClosePath(cgContext);
    CGContextStrokePath(cgContext);
}

This routine exploits the circular symmetry of the cog. Much of the apparent complexity of the routine comes from the need to use radians to specify the angles involved. The cog is built by repeating the same motif eight times around the circle. After drawing each motif, the code rotates the coordinate system by 45°(p/4 radians) and continues with the next motif.

Figure 6.11 illustrates the motif that is repeatedly drawn inside the for loop.

Figure 6.11. A Single Circular Cog Motif

There are two parts to the motif. The two parts together cover 45°, or 1/8 of a circle. The first portion of the motif is created using CGContextAddArcToPoint.

The curve starts at the x axis, extends toward the center of the circle, and then returns to the outer edge of the cog. To accomplish this the code computes the point on the circle midway through the 45° angle (midPoint in the code) and uses that point and the origin as the parameters to the arc routine. The radius for this arc is carefully selected so that the resulting curve will end on the midpoint.

The second half of each motif is created using CGContextAddArc. The starting angle is simply the 22.5° mark (half of the 45° sweep of the motif), and the ending angle is simply the 45° mark. As each motif is drawn, the endpoint of the previous motif becomes the starting position for the next one.

Adding Paths

If you construct a stand-alone path, either as a CGPath or an NSBezierPath, you can add that entire path and all its contours to another path. This not only allows you to create more complex paths from smaller elements, but it is also the key to drawing CGPaths in a CGContext.

The routines you use to combine paths are CGContextAddPath, CGPathAddPath, and appendBezierPath:. Each of these routines accepts the path you want to add as a parameter.

When you are working through NSBezierPath, you use that class to both store and draw the path. The CGPath interface, however, does not include any methods for drawing a path. To draw a CGPath object, you have to first copy the object into the current path of a CGContext. To do that, you use the routine CGContextAddPath, which adds the path to the current path in the context. If you want to draw the CGPath object by itself, you can first clear the current path in the context with CGContextBeginPath. Once your CGPath is in the current path of a context, you can stroke or fill it using the usual CGContext routines.

Additional CGContext Functionality

The CGContext API contains an interesting routine that constructs a path from the stroked representation of another path. The routine that accomplishes this is CGContextReplacePathWithStrokedPath, which takes into account the stroke parameters in the context (things like stroke with and line join style) and the current path. From these, the routine generates a new path that outlines the portion of user space that would be colored if the computer were to stroke the current path with those parameters. It replaces the current path in the context with this new path.

You can use this routine to achieve a number of special effects. For example, if you want to color the stroke of a particular path with a pattern, you can use CGContextReplacePathWithStrokedPath to construct the appropriate path and then use the usual CGContext routines to fill that path with a pattern. The routine also helps if you want to use the stroke of a path as a clipping area.

Additional NSBezierPath Functionality

The NSBezierPath class has two methods that make it easy to create entire paths from simple shapes. As its name implies, bezierPathWithRect: creates and returns an NSBezierPath instance that contains a rectangular contour. Similarly, bezierPathWithOvalInRect: returns an NSBezierPath instance that contains an oval contour.

There are two routines that construct new paths from existing paths. bezierPathByFlatteningPath is an instance method that asks a path to return a new bezier path that is a flattened representation of itself. Flattening a path involves taking every curve segment in the path and replacing it with a series of short straight line segments that closely approximate the path.

The bezierPathByReversingPath routine returns a new bezier curve, but the directions of all the contours in the path run in the opposite direction. This can be a handy routine to use when constructing a path that you will eventually use the non-zero winding rule to fill. More information on winding fill rules can be found in the next chapter.

Finally, the NSBezierPath class contains a number of routines that make it easy for you to extract the shapes of the glyphs from fonts and use them when constructing paths. Those methods are

appendBezierPathWithGlyph:inFont:

appendBezierPathWithGlyphs:count:inFont:

These routines append the paths that form glyphs from a particular font. The path that you are appending the letter shapes to must have a current point at the time you call this routine. That current point will serve as the text location for the glyphs you are adding to the path. Note that these routines accept Glyph IDs and not characters. If you want to draw text, there are much better options available. However, this can be a handy pair of routines if you want to extract the paths from some text and manipulate those paths before drawing them.


Path Drawing Performance

In this section, some Quartz drawing issues are presented that affect not only the appearance of Quartz 2D drawings, but also the performance of working with Quartz 2D graphics. These issues focus, very narrowly, on specific situations, situations that arise often enough to be worth mentioning here.

The Half-Pixel Line "Problem"

In most pixel-based graphics libraries, the computer draws its graphics on pixel boundaries. Much time up to this point has been spent describing how Quartz 2D is a resolution independent API. When using Quartz 2D to draw to the screen, programmers often run into a problem where the horizontal and vertical lines that Quartz draws appear blurry or indistinct. This problem is particularly noticeable with one-point wide lines when the developer expects the line to draw one pixel wide, and it actually draws over two pixels. This is the result of the way Quartz 2D draws its lines and treats pixel boundaries. Figure 6.12 is a conceptualization of a pixel grid positioned over a one-point wide line as drawn in user space.

Figure 6.12. Quartz Drawing a Line on a Bitmap

The black rectangle in the figure represents a line that Quartz 2D has mapped from user space onto a pixel grid. You might imagine that this horizontal line was specified with the endpoints (1.4, 1) and (8.65, 1). The line was drawn with a line with of 1. When Quartz maps the endpoints of the lines onto the pixel grid, however, it maps the y coordinate whose value is 1 to a line between two pixels. Because the line extends a half a pixel on either side of that boundary, the line covers two pixels instead of one. When Quartz 2D rasterizes this line, it will shade the pixels on either side of the boundary with a tint of the stroke color. The resulting line ends up looking fuzzy and indistinct because its image has been spread across two pixels instead of the one pixel that the programmer expected.

There are a number of ways to solve this problem. Perhaps the most obvious is to add or subtract a half a point from the coordinates of the line. This places the integer valued coordinates of user space at the centers of pixels instead of on the boundaries between pixels. For example, if you choose to add a half point, the coordinates in the previous example would be (1.9, 1.5) and (9.15, 1.5). Another way to achieve the same effect without having to change each point you specify is to offset the transformation matrix of user space by ½ pixel in either coordinate direction. A third potential solution is simply to turn off antialiasing for the context using CGContextSetShouldAntialias.

Antialiasing in Complex Paths

When working with complex paths, there is a problem that is very similar to the half pixel line problem that affects rendering performance. The problem comes about as a result of antialiasing. Consider the path shown in Figure 6.13

Figure 6.13. Intersecting Paths with Crossover Pixels

Figure 6.13 illustrates two intersecting path segments. These segments are drawn over the pixel grid that they will eventually be rasterized onto. Two of these pixels are called out in the diagram because both segments intersect those pixels simultaneously.

When the computer is drawing these segments, it will apply some color to both of the crossover pixels. If the segments are drawn separately, it is correct for the computer to color the crossover pixels once when the first segment is drawn and then again when the second segment is laid down on top of it. If the two segments are part of the same path, however, the crossover pixels should be colored only once, but with roughly twice the contribution from the path.

To correctly render these two segments when they are part of the same path, the computer must recognize that the two segments intersect and figure out what pixels the segments share. This calculation can be very time consuming. If you create a complex path with many self-intersections, running these calculations can be a significant performance problem.

If you run into this problem in your own application, there are a couple of things you might try to alleviate the problem. They stem almost directly from the nature of the problem itself, so you can try splitting your complex path into one or more simpler paths and draw them sequentially rather than all at once.

This will generate a slightly different image than drawing a single path. If your path has lots of small bends and curves that are less than a pixel in size, you might consider simplifying the path to remove those variations that are too small to be seen at the resolution you are rasterizing them. Finally, if necessary, you might consider rendering the complex paths without antialiasing (perhaps as the user is manipulating them) and leave the final, higher quality rendering for a separate step that is done only at the discretion of the user.

Drawing Many Lines and Rectangles

The process by which Quartz 2D renders lines of arbitrary width with antialiasing is a bit more involved than the line drawing mechanisms of traditional graphics libraries. As a result, programs that generate a large number of lines and/or rectangles as part of their graphics often find the performance of Quartz 2D disappointing. Typically this is the result of a direct mapping that takes some combination of MoveTo/LineTo commands into a similar set of Quartz 2D drawing commands.

When you have a large number of lines to draw, you have several choices. Apple has enumerated four of these choices in a set of sample code that directly addresses the performance characteristics of drawing a large number of lines. This code sample is available on the web at http://developer.apple.com/samplecode/QuartzLines/QuartzLines.html

This code sample compares the performance of drawing lines in the following ways:

  • Each line is drawn as a separate path.

  • Several lines are collected together into a single path.

  • The lines are drawn using bulk drawing routines in Tiger.

  • Finally, the code limits the number of lines drawn.

These drawing techniques are listed roughly in order of increasing performance.

The bulk drawing routine mentioned for Tiger is CGContextStrokeLineSegments. This routine accepts an array of points and treats them as the endpoints of a series of connected line segments. The computer will stroke this poly-line using the current settings of the context in a very efficient manner.

By far the most effective of the techniques demonstrated in Apple's sample is the last one, limiting the number of lines you draw only to those that the user will see. One obvious way to incorporate this suggestion into your application is to only draw shapes that will intersect the visible area of user space. However, your application may also choose to filter out lines that have little effect on the final image. If a line will take up less than a pixel when transformed from user space to the output device, then it may not be worthwhile to even submit it to Quartz for rasterization. If you can cull many such lines from your graphic, you may receive a significant performance boost. By the same token, if you draw one line using a opaque color and overwrite that line with another that is also opaque, then the computation that went into drawing the first line has largely been wasted. This kind of thing might happen if you are trying to draw a signal trace with 1000 samples into a bitmap that is only 100 pixels wide. If you can prefilter the data set to remove lines that are obscuring one another, you can improve the performance of your application.

NSFrameRect and Friends

If you are working in Cocoa and drawing to a pixel-based context and are willing to trade a bit of the precision that extends from Quartz 2D's rendering process for higher performance when drawing rectangles, you can use the Cocoa routine NSFrameRect.

NSFrameRect speeds up the rendering of a simple stroked rectangle by sacrificing some of the calculations that Quartz 2D typically uses to properly calculate the line joins at the corner of a rectangular path. If you wish to draw a rectangle quickly and can sacrifice that precision, then NSFrameRect and the related routines NSFrameRectWithWidth and NSFrameRectWithWidthUsingOperation may be just the ticket.

This chapter was a first introduction to creating line art in Quartz 2D. Paths are an important tool for creating graphics in Quartz 2D, and this chapter showed how paths are constructed from segments and contours. The discussion of line art in Quartz 2D continues in the next chapter where the text explores the options available when stroking or filling a path.


Chapter 7. Line ArtDrawing

Previous chapters have discussed the Crayon Model, the technique Quartz 2D uses to draw line art. In the Crayon Model, your application draws by first defining a path and then asking the computer to paint that path onto a graphics context. Chapter 6, "Line ArtBuilding Paths," covered the techniques you use to construct the paths that you want to draw. There are two basic drawing operations you can apply to a path. Filling a path involves painting the interior of the path with a solid color, a pattern, or a nice gradient of colors called a shading. When the computer strokes a path, it traces a line along the path. This chapter continues the exploration of line art by describing the options that affect these two drawing operations. Along the way we will look at some more aspects of a path such as the joints between segments and the way the computer determines what the interior of a closed path is. By the time we are done you should have a good understanding of how to draw the paths you've constructed.


Drawing Basics

Cocoa and the Core Graphics APIs use slightly different models to handle drawing paths. Cocoa encapsulates a path into an object, an instance of NSBezierPath, that knows how to draw itself. The Core Graphics API, in contrast, relegates all drawing functionality to a CGContext.

Drawing with Quartz 2D

While the Core Graphics API set does include the CGPath abstract data type discussed in the previous chapter, instances of that class don't have any drawing behavior of their own. A CGPath instance must be copied into the current path of a CGContext before the computer can draw it. After copying the path into a context, the methods of the CGContext data type change the drawing parameters stored in the graphics state to affect the way the path is rendered. Finally, there are routines of the CGContext that stroke or fill the path using those parameters.

The interface of a CGContext includes several methods that accomplish the actual drawing. There are simple routines that simply stroke or fill the path. There are also routines that can stroke and fill a path in a single drawing operation. Regardless of the routine, however, the drawing operation will consume the current path and leave it empty.

In PostScript, stroking or filling a path also consumes the path. Unlike PostScript, however, Quartz (and PDF) do not save the current path as part of the graphics state in a contextafter you execute a drawing operation, the path is irrevocably lost. If you want to draw that same path again, you will have to reconstruct it using the context routines first (or recopy a CGPath object into the context).

The CGContext interface also contains a number of utility routines that can short-circuit the steps required to draw simple shapes in a context. For example, the routine CGContextFillRect creates a rectangular path and fills it, all in one operation. These convenience routines will also destroy the current path of the context.

Drawing with NSBezierPath

An NSBezierPath is a bit more sophisticated than a CGPath. NSBezierPath objects know how to draw themselves. To stroke or fill an NSBezierPath you call the appropriate methods of the object itself. The path will be drawn in the current NSGraphicsContext. Most of the drawing parameters that manipulate the appearance of the stroke and the fill are also part of the NSBezierPath API. Moreover, unlike drawing with the basic Core Graphics APIs, drawing an NSBezierPath does not consume the NSBezierPath object. You are free to redraw the same object many times.

Cocoa also provides a number of functions that draw simple shapes into the current graphics context. For example, the NSFrameRect family of routines draws rectangles in the current NSGraphicsContext. You must be careful, however. NSFrameRect does not draw a rectangle the same way that creating a rectangular path and stroking the rectangle would. This routine draws a rectangle that is immediately inside the rectangle you pass it, rather than having the stroke straddle the edge as you might expect. See the NSFrameRect documentation for more information.


Fills

Filling a path colors the interior of that path. There are several techniques that Quartz 2D can use to fill a path. By far the most common is simply filling the path with a solid color. Quartz can use any color it can represent to fill a path. In particular, the alpha channel in Quartz 2D's color model offers the advantage of being able to use translucent colors to fill paths.

Quartz 2D can fill paths with more than just solid colors. If you are working through the base Core Graphics APIs, you also can fill a path with a repeating pattern or a smooth gradient of colors called a shading. To use these special fill types, an application must create a number of supporting objects. For that reason, discussion of shadings and patterns is deferred to Chapter 13, "Shadings and Patterns."

Regardless of what you use to fill the path, however, the computer has to determine, "What part of user space is the inside of the path?" For simple paths, shapes like rectangles, circles, and other basic closed loops, it is pretty easy to figure out what part of user space constitutes the interior of the path. For more complex paths such as those with lots of self-intersections or paths that consist of more than one contour, the question becomes much less intuitive. In every case, Quartz uses a technique called winding rules to find the interior of a path.

Winding Rules

The winding rules in Quartz 2D are based on simple tests. The tests take under consideration a point in user space and a path. They examine the way the path winds around that point and determine whether or not that particular point is inside the path. Quartz 2D is not actually going to apply the winding rules to each point it wants to color. That would take too long. These tests are really a conceptual model that help to describe how the winding rules affect drawing.

Quartz 2D supports two different winding rules. They are called the Even/Odd winding rule and the Non-Zero winding rule. Figure 7.1 demonstrates each of these.

Figure 7.1. Winding Fills and the Interior of Paths

To help describe the differences between the two techniques, this diagram has been annotated with some additional information. The dots in Figure 7.1 represent the "particular points" to which we're going to apply the winding tests.

The tests also rely on a line that extends from the point of interest in an arbitrary direction. Figure 7.1 includes arrows that show the (marginally) arbitrary directions that our lines extend from the points. The star-shaped path in the top of the figure is single, self-intersecting contour. The hexagonal paths, in the bottom half of the figure are each built from two contours.

Direction arrowheads have been left off in the left-hand side of the figure because the Even/Odd winding fill produces the same results regardless of the contour directions. The test starts out at the point of interest and travels along the direction line. Each time the line crosses a segment of the path, the test increments a counter. Once outside of the path, if the number of crossings is an odd number, the starting point must have been inside of the path (otherwise it was outside). In Figure 7.1, the paths each cross their reference lines twice so both points are outside of their respective paths. This leads to the observation that both the star and the hexagon have holes in their centers when tested filled with the Even/Odd winding rule.

The Non-Zero winding test uses a similar technique but takes the directions of the contours into account. The test proceeds much as in the Even/Odd winding test, but this time, when the path crosses the reference line, if it is travelling in one direction (say from the top of the line to the bottom in our diagram), then we increment the winding counter. If the path crosses the line in the other direction, the test subtracts one from the counter. After taking into account all the path crossings, if the winding number is zero, the starting point was outside of the path.

The hexagonal paths in the right side of Figure 7.1 demonstrate the importance of path direction in the Non-Zero winding fill rule. Note that when both contours in the hexagonal path are in the same direction, the center of the shape is inside the path. If the contours are in opposite directions, however, the path contains a hole.

Using the Core Graphics APIs directly you select the winding rule you want to use with a path by choosing the appropriate routine. For example, if you want to fill the current path using the Non-Zero winding fill rule, you would simply call CGContextFillPath. If you want to use the Even/Odd rule, however, you would call CGContextEOFillPath. (Similar routines are available for clipping, CGContextClip and CGContextEOClip).

Using an NSBezierPath instance, you select the appropriate winding rule by calling the method setWindingRule:. This method takes one of two constants as a parameter, either NSNonZeroWindingRule or NSEvenOddWindingRule.

Filling Routines

CGContextFillPath and CGContextEOFillPath are two of the routines that ask a CGContext to fill a path. These two routines allow you to fill a path with a solid color or with a pattern.

When using the Core Graphics APIs, the computer uses the CGContext's current fill color to fill the path. The fill color is part of the context's graphics state. There are a number of routines in the CGContext API that allow you to specify a fill color:

CGContextSetFillColorWithColor This routine allows you to set the current fill color using a CGColorRef. A CGColorRef encapsulates both a color space and color components so this routine completely defines the fill color.

CGContextSetFillColor This routine allows you to specify the components of the color to use as a fill color. It accepts a C-style array of floating point color values. The number of elements in this array depends on the current fill color space. If the color space requires N components to represent a color, you must supply N+1 components in the array. The last component is used to specify the alpha value for the color. For example, RGB color spaces require three components (red, green, and blue) to specify a color. If the CGContext is currently using an RGB color space, you will need to supply four components to CGContextSetFillColor. To change the fill color space of a CGContext, you use the routine CGContextSetFillColorSpace.

CGContextSetGrayFillColor This routine sets the current fill color space of the context to a Device Gray color space and changes the current fill color to a gray color based on the arguments to the routine.

CGContextSetRGBFillColor This routine changes the current fill color space to Device RGB and changes the current fill color to the one specified in the argument list.

CGContextSetCMYKFillColor As you might already have guessed, this routine changes the current fill color space to Device CMYK and then sets the current fill color to the one specified in the routine's parameters.

When working with Cocoa, you specify the fill color of the current NSGraphicsContext using NSColor's set or setFill methods. An NSColor object encapsulates all the color space and color value information needed to completely specify a color. The set method changes both the fill and stroke colors of the current context while the setFill method allows you to change the fill color independently. After you have established the fill color, you simply call the fill method of the NSBezierPath instance you want to draw.


Strokes

A stroke, in Quartz 2D parlance, is nothing more than a line that the computer traces along a path. The library will draw the line with a fixed width with the center of the line lies directly atop the path. Apart from the width of the stroke, there are a number of other parameters that affect the way the computer draws the stroke. Figure 7.2 illustrates the anatomy of a stroke and presents several of these drawing parameters.

Figure 7.2. The Anatomy of a Stroke

The white line down the center of Figure 7.2 would not actually be drawn by Quartz. In the figure, it represents the stroked path, in this case two straight line segments. The gray area is the stroke itself and represents what the computer would draw when stroking this path. Notice that the width of the stroke, called the line width, extends to either side of the path. The part of the stroke stretching beyond the end of the path is called the end cap. This particular stroke uses round end caps. The point where the two segments of our path meet is called the join between the two segments. When stroking a path, you have some choices about the way the computer draws the joins. The stroke in figure 7.2 is "chopped off" because this stroke is using a join style called a bevel join.

Your application has control over all these features of a stroke and more. In the Core Graphics API the line parameters are part of the graphics state of the context, which means that you can save and restore them on the graphics state stack. For the Cocoa drawing APIs, the line parameters are stored as part of the NSBezierPath object. Before discussing the methods that apply a stroke to a path, first examine the parameters that your application can use to change the way that stroke looks.

Line Width

The simplest stroke parameter is the line width (a.k.a. stroke width). As seen in Figure 7.2 this is the total width of the line. Half of the stroke width will lie on either side of the path that you are stroking. Line widths are floating point valued and specified in Points. When working with bitmaps, Quartz 2D allows you to specify widths that are less than one pixel after they are transformed to device space. The computer uses antialiasing technologies to render these ultra-thin lines. Figure 7.3 shows the effect of changing the line width for a simple path.

Figure 7.3. Effect of the Line Width Parameter

To change the line width you can use the CGContextSetLineWidth method of a CGContext or the setLineWidth: parameter of an NSBezierPath.

In PostScript, if you set the line width to 0 it is a request to the graphics device to use a one pixel wide line regardless of how large a pixel might be. Quartz does not follow this convention. Quartz will not render lines with a line width of 0.

Changing the line width of the a stroke can affect the other parameters that go into stroking a path. For example, the end caps for the stroke will change to match the width of the stroke. The line width also can have an effect on the length of the miters applied at sharp corners of the path (see the section on the Miter Limit that follows for more details).

The default line width for a newly created CGContext is 1 Point. Without any intervention on the part of your application, the default line width for a newly created NSBezierPath is also 1, but you can change that default by calling the NSBezierPath class method setDefaultLineWidth:

End Caps

End caps allow you to change the way the start and end of strokes are drawn. Quartz 2D supports three different end cap styles. These are illustrated in Figure 7.4.

Figure 7.4. End Caps

All of the strokes in Figure 7.4 are the same length. The strokes with square and round end caps extend beyond the length of the path by a small amount.

To change the cap style in the current graphics state, you use the context method CGContextSetLineCap with any of the three constantskCGLineCapButt, kCGLineCapRound, and kCGLineCapSquare. If you want to change the cap style used when stroking an NSBezierPath, you can use the method setLineCapStyle: with the similar cap constants NSButtLineCapStyle, NSRoundLineCapStyle, or NSSquareLineCapStyle.

By default a CGContext will use butt end caps on any paths you stroke. The NSBezierPath class interface provides the method setDefaultLineCapStyle: to allow applications to change the default cap style for newly created NSBezierPaths.

Line Joins

Changing line joins changes the way that the computer draws the joints between curve segments of the path. The system supports three different line join styles, which are illustrated in Figure 7.5.

Figure 7.5. Line Joins

The method of the CGContext that changes the line join is simply, CGContextSetLineJoin. Just as there are constants for the line caps, there are constants for each of the line joins. They are kCGLineJoinMiter, kCGLineJoinRound, and kCGLineJoinBevel. The similar method that changes the line join style of an NSBezierPath is setLineJoinStyle:. The Cocoa API defines the same constants as NSMiterLineJoinStyle, NSRoundLineJoinStyle, and NSBevelLineJoinStyle.

The Miter Limit

The miter join extends the two outside edges of the strokes until they meet. When the angle between two edges is large, the miter doesn't extend very far beyond the place where the two segments meet. If the two segments meet at a very shallow angle, however, the miter might extend quite some distance from the join point. To avoid this issue, your application can limit the length of the miter by changing a parameter known as the miter limit.

Before discussing exactly what the miter limit means, first look at the effect of adjusting the miter limit. When the angle of a stroke generates a miter join that "exceeds" the miter limit, the computer substitutes a bevel join in its place. Figure 7.6 shows the same angled path drawn with two different miter limits to illustrate this point.

Figure 7.6. The Effect of Changing the Miter Limit for Acute Joins

The paths in Figure 7.6 are composed of two line segments that meet at a 30° angle. When the computer draws the path on the left side of the figure, the miter limit was set to a value of 10. The length of the miter does not exceed the miter limit, so the join is drawn as a miter. For the right side of the figure, we changed the miter limit to 3. In this case, the same 30° line join exceeds the miter limit. The computer draws this join as a beveled join instead.

You've already seen that the miter value for a join is related to the angle at which the two segments meet. The miter join is also affected by the line width of the path that you are stroking. Figure 7.7 shows how you can determine the miter value for a particular join.

Figure 7.7. Miter Value for a Join

As shown in the figure, the miter value for a join is the ratio of the miter length (measured from the inner corner of the join to the outer corner) and the line width of the path. If the miter value of the join exceeds the miter limit, then the computer will substitute a bevel join for the miter.

To change the miter limit of the current context, you can use the routine CGContextSetMiterLimit. If you want to change the miter limit used when drawing an NSBezierPath, you can call its setMiterLimit: method.

Line Dash

Quartz 2D is not limited to drawing only solid lines only. You also can ask the computer to draw a dashed lined. To Quartz, a dashed line is a pattern of line segment lengths. To create this dash pattern, you supply the library with an array of floating point values. The first value tells the computer how long the first segment of the dash should be. The second value gives the length of the gap in the line before the next segment begins. The third value in the array, if specified, gives the length of the next drawn segment (and the fourth value would be the length of the next gap). When the computer reaches the end of the array, it simply repeats the pattern starting over again at the first array entry. Figure 7.8 demonstrates a number of dashed lines with their corresponding value arrays.

Figure 7.8. Some Dashed Lines and their Pattern Arrays

In addition to a pattern array, a dash also has what is called a "phase" setting. The phase tells Quartz how many points into the pattern array the path should start. For example, if the dash pattern were [1, 3, 1, 4] and the dash phase were set to 4.5, the computer would start the dashed line half-way through the second array entry. It would begin the line with a gap 1.5 points long, then a 1 point stroked segment, and a 4 point gap before starting over at the 1 point stroked segment at the start of the pattern.

Dashes can be applied to any stroke, and the dashes themselves are affected by the cap settings of the current context. For example, if you select rounded end caps, each drawn dash segment will receive a round cap as well. Figure 7.9 illustrates the same dash pattern with different end caps selected.

Figure 7.9. Dashed Lines with End Caps

Just as the caps extend a stroke beyond its starting and ending points, the end caps on a dashed lines extend the drawn segments by just a bit. You may need to take this into account when designing a pattern for your dashed lines. The last line in Figure 7.10 shows an interesting application of the end caps applied to dashes. The dashed segments in this line are very short (1/10 Point) so that the end caps dominate the drawn segment of the dash. The end result is a dotted rather than dashed line.

Figure 7.10. Effect of Changing Flatness When Stroking a Path

To change the dash settings on a CGContext, you use the routine CGContextSetLineDash. The line dash is part of the graphics state and can be saved and restored with the rest of the graphics state settings. Like most other settings, the dash pattern is an attribute of an NSBezierPath. If you are working through Cocoa, you can change the dash attributes of a path using the setLineDash:count:phase: method.

Flatness

The flatness of a path is a parameter that controls a rather technical detail of the way the computer draws bezier paths. To draw a path, the computer breaks the curves in the path down into short line segments and draws the shapes using those short segments that approximate the curve. The flatness value is a measure of how closely this approximate curve must match the actual curve itself.

If the computer has a lower flatness setting, it uses shorter line segments and takes more segments to draw the curve. In this case the resulting shape will very closely match the curve. With larger flatness settings, the computer draws the path using longer segments. In general, that means it will have to draw fewer segments, but the shape will not follow the curve as closely. Figure 7.10 illustrates the effect of changing the flatness before drawing a path.

The figure shows the letter G from the Palatino font drawn with three different flatness settings. Notice the way the smoothness of the curve is affected by each flatness setting.

Flatness is a device-dependent setting. Changing the flatness on one device is not guaranteed to give you the same results if you use the same setting on a different device. Because of this, you should rarely rely on changes in the flatness setting for any particular effect. By the same token, however, changing the flatness value usually means that the computer has to work with fewer segments when drawing paths. If you are drawing paths where you can sacrifice a little visual accuracy for some added drawing speed, you may get a performance boost by raising the flatness.


Chapter 8. Image Basics

In introducing Quartz 2D, and throughout the book up to this point, the fact that Quartz 2D has a resolution independent drawing model has been emphasized. It has also been pointed out that Quartz 2D does not, in general, base its drawing model on the concept of a pixel. However, computer graphics is riddled with resolution dependent graphics that are pixel-based. One of the most popular examples today are the images captured by digital cameras. The digital camera works by capturing light and generating a set of color pixels that closely match what it "sees."

To draw pixel-based graphics, like the images from a digital camera, Core Graphics needs some way to model those graphics. The Quartz 2D API uses the concept of an image for this task.


What Is an Image?

Image is a bit of an overloaded term when you apply it to computer graphics. In fact, all of the graphics you create using a computer could be called images. In Core Graphics, however, the term "image" has a specific connotation.

If you wanted to be very exact, you might say that in Quartz 2D, an image is defined as a rectangular array of color samples from an associated color space. Returning to the digital camera, for example, the CCD in the camera captures light from a scene and generates a large set of color values (usually RGB) that represent that light. Each RGB triplet is called a color sample. The camera structures these color samples into a large, rectangular array, and computer graphics professionals like to call each sample a picture element (or pixel). The camera generates a big rectangular array of color samples and that matches the Quartz 2D definition of an image.

This definition is actually a very tedious one for a concept that many software engineers are already familiar with. As an application developer you are no doubt familiar with image files such as JPEGs and GIFs. These file formats encode image data not only from scanners and digital cameras, but also from artificial sources like 3D renderers and ray tracing programs. Graphics programmers familiar with pixel-based graphics libraries will recognize that pixel buffers are also images. The RGB values of the pixels in the buffer represent a color sample from an RGB color space.

Keep in mind, however, that there are some things that people who work with computers often call "images" but that do not match the Core Graphics definition of an image. For example, many graphics professionals refer to PDF files as "images." But PDF data, in general, doesn't match the Quartz definition of an image. PDF data consists of a series of drawing commands consisting of bezier curves, coordinate transformations, and other such things. The data in the file is not simply a rectangular array of pixels, there is no guarantee that all of the colors in the PDF file have come from the same color space. These two facts alone do not fit the Core Graphics definition of an "image."

Image Masks

In addition to images, Quartz 2D also allows you to work with image masks. Like an image, an image mask is a big rectangular array of pixels. Each pixel has an associated value. Unlike images, however, the values of pixels in an image mask do not represent colors. In fact, it doesn't make much sense to talk about drawing an image mask because the mask itself does not contain any drawable information. Instead, your application uses an image mask to modify the way the computer draws an image. Using the mask, the computer can selectively transfer pixels from an image onto a graphics context. The pixel values in an image mask determine how much of the color in the corresponding pixel of the image should be transferred to the drawing destination. Figure 8.1 illustrates the effects of copying an image through a mask.

Figure 8.1. The Effects of Masking an Image

The figure shows an image, a mask, and the results of combining the two. In the masked image, the black part surrounding the flower actually represents transparent pixels in the image.

The image mask is represented in a drawable form so that you can see how the values in the mask affect the way the computer transfers the image onto the drawing destination. Where the mask is black (full masking), the mask prevents the image from drawing in the destination. Where the mask is white (no masking), the image pixels are copied directly to the destination. In places where the mask is between 0 and 1, a proportional amount of the source image is transferred to the destination. Because the original image is completely opaque, the net effect is exactly the same as if the image were blended with the destination using the mask value as the pixel's alpha.

Now that you know what images and image masks are, you can begin to explore how Core Graphics allows you to work with them. This chapter explores the way that Quartz represents images and masks data and examines the characteristics of an image and the opaque data type, CGImage, that Quartz uses to keep track of images. Finally, the various options your application can take advantage of when drawing images is discussed.


Introducing CGImage

Quartz 2D uses the same opaque data type to represent both images and image masks. That opaque data type is simply called CGImage. The CGImage object itself is a surprisingly lightweight entity. Rather than keeping track of a lot of data in and of itself, the CGImage class relies on other objects to manage many details on its behalf. The CGImage object's job is tie together these different objects into a single entity that the computer can draw on a CGContext.

The first important aspect of the image maintained by a CGImage object is its geometry. For a rectangular array of color samples, or pixels, that means that the CGImage keeps track of how many pixels high and how many pixels wide the image is. Because this is working with pixels, a discrete valued entity, CGImage uses integers (more specifically size_t) to represent the height and width of an image.

All of the color samples in the pixels of a CGImage come from a particular color space. The CGImage keeps track of this color space by using an instance of the CGColorSpace opaque data type. The primary job of the color space is to help the computer interpret the values of the pixels. As you will see, however, the color space has a more subtle duty in that it defines how many color components each pixel in the color should have.

A CGImage also needs some means of getting at the data structures that store the actual pixel information. In this task, the CGImage enters a partnership with the code that created it. The CGImage doesn't own any of the image data itself. Instead, it works with an opaque type called the CGDataProvider. A CGDataProvider is simply an object that knows how to supply a CGImage instance with pixel data. Some CGDataProviders own the data they pull pixels from. Others allow your application to maintain the image data. Altogether, these different data providers give you a lot of flexibility in locating, storing, and working with image data.

Quartz offers even more flexibility when it comes to the pixel data itself. The simplest way to store color samples is as ordered n-tuples. The number of values in each "tuple" is determined by the color space and whether or not the image contains an alpha channel. A grayscale image with no alpha would use a single value in each "tuple" (a single value for each pixel). An RGB image with an alpha channel would require four values for each pixel. Quartz gives you options in terms of the number and kinds of values that your image specifies for each component in a pixel. It also gives you a bit of wiggle room to select the ordering of the channels within a pixel (in particular the position of the alpha channel relative to the color components of each pixel).

All of this information comes together when your application creates a CGImage. More of this topic will be explored shortly. The true beauty of Quartz, however, is the fact that it encapsulates much of the underlying complexity of images behind a relatively simple API for drawing images. The next topic to cover is the way your application draw images with Quartz 2D, and afterward the question of how to create images will be revisited.

What about NSImage?

Cocoa contains a class known as NSImage. Although both CGImage and NSImage share the "image" moniker, the conceptual model of an NSImage really doesn't match that of a CGImage. NSImage, however, has some features beyond the functionality available through the CGImage interface. Fortunately, Cocoa developers can take advantage of both NSImage and CGImage within their applications.

To give you some idea of the differences, CGImage can only store one representation of an image, usually a compressed data stream or a pixel map. NSImage, in contrast, can store arbitrarily many representations of the same image. A single NSImage might contain the same graphic at a number of different resolutions or in a number of different color spaces. When you ask the computer to draw an NSImage, the system will decide which of the representations it will draw based on the characteristics of the destination device.

Another interesting difference is that your application can change the image stored in an NSImage by using the lockFocus method of an NSImage object and then drawing a new graphic. In contrast, once you've created a CGImage, the contents of that image are considered immutable and should not be changed.

Finally, an NSImage can also represent metafile contents; that is, an NSImage can be used to store and draw a PDF document. As we've mentioned, however, CGImage is strictly for working with pixel-based images.

For these and other reasons it would not be prudent to consider CGImage and NSImage to be equivalent classes. NSImage is a very rich class, and to do it justice would require quite a bit more space that we have here. We recommend that you look at the Cocoa documentation and Apple's sample code if you want to explore the fascinating behavior of the NSImage class.


Drawing CGImages

Quartz 2D includes a single routine call for drawing an image in a context, CGContextDrawImage. This routine accepts three arguments, the context you want to draw the image in, a rectangle within that context into which you want the image drawn, and the image that the computer should draw. Quartz 2D bolsters this relatively simple routine call with hallmark features like color management, resolution independence, and powerful transformations to provide a very feature-rich environment for drawing images.

Resolution Independence

In working with computer graphics, resolution of an image (measured in pixels per inch) can play an important role in how the computer handles the image. It may surprise you to learn, however, that Core Graphics doesn't concern itself much with the resolution of an image. In fact, the CGImage opaque data type doesn't even keep track of resolution as an attribute of an image.

All of the Quartz 2D drawing primitives are resolution independent entities. The computer takes these primitives and reproduces them at the full resolution of the output device. In working with images, Quartz 2D accomplishes this task using two techniques called resampling and interpolation.

Resampling is a process in which the computer takes an image, overlays a new grid of pixels on top of the image, and generates new samples (one for each pixel in the grid) based on the image colors of the underlying image. This process is illustrated in Figure 8.2.

Figure 8.2. The Resampling Operation

The left side of Figure 8.2 shows a high resolution image with a resampling grid laid down on top of it. This example is going to resample the image at a lower resolution. The image on the right is the result of resampling the image to that grid. The figure demonstrates the application of a very simple resampling algorithm. When trying to determine the color for a pixel in the resampled image, you apply the color of the source immediately underneath its center.

If you look at the left-hand image of Figure 8.2, however, you will see that some of the squares in the resampling grid actually cover more than one pixel in the source image. A more sophisticated algorithm would generate the color for the resampled pixel by combining these pixels.

The technique that the computer uses to generate the resampled pixel is called the interpolation algorithm. By changing the interpolation algorithm, the computer can improve the resulting image, but the higher quality often comes at the expense of increased processing time. Quartz 2D allows the program some measure of control over this trade-off by allowing it to specify a hint that helps the library decide which interpolation algorithm to choose.

Interpolation Quality

Interpolation, and the interpolation algorithms applied to a graphic, depend on the capabilities of the device that Quartz is drawing to. A graphics card rendering Quartz images in hardware might be able to employ one or two simple interpolation techniques. A bitmap graphics context where the rendering is handled by software on a general purpose processor might be able to use many different interpolation techniques.

Your application can indicate what level of interpolation quality it wants the computer to use by using the CGContextSetInterpolationQuality method of a CGContext. This routine allows you to provide a hint to the context about whether you would like to use a lower quality, faster interpolation technique, or a higher quality interpolation algorithm that takes longer to calculate.

Keep in mind, however, that interpolation and interpolation quality are very device-specific. When drawing to the screen, for example, your application might find that using a high quality interpolation setting provides acceptable performance. That same interpolation quality setting might be problematically slow when the same drawing code prints the graphics to a high-resolution printer.

Clipping with an ImageMasking

Apart from drawing images, Quartz also can add an image mask to the current clipping area of a context. If you need to clip your drawing to a complex shape, you may find it easier to create an image mask and clip your drawing to that.

You add the clipping mask to the context with the routine CGContextClipToMask. As with the other routines that add to the clipping area, the computer will intersect your mask with the clipping area already established in the context. After adding the mask, subsequent drawings will show through the translucent portions of the mask and be suppressed wherever the mask is opaque.


Creating CGImages

There are a number of services in the operating system that can return CGImages to your application. The most obvious source is Core Graphics, which offers a number of routines for creating CGImages from various data sources. However, in addition to Quartz, you can obtain CGImages from other operating system services. For example, QuickTime provides the routine GraphicsImportCreateCGImage, which can create a CGImage from a QuickTime Graphics importer.

The next few sections explore some of the Quartz 2D routines that your application can use to directly create images from data sources that are directly supported by Core Graphics itself. The next chapter examines some handy techniques your application can use to import images from external sources, like image files, and in other data formats, like TIFF images.

Given the breadth of color spaces that Quartz 2D supports, CGImage has to work with quite a variety of pixel types. The different characteristics that describe pixel formats are very important when you want to create an image or find out more about an image you have obtained elsewhere. Before talking about creating CGImage objects, take a look at the characteristics that describe the pixels they contain.

Pixel Format Information

There are two general categories of information that describe the pixels of a CGImage. The first category describes the color characteristics of the pixels. This information is related to the color space and alpha channel. The computer needs to know how many color components the application will use to represent each pixel, and it needs to know which of those components represent which channel. The second category describes the actual bit and byte-level characteristics of each pixel. This information describes how the pixels are arranged in memory. For example, a 32 bit-per-pixel ARGB Image will use one byte for each color channel, while a 16 bit-per-pixel ARGB image might use only five bits for each color channel. The computer needs to know all of this information to understand the pixels of an image completely.

Pixel Color Space

All of the color samples, and therefore the pixels, in a CGImage come from the same color space. Correspondingly, the color space of an image defines the number of components that go into each pixel. For example, if an image lives in an RGB color space, each pixel will have at least three color components. CMYK images, in contrast require four color components per pixel.

While Quartz 2D always includes an alpha channel for most of the colors it works with, one exception is in image data. Quartz 2D can create CGImages from image data that does not include an alpha channel. This means that the pixels in an RGB image need not have four components per pixel.

If you are creating an image from your own data, you will have to tell the computer what color space the pixels are coming from. You represent that color space using an instance of the CGColorSpace opaque data type. If your code already has a CGImage and wants to know how many color channels each pixel of the image requires, you can retrieve the CGColorSpace object CGImageGetColorSpace. You can then ask the color space for the number of components in each color sample using the CGColorSpaceGetNumberOfComponents method.

Pixel Alpha Channels

In addition to the color channel information, Mac OS X also supports alpha channel transparency. One option for your pixel data is that it will not contain any alpha information at all. In the case that the image data does not contain an alpha channel, the pixels may still contain an additional component that the computer needs to ignore.

If an image does contains alpha information, then each pixel requires an additional component that carries the alpha information. Quartz 2D will need to know two other important bits of information about how the alpha channel relates to the color channels. From a byte-ordering standpoint, the library will have to know where the alpha channel is found in relation to the color channels. CGImage supports pixels with the alpha channel as either the first or last component of each pixel. Quartz 2D will also need to know if the components in the color channels have already been multiplied by the alpha value or not.

Premultiplied Alpha

When compositing a pixel from an image with other graphics, the computer needs to multiply the values in the color components of each pixel by the alpha value for that pixel.

If your application has the option of creating image data as premultiplied values, then you may choose to do so as it will save the computer the step of premultiplying the image data before compositing it onto the destination.

If if it is more convenient for your application to work with non-premultiplied data, however, you will probably get better performance if you do not premultiply the data yourself before handing it off to Core Graphics.

The computer has a lot more information about the exact circumstances under which it will use your pixel data. For example, much of your image may be clipped or masked away. Premultiplying the masked pixels may be a waste of processing power. Moreover, the engineers at Apple have put a lot of time, experience, and expertise into optimizing the premultiplication algorithms. By relying on Core Graphics to handle this operation for you, you will enjoy the benefits of that hard work without having to duplicate it yourself.

Quartz 2D collects these two characteristics of the alpha channel in an enumeration called CGImageAlphaInfo. The names of the values in this enumeration are very descriptive. For example, kCGImageAlphaLast indicates that the pixel format contains an alpha channel and that it is the last component of each pixel. The value kCGImageAlphaNoneSkipFirst indicates that the image does not contain alpha information but that the computer should skip the first component of each pixel anyway.

QuickDraw's 32-bit pixel format is an excellent example of a pixel type where the pixels occupy enough space for an additional component, but that component does not maintain alpha channel information for the pixel. This pixel format is characterized by the kCGImageAlphaNoneSkipFirst enumeration value. In this pixel format, the computer encodes the three color channels in the three low order bytes. The upper byte, however, is generally ignored. If you are going to create a CGImage from a QuickDraw PixMap (perhaps via a graphics port), you will need to take this additional byte into account.

While this extra component is ignored by QuickDraw, in general, the QuickDraw implementation on Mac OS X does change the value of this byte. For compatibility reasons, when you draw into a QuickDraw port, the computer will change the value of this byte to 255 (which, when interpreted as an alpha value usually represents an opaque pixel).

Pixel Format Information

The other important characteristics of images is the way that the pixel channels are organized in memory. We've just seen a bit of this information in the discussion of the different alpha channel options where the CGImageAlphaInfo enumeration tells us whether or not the alpha channel is the first component in a pixel or the last. There are additional features of the pixels that help a CGImage extract color information from its pixel data.

The first attribute like this is a value that tells how many bits the pixel data uses to represent the value of each color component. For example, a popular way of encoding RGB data in 16 bits is to use five bits of data for each color channel. With five bits per channel and three channels, this RGB pixel format only uses 15 bits for color information. The last bit is ignored, so each pixel uses 16 bits.

To help the library identify situations like this, where bits might be ignored, the second piece of information Quartz 2D likes to know is the number of bits that go into a single pixel.

A third important aspect of how pixel data is organized is the number of bytes in a single row of the image (commonly called rowBytes). The computer will often organize image data so that the number of bytes in one row of the image is wider than its pixel data alone would suggest. This is often done for performance reasons. For example, the registers of the AltiVec programming unit work best with data that are aligned to 16-byte (128 bit) boundaries, that is, addresses whose value is an even multiple of 16. The AltiVec processor, therefore, will find it easiest to access the pixels in a row if each row starts on a 16-byte boundary. To help ensure that this is the case, the computer may pad images to 16 bytes in order to ensure that the AltiVec processor can access the rows as efficiently as possible. This is just one example of a reason the image data might be padded. The important point to realize is that an image may have a larger value for its rowBytes than the pixel layout would indicate alone.

Valid Pixel Types

Taking into account all the pixel formatting options we've just discussed, alpha information, bits per component, bytes per pixel, rowBytes, and the color space, you might imagine that Quartz has a lot of flexibility when working with different image types. The system does not support every combination of the different variables. In practice, this is rarely a problem, as CGImage does support creating images from popular image formats like those shown in Table 8.1.

Table 8.1. Some Popular Quartz 2D Pixel Formats

Color Space

Bytes per Pixel

Bits per Component

Alpha Information

Description

CMYK

32

8

kCGImageAlphaNone

The most popular CMYK format is probably 32 bit CMYK with one byte for each color channel.

RGB

32

8

Any

You often run into 32 bit-per-pixel RGB images. Typically three bytes will be color data. If the pixel format contains alpha, the remaining byte will contain the alpha information.

If you are working with QuickDraw, you will usually skip the first byte. If you are working with data from Cocoa, the alpha channel is often found in the last position.

RGB

16

5

kCGImageAlphaNone SkipFirst

The color components are usually the low order bits of the 16 pixels. The first bit is usually ignored.

RGB

128

32 bit floats

Any

Mac OS X 10.4 and later support 128 bit-per-pixel floating point pixel formats.

Grayscale

1

8

kCGImageAlphaNone

Single byte grayscale images are popular

When creating a CGContext that draws into an offscreen bitmap, we will find that many of the attributes used to describe images are also used when describing offscreen pixel buffers. In practice, Quartz 2D is much more flexible when accepting different attribute combinations for images than it is when creating bitmap contexts. For example, Quartz 2D can create a CGImage from 24 bit-per-pixel RGB pixel data, but it may not be able to create an offscreen context that generates 24 bit-per-pixel RGB pixel data.

Image masks do not contain any color information, of course, nor do they include alpha information. In spite of this, masks have very similar characteristics to images. The pixels in an image mask have a single component value that represents the mask value for that pixel. Most image masks use 1, 2, 4, or 8 bits per component. Usually, with an image mask, the number of bits per component and the number of bits per pixel will match. Image masks also have a rowBytes value, which enjoys the same oddities as the rowBytes field in images.

Image Data Sources

This chapter has covered the way that the application describes pixel information to the computer, but no time has been spent on where the pixel data comes from. To retrieve its pixel information, a CGImage works with a class known as a data provider. A data provider is little more than an object that encapsulates a bundle of bytes and returns those bytes on demand. Core Graphics represents data providers using the opaque data type CGDataProvider. Your application can create a data provider in a number of ways.

The simplest data provider is one that provides access to a memory buffer that your application owns. In the case of working with images, this memory buffer is more likely than not, a pixel buffer. However, when working with CGImages, that is not your only choice. Core Graphics also allows you to work with blocks of memory that contain compressed image data in JPEG or PNG format.

Regardless of what the block of memory contains, you create the data provider the same way using the routine CGDataProviderCreateWithData. This routine takes a pointer to the data buffer and an integer expressing the size of the memory block. The routine also takes a routine pointer. When the CGDataProvider is finished using the memory buffer, it will call the routine pointer, giving your application the opportunity to release that buffer. In addition to the routine pointer, CGDataProviderCreateWithData accepts a pointer value that the computer will pass to the routine. If your application does not want to use the routine pointer, you can simply pass NULL for both of these parameters.

Image files are another popular source of pixel data. If you want to create a data provider that provides CGImage with the contents of a file, you can use the routine CGDataProviderCreateWithURL. In most cases you will pass a local file URL to this routine, however you can also pass network URLs. If the operating system is able to resolve that URL and extract data from it, then it can create a CGImage from that network data. However, network access can be a complex process, subject to network outages and the like. Because Core Graphics doesn't have any opportunity to return network errors, there are better mechanisms in Mac OS X for retrieving remote data.

A third technique for creating a data provider allows your computer to return pixel data through callback routines. Your application can create a structure containing routine pointers, and the computer will call those routines in response to data requests sent to the CGDataProvider object. The Quartz API allows you to create two different types of callback data providers. One of the simplest kinds of data provider is one that sequentially reads a stream of data. A direct access data provider should only be used if your application can skip around in the data set and access the data randomly. If your application can skip around in the data stream and access the data randomly, then you can create a direct access data provider. The direct access and stream-based data providers have slightly different callback structures.

Working with Planar Pixel Data

CGDataProviderCreateWithData allows you to rather easily create an image from interleaved pixel data. Some algorigthms, however, are more efficient when working with planar pixel data. If your application needs to work with planar image data, you have a couple of choices. You employ some code to reinterleave the color planes of the image into a separate buffer and create a CGImage from the interleaved data. The vImage library that is part of the Mac OS X graphics system contains routines for interleaving some planar data formats. Another option is to incorporate a custom data provider into your application. Your custom data provider can integrate the color planes of the image into interleaved pixels as the computer calls for them.

Scaling Pixel DataThe Decode Array

While the pixel data as presented by the data provider should be immutable, you can ask the computer to scale the pixel values as it draws the image by adding a decode array to your image.

The decode array is simply represented as an array of floating point values. Although your application constructs the array as a single dimensional array, the computer interprets those values in pairs. The decode array for the image should contain the same number of value pairs as there are color channels (plus the alpha if the image data contains it) in the image.

Each pair of values in the decode array specifies a valid range for the pixel values of the corresponding color channel. For example, if your decode array contains the values (0.5, 0.8) for the red channel of an RGB image, then as the computer takes each pixel from the data provider, it will scale the red value for that particular pixel so that it lies between 0.5 and 0.8 in the representation of that image on screen.

Core Graphics also allows you to supply a decode array for image masks. Using a decode array with a mask allows you to scale the mask value just as you scale color channel values using the decode array in color images.

Your application can use the decode array to provide a number of image effects. For example, you can use the decode array to remove the alpha channel of an image by using (0, 0) as the value pair for the alpha channel value of the image. If you want to invert an image, you can pass (1,0) as the value pair for each channel. When the computer maps the color channels through the decode array, it will invert each color channel.

The decode array is an intrinsic part of the CGImage object. If you want to use a different decode array with your pixel data, you will have to create another image with the alternate decode array.

Creating an Image from a Pixel Buffer

Creating an image from a pixel buffer in memory is probably one of the most popular techniques. Quartz 2D supplies the routine CGImageCreate for this purpose. This routine takes quite a few parameters, but you should not let this put you off. The sample code snippet in Listing 8.1 illustrates a simple application of this routine. The code in this listing is used to explore the basic technique, and you can modify it to support your own particular pixel formats as needed.

Listing 8.1. Creating an Image from a Pixel Buffer in Memory

CGImageRef CreateImageFromARGB32Bitmap(
    unsigned int height,
    unsigned int width,
    void *baseAddr,
    unsigned int rowBytes)
{
    const size_t bitsPerComponent = 8;
    const size_t bitsPerPixel = 32;

    CGImageRef retVal = NULL;

    // Create a data provider. We pass in NULL for the info
    // and the release procedure pointer.
    CGDataProviderRef dataProvider =
            CGDataProviderCreateWithData(
                    NULL, baseAddr, rowBytes * height, NULL);

    // Get our hands on the generic RGB color space.
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateWithName(
            kCGColorSpaceGenericRGB);
    // Create an image
    retVal = CGImageCreate(
            width, height, bitsPerComponent, bitsPerPixel,
            rowBytes, rgbColorSpace, kCGImageAlphaPremultipliedFirst,
            dataProvider, NULL, true, kCGRenderingIntentDefault);

    // The data provider and color space now belong to the
    // image so we can release them.
    CGDataProviderRelease(dataProvider);
    CGColorSpaceRelease(rgbColorSpace);

    return retVal;
}

Our sample code creates an image based on a very specific pixel format. This routine creates an image from 32 bit-per-pixel (8 bits per component), premultiplied, ARGB data. Your own code may have to support other pixel formats, but the basic technique is the same.

Our routine accepts the height and width of the image as parameters. It also accepts the pointer to the image data and the number of bytes in each image row. As was said, in some cases the number of bytes in one row of an image may be more than the width of the image multiplied by the number of bytes in a pixel.

As with all images, we need to create a data provider to serve as the conduit between our data source (the pixel buffer) and the CGImage. The code sample uses CGDataProviderCreateWithData. This routine creates a simple data provider based on a pointer to a block of memory and the size of that block. In this case, we do not give the data provider any mechanism for releasing the data. This code would rely on some external agent to ensure that the pixel data exists until the image is released and that the computer frees the pixel data when it is no longer needed.

After creating the data provider, the code also creates a color space for your image. In this case, you know the image data to be RGB data and can assume that the color samples in the pixel data come from the generic RGB space on the operating system. If your image data comes from some other source, say a scanner, it would be much better to use the color space of that source rather than the generic space.

With the data provider and color space in hand, you can finally create an image based on the pixel data. CGImageCreate ties together all the different pieces and returns an instance of the CGImageRef opaque data type. Note that in this case we indicate our data is using premultiplied alpha values and that the alpha channel is the first component of each pixel. The code passes in NULL as the decode array, indicating the choice to use the color values from the pixel buffer directly without scaling them.

The last two parameters of the CGImageCreate call involve concepts that haven't been covered yet since they have more to do with drawing the image than creating it. The next to last parameter tells the operating system that the image can be drawn with interpolation. This will come into play if Quartz wants to draw the image with a transformation that requires it to resample the graphic and generate additional pixels.

The final parameter relates to the way the computer transforms the colors of the image when drawing the image to a device that uses a color space other than the one the application has created the image in. This parameter is called the rendering intent for the image. Color management documentation can offer more information about the different rendering intents. In this sample code the default value is requested.

After calling CGImageCreate, you have the return value for the routine; however, keeping in line with the ownership rules of CG objects, you need to release the auxiliary objects created within the routine. Release both the color space, and the data provider that you created. If the image itself has retained those objects, then these release calls will not free the objects, but you still must release your claim on them.

This routine returns the CGImage that it has created and expects the calling routine to release that image when it has finished with it.

Creating from a QuickDraw PixMap

If you are a QuickDraw programmer and are trying to transition your code to Quartz, you may find that creating a CGImage from a PixMap is a useful transition strategy. Creating an image from a QuickDraw PixMap is no different than creating an image from any other block of pixel memory. The PixMap will have to have a pixel format that is compatible with Quartz 2D. That means, for example, that you cannot use 8 bit-per-pixel indexed color spaces with Quartz 2D.

One aspect of PixMaps that seems to trip up QuickDraw code from older Macintosh applications is the rowBytes fields. Older versions of the Mac OS treated the high order bits of the rowBytes fields as flags. Application code would often mask out these high order bits using either 0x7FFF or 0x3FFF as the bit mask.

Later versions of the Mac OS, including Mac OS X, include routines that you should use to extract the base address and rowBytes of a PixMap. These routines are GetPixBaseAddr and GetPixRowBytes respectively. If your code uses the old masking behavior for the PixMap's rowBytes field, you should update the code to use GetPixRowBytes instead.

When you are using a PixMap as the source of an image on Mac OS X, you should probably ensure that the PixMap's pixels are locked for as long as the CGImage exists. You lock a PixMap using the LockPixels routine and unlock them with UnlockPixels. On Mac OS X, there is a very small, but non-zero chance, that a PixMap's pixels will move over the life time of the image. LockPixels and UnlockPixels are not expensive routines and calling them, potentially without cause, shouldn't be an egregious burden.

Listing 8.2 includes a sample routine that converts a 32-bit color QuickDraw PixMap into a Core Graphics image. This routine was taken from a code sample, called CGImageFromPixMap, that is included in the Chapter 8 code samples.

Listing 8.2. Creating a CGImage from a QuickDraw PixMap

CGImageRef CreateCGImageFrom32BitPixMap(PixMapHandle offscreenPixMap)
{

    // Extract information about the geometry of the offscreen
    Rect bounds;
    GetPixBounds(offscreenPixMap, &bounds);

    // Calculate the length of the pixel data and use that
    // to create the data provider for the image
    long rowBytes = GetPixRowBytes(offscreenPixMap);
    unsigned int dataLength = rowBytes * (bounds.bottom - bounds.top);
    // We pass the CGDataProvider an
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(
            offscreenPixMap,
            GetPixBaseAddr(offscreenPixMap),
            dataLength,
            NULL);

    // We're now ready to create the image.
    CGColorSpaceRef rgbColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGImageRef pixImage = CGImageCreate(
            bounds.right - bounds.left,
            bounds.bottom - bounds.top,
            8, 32, rowBytes, rgbColorSpace,
            kCGImageAlphaNoneSkipFirst, dataProvider,
            NULL, true, kCGRenderingIntentDefault);

    CGColorSpaceRelease(rgbColorSpace);
    rgbColorSpace = NULL;

    CGDataProviderRelease(dataProvider);
    dataProvider = NULL;

    return pixImage;
}

Creating from PNG and JPEG data

If your application has access to PNG or JPEG data, then Core Graphics includes the ability to work with that data directly. Creating a CGImage from compressed data in either of these formats is as easy as creating an appropriate data provider and calling Quartz to create an image from that provider.

Because your application can create a data provider from a number of sources, you have a lot of options for the source of the compressed data. For example, if you have JPEG data in a block of memory owned by your application, you can use the same call to CGDataProviderCreateWithData used in the previous example.

In this code sample, a CGImage is created from a PNG file included in the application's resources. Listing 8.3 demonstrates one potential implementation for this kind of routine.

Listing 8.3. Creating an Image from a PNG Resource

CGImageRef CreateImageFromPNGResource(CFStringRef resourceFileName)
{
    CGImageRef retVal = NULL;

    // Try to get a URL to appropriate URL in our resources
    CFURLRef resourceURL = CFBundleCopyResourceURL(
            CFBundleGetMainBundle(), resourceFileName,
            CFSTR("png"), NULL);

    // if we succesfully obtained the URL, create an image from it.
    if(NULL != resourceURL) {
            CGDataProviderRef dataProvider =
                    CGDataProviderCreateWithURL(resourceURL);

            retVal = CGImageCreateWithPNGDataProvider(
                           dataProvider, NULL, true,
                           kCGRenderingIntentDefault);

            CGDataProviderRelease(dataProvider);
            CFRelease(resourceURL);
    }

    return retVal;
}

Creating an image from PNG or JPEG data frees you from having to work with a lot of the details concerning the pixel format that are important when creating an image from a pixel buffer. These settings are taken from the JPEG or PNG data itself. This simplifies the sample code somewhat.

The code begins by asking the main bundle to return a URL to the PNG file in the Resources directory of the application package whose name matches the one passed in as a parameter to this routine. If the computer returns this URL successfully, you create a data provider from the URL and call CGImageCreateWithPNGDataProvider to create the actual CGImage instance. If this had been working in JPEG data instead, you could use the routine CGImageCreateWithJPEGDataProvider. As in the previous example, NULL is passed in for the decode array, true to allow the computer to use interpolation when drawing the image, and the default rendering intent is used for the image.

As in the previous code sample, the routine ends by releasing the auxiliary objects it has created and returns the CGImage it has created to the caller. The caller is responsible for releasing the CGImage after finishing with it.

Creating Image Masks

Creating image masks is very similar to creating images. The largest difference is that color masks do not require a color space and have a single component per pixel. The primary routine your application can use to create an image mask is CGImageMaskCreate. Aside from the fact that the color space parameter is missing, this routine accepts arguments that are identical to those passed to CGImageCreate.

Creating from Another Image

Another popular task is creating an image based on the contents of another image. This can helpful if, for example, you have cached a background for a view in an offscreen port and want to copy part of that image to the window as part of redrawing your view.

To perform such a task, you would like to take an existing image, the entire background, and create a new image with the same pixels as a rectangle in that larger image. Mac OS X 10.4, Tiger, provides a routine for just this purpose. On Tiger you can call CGImageCreateWithImageInRect passing in a source image and a rectangle. The computer extracts the portion of the image in the rectangle and creates a new image with those pixels as its contents.

Unfortunately, older versions of Mac OS X don't include this routine. However, a strategy that may be useful is to create an offscreen pixel map, draw the image into that map, and use a sub-rectangle of the pixel map to create a new image.

This technique suffers a bit in that it is not always possible to match the color settings of the original image exactly. For example, a JPEG image can be 24 bit-per-pixel RGB, but the operating system cannot create 24-bit RGB offscreen graphics contexts. However, your code may be able to use a pixel map with similar color characteristics (say 32-bit XRGB in this case) to create an image that very closely matches the color space of the original image.

If you do not need to create an independent CGImage, then you may be better off simply using context clipping or an image mask to select only that portion of the source image you are interested in drawing.

Another useful routine is CGImageCreateCopyWithColorSpace. This routine allows you to create a new image that is identical to the source image except that the color samples will be interpreted as having been taken from another color space. For this to work, the original image's color space and the color space passed to CGImageCreateCopyWithColorSpace must have the same number of components

It is important to understand that this routine will not correct the image to the color space you pass it. Instead the routine uses the exact same image data and just changes the environment used to interpret those pixel values.

If you need to color correct an image from one color space to another, you have several choices. If Quartz 2D can create a bitmap graphics context for your desired color space, then you can create an offscreen bitmap and use CGContextDrawImage to draw into that context. Quartz will color correct the source image to the destination color space as part of that drawing operation. This technique is demonstrated with sample code shortly when discussing image drawing performance.

If Quartz 2D cannot create an offscreen bitmap that uses the color space you want, then you will have to revert to using ColorSync (or some other color management system) to correct the image data. Unfortunately this kind of operation is beyond the scope of this book.

Combining an Image and a Mask

If you want to combine an image and a mask into a new image, you can do so with the routine CGImageCreateWithMask. This routine accepts two parameters.

The first is a CGImageRef that represents the source image you want to mask. Just about any image will do, but it may not already have a mask or masking color applied to it.

The second parameter, also a CGImageRef, can either be an image mask or an image. If you pass in an image, the computer will reinterpret the source samples of the image as the alpha channel values for the resulting image. For this reason, the image should contain a single channel per pixel.

Ideally the second parameter, the masking image, should match the size of the source image in the first parameter. If the second image does not match the size of the first, then the computer will scale it so that their sizes do match.


Image Performance Issues

In the resolution independent drawing universe of Quartz 2D, images are a bit like a racing duck out of water. Getting them to run quickly requires a bit of attention and training.

It's relatively easy to recognize some of the performance issues that working with images in the Quartz 2D environment will engender. For example, in addition to the geometrical transformations needed to take an image from user space to the device's coordinate system, images incur the added overhead of resampling and interpolating the pixels of the image on the destination device.

There are other performance impacts of images, however, that may not be as immediately obvious. Working with images also can involve working with large volumes of pixel data. Shuffling around all that data can also have a performance impact on your application. For example, if your image data is very large, drawing it may require the system to page in a lot of virtual memory. Going to the disk to page in memory is a very slow operation.

This section discusses a potpourri of issues that may affect your application when working with Quartz 2D. Our goal is not to solve every performance problem that could arise when working with Quartz 2D and images, however thinking about these issues may help you track down and resolve performance problems within your own application.

Pixel Data Is Immutable

It is important to note that regardless of the data provider that offers the pixel data, the image model presented to your application by CGImage requires the pixel data to be immutable for the life of the image. Once you have created a CGImage with its associated data provider, the data provided should be the same every time the computer asks for it.

The immutability of the pixels gives the operating system the freedom to optimize the way it draws your image. For example, if you ask the computer to draw the image repeatedly to the screen, Quartz 2D might hypothetically decide to cache a representation of that image in VRAM. The GPU on the graphics card could reproduce this image from VRAM anywhere on the display very quickly.

If the pixels of the image were not immutable, however, optimizations of this kind would be much more difficult.

It is important to realize that this immutability of the image is your application's responsibility and may not be enforced by the operating system. If you do create a bitmap context and a CGImage on the same memory buffer and try to draw the CGImage after changing the pixel data, the current version of the operating system may properly draw the modified image to some devices and not draw it properly on others. Moreover, your application might break in future versions of the operating system when Apple changes the behavior of the operating system.

Reuse Your CGImageRefs

The fact that you must create a new CGImage every time your image data changes is closely related to a performance guideline which recommends that you reuse the same CGImageRef to draw some image data if that data has not changed.

The reasons for reusing a CGImageRef are very similar to the reasons the operating system requires pixel data to be immutable. If you reuse an existing CGImageRef, the operating system has more opportunities to help you optimize the drawing of your images.

Continuing with the hypothetical optimization just mentioned, if the computer caches information about your image (like the VRAM cache mentioned in the previous section), that cached image information will be part of the opaque CGImage object. When you reuse that CGImage object, you give the operating system the opportunity to use that cached information to draw your image more quickly.

Use Resampling and Image Interpolation Correctly

When drawing images, interpolation can be a time-consuming operation. You should only use image interpolation when you need high quality rendering. For example, if your application is going to allow the user to drag an image around the screen, and you wish to provide feedback showing the image's position as it is dragged, you might consider drawing the drag feedback with a low-quality image interpolation setting to maximize the performance of the drag operation. Once the user has dropped the image, you could then redraw the image with a higher quality in its final position.

By the same token down-sampling an image takes some processing time. Consider an application that handles large image but draws thumbnails in a scrolling list. This application will get better performance if it calculates and caches the scaled down thumbnail images than if it tries to repeatedly scale and draw the full-size images.

Color Management Issues

Quartz 2D carefully incorporates color management as a fundamental feature of the graphics library. Both CGImages and CGContexts have color spaces associated with them. When you draw an image in a context, the color management system in Quartz carefully maps the colors from the image into the color space of the destination context.

This additional color matching step does not come for free. If you plan to draw an image to a particular color space repeatedly, you can improve drawing performance by skipping this color matching step. To do that, you can create a new image from pixel data that has already been corrected to the destination color space.

Speeding up this color management step is another instance where honoring the immutability of pixels and reusing CGImages can give you a performance boost. The computer might be able to cache intermediate results of color matching calculations to speed up subsequent renderings of a CGImage.

For many images, you might be able to use Quartz to create a new image in the destination color space. The following code sample illustrates one strategy for creating a color corrected version of an image. This sample converts an image to the color space of the main display. You might use this code to transform an image that you will draw in a window repeatedly.

Listing 8.4. Converting an Image to the Display's Color Space

CGColorSpaceRef CreateDisplayColorSpace()
{
    CGColorSpaceRef retVal = NULL;
    CMProfileRef systemMonitorProfile = NULL;

    // As the system for the default display profile.
    CMError getProfileErr = CMGetSystemProfile(&systemMonitorProfile);
    if(noErr == getProfileErr) {
            // Create a CGColorSpace from that profile
            retVal = CGColorSpaceCreateWithPlatformColorSpace(
                           systemMonitorProfile);

            // Be sure to close the profile
            CMCloseProfile(systemMonitorProfile);
    } else {
           // If we could not open a profile... we simply return the
           // generic RGB color space.
           retVal = CGColorSpaceCreateWithName(
                          kCGColorSpaceGenericRGB);
    }

    // This is a "Create" routine so the caller is responsible for
    // releasing the result.
    return retVal;
}
void ReleaseTempImageCallback(void *info, const void *data, size_t size)
{
    free(info);
}

CGImageRef CopyImageWithDisplayColorSpace(CGImageRef sourceImage)
{
    const UInt8 bytesPerPixel = 4;

    CGImageRef retVal = NULL;

    size_t width = CGImageGetWidth(sourceImage);
    size_t height = CGImageGetHeight(sourceImage);

    CGColorSpaceRef displayColorSpace = CreateDisplayColorSpace();

    // Allocate a block of memory large enough to hold the
    // color transformed image.
    // We assume the display is an ARGB color space.

    // Calculate rowbytes to be a multiple of 16 bytes
    size_t rowBytes = ((bytesPerPixel * width) + 15) & ~15;
    void *imageBuffer = malloc(rowBytes * height);

    // We create a bitmap graphics context that draws into our block of
    // memory and matches any drawing to the context to the display
    // color space.
    CGContextRef offscreenContext =
            CGBitmapContextCreate(
                   imageBuffer, width, height, 8,
                   rowBytes, displayColorSpace,
                   kCGImageAlphaPremultipliedFirst);

    // Now we can draw the source image into the offscreen
    // context. This allows the computer to color correct the
    // image to the display color space
    CGContextDrawImage(offscreenContext,
           CGRectMake(0, 0, width, height), sourceImage);
    // We're done with the bitmap context.
    CGContextRelease(offscreenContext);
    offscreenContext = NULL;

    // Now we create another image using our color corrected image data.
    CGDataProviderRef imageDataProvider =
           CGDataProviderCreateWithData( imageBuffer, imageBuffer,
                   rowBytes * height, ReleaseTempImageCallback);

    retVal = CGImageCreate(
                   width, height, 8, 32, rowBytes,
                   displayColorSpace, kCGImageAlphaPremultipliedFirst,
                   imageDataProvider, NULL, false,
                   kCGRenderingIntentDefault);

    CGDataProviderRelease(imageDataProvider);
    imageDataProvider = NULL;

    return retVal;
}

Listing 8.4 calls into service a number of Quartz topics not yet covered. In particular, it creates an offscreen graphics context and a CGImage. Creating an image was discussed earlier in this chapter, and creating offscreen contexts is covered in the chapter on offscreen drawing.

The first routine CreateDisplayColorSpace uses a ColorSync routine to retrieve the color profile of the main display. It then creates a color space based on that profile and returns it to the caller. In the unlikely event that the computer cannot retrieve the system profile, then the code uses the generic RGB color space as the destination color space.

The second routine ReleaseTempImageCallback is used by the CGDataProvider used when the image was created. Data providers are discussed later in this chapter. This routine allows the code to release the image data of the new image when the application is done with it.

Much of the work in this code sample is handled by the CopyImageWithDisplayColorSpace. This routine creates a new image based on source image data but color corrected to the display color space. The routine does make the simplifying assumption that the display color space will be an RGB color space. It also assumes the image might have an alpha channel that the code should preserve.

The code begins by extracting attributes of the source image. From those attributes it allocates an image buffer large enough to hold a copy of the image but repurposed as a 32 bit-per-pixel ARGB image. The code creates the context with the display's color space. The code then draws the image into the offscreen context. This drawing operation induces Quartz to correct the source image into the color space of the context, the display color space. Finally, the code takes the now corrected image data and constructs a new CGImage from it.

The end result of all this work is a CGImage that does not have to be color corrected (again) when drawn to the display. This Quartz 2D-based technique is certainly not the only way to change the color space of the image. You could also use ColorSync to correct your image data prior to creating the original CGImage.

Compositing Takes Time

Every time the computer has to draw an alpha composited pixel, it must read the value of the pixel in the destination context, multiply its value by the color of the pixel being composited, and write the results back into the destination. This read-modify-write cycle can take some time. Contrast this with the instance where the pixel being drawn is opaque. The computer can skip the read and modify steps and simply write the opaque pixel to the destination. Needless to say, this can be a significant performance boost and means that, in some cases, you may be able to boost the performance of your application by correctly setting the alpha channel information in your images to exclude alpha if the image has none.

Images and VRAM

As Mac OS X evolves, the video memory on the graphics card is rapidly becoming a premium resource. When you draw an image to the display the computer will have to transfer some fraction of the image data across the graphics bus to the video card. This ties the performance of the graphics to the speed of the VRAM bus.

For some graphics, the computer may be able to send the drawing commands that create the graphics to the video card faster than it can pass over an image generated by those drawing commands. For example, suppose your application needs to fill a large area of a window with a blue field and place a small icon in the middle of it. If you draw this graphic from a cached CGImage, the computer might have to transfer all the pixels of the image across the bus. If, however, you can draw the blue field using a line art primitive, the computer can issue the drawing commands for the background using a few bytes, and the graphics processor on the video card can quickly fill the rectangle in the frame buffer. Drawing the icon might still require sending data across the bus, but certainly it will not require as much work as transferring the larger image.

Moreover, as the graphics system evolves to rely more heavily on the graphics card memory space in VRAM, it will quickly become crowded. If you are drawing several images, you will be driving the graphics resources of other applications out of VRAM which can have an adverse affect on the behavior of the system as a whole.

These facts should not be taken as a general injunction against drawing images in windows. However, there may be instances in your application where you have used an image in the past, but replacing that code with similar code that does not use images might improve performance.

Most often, this kind of thing comes into play in applications written to pixel-based libraries like QuickDraw. Those applications would often cache graphics in offscreen bitmaps as an optimization technique. The application would then transfer those graphics to the display because the pixel copy operation (from main memory to main memory) was a cheaper operation than reissuing the drawing commands.

If your graphics are relatively simple, say the graphic picture of the scale on a ruler, then you may actually enjoy enhanced performance by removing the "optimization" of caching the graphics in an offscreen bitmap. The computer may be able to draw the scale more quickly using line art primitives given that those primitives cause fewer bytes to be shipped across the video bus and much of the rendering can be handled asynchronously by the GPU.

Keep It Simple, Then Test It

Perhaps the most important point to make about images and Quartz 2D is that the library does not treat images the same way that traditional graphics libraries do. Moreover, advances in the graphics architecture and other systems on Mac OS X gives the system a very different profile than other graphics environments. For example, the sheer volume of pixel data with many images can lead to problems with the virtual memory system. As a result, some of the techniques you would use to optimize drawing in other graphics libraries can have an adverse effect on the performance of Quartz 2D.

When in doubt, your best bet is to keep your drawing code as simple as possible. When you keep it simple, you make it easier for the operating system to work on your behalf and optimize the graphics pipeline. If you do this and still find your graphics has disappointing performance, carefully apply profiling tools, among others, to determine where your bottlenecks are.

If for no other reason, images are an important part of the Quartz 2D imaging model because they represent the best way to exert pixel-level control over your graphics. For developers making the transition from pixel-based graphics libraries, images may be the key to duplicating functionality that Quartz 2D cannot duplicate in other ways. As you work with images, however, keep in mind that they consume a lot of memory and are inherently resolution dependent. These facts have an impact on both the performance of programs that use images and the quality of the output they generate.


Chapter 9. Importing and Exporting Images

Computers are riddled with image files in popular formats such as JPEG, PNG, GIF, TIFF, and many others. In addition there are a number of powerful tools available on Mac OS X that graphic artists can use to create image resources for an application. Fortunately, Mac OS X makes it remarkably easy to import many kinds of image files into CGImages that you can draw in your application.

In a similar fashion, it is nice to be able to take the graphics you have created in your application and export them to image files for use by others. The operating system also includes technologies that let you take the images your application creates and export them into graphics files on disk.

This chapter looks at two of the technologies, QuickTime and Image I/O, that Mac OS X offers for importing and exporting images and discusses some of the features that these libraries offer. Some basic code samples are offered that demonstrate the fundamentals of using these technologies to read and write image files.

QuickTime is well known as a technology for recording and presenting multiple media types including video and sound. QuickTime also includes components for reading and writing still image files in a number of formats. Using a few lines of QuickTime code in your application will make it able to read and write image files in several different formats.

Image I/O is a technology Apple introduced in Mac OS X 10.4 Tiger. The Image I/O API provides a mechanism for importing and exporting images from a broad variety of image file types. The library supports many formats including open-source-based floating point image file formats (a.k.a. High Dynamic Range images) like OpenEXR and LogLUV TIFF images. Image I/O includes the ability to import individual images from files in formats that support storing more than one picture per file. The native compressors and decompressors in Image I/O are highly optimized and allow for the progressive loading of image data should the application desire that. The library also includes routines for working with and creating image thumbnails and can help your application read and write image meta data.

Image I/O understands how to make use of the QuickTime importers and exporters as a fallback mechanism. This means that if Image I/O is asked to read or write a file it does not understand natively, it can make use of any QuickTime components that can work with that format. In this way, applications that use the Image I/O API capitalize on the benefits of both libraries.

Image I/O is the preferred mechanism for importing and exporting images on versions of the operating system starting with Mac OS X 10.4 Tiger. This technology is used by all the system services on that operating system that work with images. If your application needs to support older versions of the operating system, you can fall back to using QuickTime, but the additional functionality found in Image I/O will add value when the library is available.


Using QuickTime

QuickTime can be a very imposing API. Often it can be difficult to sift through the many services that QuickTime provides to find the functionality you are interested in. Fortunately, understanding how to import and export images with QuickTime only requires a small number of easily understood routines. This is the functionality in QuickTime that this chapter focuses on.

Import Image Example

QuickTime is a component-based software system. The power of QuickTime comes from having a large number of software components that understand a variety of media formats. To import images, your application must select a graphics import component from the set of components that make up QuickTime and then ask that component to read the image data for you.

An interesting advantage of the QuickTime system is the fact that enterprising software developers can create their own graphics components, including image importers, and extend the system with them. Because all graphics import components can be treated uniformly, if your application understands how to use read images with QuickTime, it can take advantage of all the supported image file types that QuickTime understands.

To use a component, your application must first indicate to the QuickTime library where the image data resides. To do that, the application must first create an entity called a DataRef. A DataRef is little more than a block of memory, a handle, that describes the location of the image data. The type of information contained in the DataRef depends on where the image data lives. If the computer should read the data from a file, the DataRef will contain some kind of file reference (an FSRef, an FSSpec, a URL, or something else). If the data is to be taken from memory, then the DataRef will contain the raw image data itself. The QuickTime API includes a number of routines that help you create DataRefs from popular sources. This example takes the image data from a file and will use the routine QTNewDataReferenceFromFSRef.

A DataRef is usually associated with a type code that indicates what kind of information it contains. The type of the data ref is a 32-bit integer (a four character code) commonly called an OSType. For example, if the DataRef contains an alias handle that points to a file, the type code associated with the DataRef will be 'alis'.

After creating the DataRef, your application must select an appropriate graphics import component for the image data. If you know the data format of your image, you can ask QuickTime to create an import component that understands that format. For example, if you know that your image data is in PNG format, you might use the OpenADefaultComponent routine to create a PNG importer:

OpenADefaultComponent( GraphicsImporterComponentType,
    kQTFileTypePNG, importer);

Most image file formats have distinguishing features that make it easy for the computer to examine the data and determine if it is in a particular format. The most convenient way to select a graphics importer from QuickTime is to use the routine GetGraphicsImporterForDataRef. When you call this routine, the computer traverses the list of all registered graphics importers and asks each to try and determine whether or not it can decode the image data. If it finds an importer that claims to be able to read the data, it will return that component to your application.

In some cases, the computer may not be able to determine what kind of importer to use based on the image data itself. This can be particularly problematic when the image data resides in memory. In this case, you can actually tag the DataRef with information that can help the system decide which importer to use. For more information, read Technical Note 1195 : Tagging Handle and Pointer Data References in QuickTime at the URL:

http://developer.apple.com/technotes/tn/tn1195.html

After you have selected an appropriate component, getting a CGImage out of that component is as easy as calling GraphicsImportCreateCGImage. This routine simply asks the QuickTime graphics importer to take whatever steps are necessary to load the image and create a CGImage from it.

All of these steps are summarized in code Listing 9.1. This routine is taken from the Chapter 9 sample code project called QuickTime Import. The application is very simple. It allows you to open any image that QuickTime has an importer for and displays that image in a window. The application contains some code for setting up the application and managing the open dialog. That code will not be repeated here. Listing 9.1 simply provides the code that imports image files into a CGImage.

Listing 9.1. Importing a CGImage Using QuickTime

void OpenImageFile(FSRef &whichFile)
{

    Handle fileDataRef = NULL;
    OSType dataRefType = 0;
    ComponentInstance graphicsImporter = NULL;
    CGImageRef image = NULL;

    // Create a DataRef from the file reference that was passed in.
    QTNewDataReferenceFromFSRef(&whichFile, 0L,
            &fileDataRef, &dataRefType);

    if(fileDataRef) {

            // Find an appropriate graphics importer for the
            // given data ref.
           GetGraphicsImporterForDataRef(fileDataRef,
                   dataRefType, &graphicsImporter);
    }

    // If the system found an image importer for us to use then
    // try to extract an image from it.
    if(graphicsImporter) {
           GraphicsImportCreateCGImage(graphicsImporter, &image, 0);

           // we're done with the component so close it out
           CloseComponent(graphicsImporter);

           // We're done with the DataRef too
           DisposeHandle(fileDataRef);
    }

    // if we got an image, then create a new window and put
    // the image into it.
    if(image) {
            CreateImageWindow(image);
            CGImageRelease(image);
            image = NULL;
    }
}

The sample project prompts the user to select an image file and then opens a window that displays the image found in the file. After selecting the file, the computer translates the user's selection into an FSRef, which is passed to the OpenImageFile routine.

Listing 9.1 uses the routine QTNewDataReferenceFromFSRef to create a DataRef from the FSSpec that is passed to the routine. It gets back both the DataRef and the OSType that describes the data it contains. If that routine returns successfully, the computer then tries to create a graphics import component by examining the data from the file using the routine GetGraphicsImporterForDataRef.

If the computer finds a graphics importer, it then calls the routine GraphicsImportCreateCGImage. After creating the image, the code closes the importer and disposes of the DataRef. The code takes the resulting image and passes it to a routine, CreateImageWindow, that opens a new window to display the image.

Export Image Example

Exporting an image from QuickTime is just as easy as importing one. The basic technique is to create a graphic exporter for the type of file you would like to export your image into. You provide the graphic exporter with a DataRef that describes where you would like the computer to put the compressed data. You also provide the CGImage that you would like to export. Once everything is set up, exporting the data is as easy as calling GraphicsExportDoExport.

If you follow the steps listed, you will get a graphics export with the default settings of the graphics exporter. If you want to customize the export settings, QuickTime provides a number of routines that begin with GraphicsExportSetSettings to assist you. The easiest way to set up the settings, however, is to use the routine GraphicsExportRequestSettings to put up a dialog that will modify the settings of the exporter.

The technique of exporting a CGImage into a PNG file is demonstrated by the QuickTime Export sample code. The routine that exports the image through QuickTime is show in listing 9.2:

Listing 9.2. Exporting an Image via QuickTime

void ExportPNGImage(CGImageRef image)
{

    Handle      dataRef;
    OSType      dataRefType;

    GraphicsExportComponent       exporter;
    unsigned long                 exportSize;

    // Open a graphics exporter for PNG files and point it at our image.
    OpenADefaultComponent(GraphicsExporterComponentType,
           kQTFileTypePNG, &exporter);
    GraphicsExportSetInputCGImage(exporter, image);

    // Then request the exporter's settings from the user
    GraphicsExportRequestSettings(exporter, NULL, NULL);

    // We're all set up to save. Ask the user where we
    // should save the file
    CFURLRef saveLocation = CopySaveLocation();
    if(NULL != saveLocation) {
            // Create a data ref to our destination.
            QTNewDataReferenceFromCFURL(saveLocation, 0,
                    &dataRef, &dataRefType);
            CFRelease(saveLocation);
            saveLocation = NULL;

            // Set the destination in the exporter and export the image
            GraphicsExportSetOutputDataReference(exporter,
                    dataRef, dataRefType);
            GraphicsExportDoExport(exporter, &exportSize);

            DisposeHandle(dataRef);
            dataRef = NULL;
    }

    CloseComponent(exporter);
    exporter = NULL;
}

This code sample should look familiar at this point. The code begins by opening a QuickTime component. In this case, you are explicitly asking for a graphics exporter component capable of writing PNG files. After you have the component, you tell the exporter that we will be exporting the CGImage.

Next, call GraphicsExportSetInputCGImage to ask QuickTime to put up a settings dialog for the PNG exporter so the user can change the exporter's settings.

The routine CopySaveLocation puts up a save file dialog and returns the URL of the location the user has chosen for saving the document. You use this URL to create a destination DataRef for QuickTime. After telling the export component where it should save the image, you call GraphicsExportDoExport to save the new file. After that the code simply cleans up and returns.


Image I/O

Image I/O is part of Core Graphics and follows the Core Graphics API conventions. Strictly speaking, it is not part of Quartz 2D, but it does integrate closely with Quartz 2D and is an invaluable asset. The two primary opaque data types that make up the Image I/O system are CGImageSource and CGImageDestination. The CGImageSource class represents an abstract source for image data. Conceptually, the image source contains one or more images. An image file, a network stream, or a block of memory are all valid image sources. Each CGImageSource knows how to extract images from a particular source. After creating a CGImageSource, you can ask it to return any of those images as a CGImageRef.

Not surprisingly, the CGImageDestination represents an object that knows how to take one or more CGImages, encode them in a particular format, and store the results, or even transfer them elsewhere. After creating an image destination attached to a storage device, your application can pass it one or more images and then ask the destination to encode and save the graphics.

The image source and image destination classes don't concern themselves with the details of interacting with the file system or the network. Instead the CGImageSource opaque data type uses the familiar CGDataProvider opaque data type that we explored when creating images, and CGImageDestination uses its companion, the CGDataConsumer opaque data type. These objects offer your application a lot of flexibility in deciding where the image data is coming from. It can read from the network or from a local file, or it can generate the image data on-the-fly if that makes sense.

Import Image Example

The first sample will demonstrate using a CGImageSource. This code performs the same task as the QuickTime example earlier in the chapter. It is included with the sample code as part of an application called ImageIOImport. Listing 9.3 recreates the image import routine from listing 9.1 using a CGImageSource instead of a QuickTime import component:

Listing 9.3. Importing an Image with a CGImageSource

void OpenImageFile(FSRef &whichFile)
{

    CFURLRef fileURL = CFURLCreateFromFSRef(NULL, &whichFile);
    // Create a data provider for the image source
    CGDataProviderRef dataProvider =
            CGDataProviderCreateWithURL(fileURL);
    CFRelease(fileURL);
    fileURL = NULL;

    // Create the image source itself
    CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider(
            dataProvider, NULL);
    CGDataProviderRelease(dataProvider);
    dataProvider = NULL;

    // extract the first image (0 based index) from the image source
    CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource,
            0, 0);
    CFRelease(imageSource);
    imageSource = NULL;

    // if we got an image, then create a new window and put
    // the image into it.
    if(image) {
            CreateImageWindow(image);
            CGImageRelease(image);
            image = NULL;
    }
}

This code sample demonstrates the basic technique of using an image source. It first creates a data provider that points to the image data we want to import. It then creates a data source from that data provider. With the data source in hand, the code simply asks for the first image in that source (the images are indexed starting at 0). When the image has been imported, the computer creates a window to display the results.

This code sample ignores a simplifying utility provided by the Image I/O API. The utility routine, CGImageSourceCreateWithURL, will create a data provider and an image source in one step. We avoided this routine for the code sample to make it clear that you can use any data provider you like.

Image I/O includes additional functionality related to the loading of images. Of particular interest may be the fact that it can retrieve image thumbnails from file formats that provide them, and it supports the incremental loading of large images. Those topics will not be covered here, but you can read more about them in Apple's documentation.

Export Image Example

It's just as easy to export an image using Image I/O as it is to read one in. By now the process should be pretty obvious. The duality of creating a CGDataProvider for an image import operation is creating a CGDataConsumer for the results of compressing an image. The CGDataConsumer is responsible for taking the compressed image data and doing something with it, be it storing the data on a disk or writing it into a block of memory. Once you've decided what to do with the image data, you create a CGImageDestination that connects to the data consumer. You can pass the images you want to store in the destination to that object, and the system will handle compressing the image and shipping it off to the data consumer. Listing 9.4 demonstrates the technique of writing a CGImage into a file using a CGImageDestination.

Listing 9.4. Exporting an Image Using Image I/O

void ExportPNGImage(CGImageRef image)
{

    // We're all set up to save. Ask the user where we should save the
file
    CFURLRef saveLocation = CopySaveLocation();
    if(NULL != saveLocation) {
            // Create a data consumer that saves into the file the user
            // selected
            CGDataConsumerRef dataConsumer =
                    CGDataConsumerCreateWithURL(saveLocation);

            // Create an image destination attached to the data consumer
            CGImageDestinationRef imageDestination =
                    CGImageDestinationCreateWithDataConsumer(
                            dataConsumer, kUTTypePNG, 1, NULL);
            CGDataConsumerRelease(dataConsumer);
            // Add an image to the destination
            CGImageDestinationAddImage(imageDestination, image, NULL);

            // Finish the export
            CGImageDestinationFinalize(imageDestination);
            CFRelease(imageDestination);
            imageDestination = NULL;
    }
}

One interesting point to note about this code sample and Image I/O is that you specify the type of file you want to export using a Uniform Type Identifier (UTI). In this case the UTI we use is kUTTypePng. Uniform Type Identifiers are a mechanism for specifying the type of various and sundry things that have distinguishable types. That's a bit of a vague description for an odd concept. In this case there are a number of different types of files you might export to using Image I/O. You use image file type UTIs to tell Image I/O what type of file you want to use. For more information on UTI's, you can read Apple's documentation on the subject at

<http://developer.apple.com/documentation/Carbon/Conceptual/understanding_utis/understand_utis_intro/chapter_1_section_1.html>

Note that after adding the image, the code calls CGImageDestinationFinalize. If you do not call this method, the computer may not finish writing the data to the output properly. Also once you have called this routine, you will be unable to add more files to the CGImageDestination.


Chapter 10. Drawing with Core Image

Core Image is one of the most exciting technologies that Apple has added to its Mac OS X 10.4 Tiger operating system. The Core Image library ties together a number of graphics technologies on the platform and makes them available to your application in a single, easy to use interface. In its initial incarnation, Core Image is a toolkit for creating image effects and filters that modify images very quickly. The library draws its performance from the fact that it takes advantage of all the special hardware that it can. If you have a processor with a vector processing unit, Core Image will make use of it applying effects. If you have a programmable graphics card, the library can use that as well. In fact, with high-speed hardware, Core Image can apply effects to full-motion video in real time as it plays. What's more, if the drawing effects are applied by a programmable graphics card, the entire process will require surprisingly little CPU time. Core Image is the epitome of the modern graphics APIs on Mac OS X. It provides incredible power that is easy to access.

Like QuickTime, Core Image has a modular architecture. The set of filters in Core Image is extensible. The library itself ships with dozens of image filters and transitions. Developers can create additional filters, called Image Units, which can extend the set of filters if the default set is not sufficient.

Core Image is a sufficiently complex technology that it would take an entire book to explore thoroughly. Unfortunately this book will not be able to cover some of the intricate details of creating custom Core Image Filters. The purpose here is to explore the basic features of Core Image and describe the capabilities of the framework. You will see how an application can set up a series of Core Image Filters to create an image effect and then ask the computer to apply that effect when drawing an image.

Additional Image Processing Support

Core Image is not the only image processing library in Mac OS X. For example, QuickTime contains a number of effects and transitions that are, in many ways, simliar to those found in Core Image. It is quite likely that Apple will change the implementation of QuickTime effects to use Core Image filters in the future. QuickTime effects and transitions work well in the context of creating a movie, but the API can be a bit cumbersome when applying effects to images in a simple, still-image drawing context.

Mac OS X also contains the Accelerate Framework. This umbrella framework contains sub-frameworks that you might apply to image processing. One of the sub-frameworks, vImage, is specifically geared toward image processing. The routines in the Accelerate Framework are particularly well suited to image processing used to extract analytical information from an image. For example, vImage can generate the histogram of an image. The routines in the signal processing framework (vDSP) might be handy if your application wants to perform a Fast Fourier Transform on an image for processing in the frequency domain.

From a less programmer-oriented perspective, there are some high-level tools for image processing on the system. The ColorSync Utility application in /Application/ Utilities can open image files and apply image filters and color space transformations to an image file. From the command line there is a service called sips (the Scriptable Image Processing System) that will allow you to retrieve information from image files and manipulate their contents (issue the command sips --help from a terminal window). This same functionality is exposed to AppleScript scripts through the Image Events scriptable application.

With such a long and rich history in computer graphics, Mac OS X the system contains a lot of useful functionality. Exploring some of the corners of the system to see what you can find is not only fun, but it might also save you a lot of time and trouble. Spend some time exploring these other image processing libraries, and you may like what you find.


What Is Core Image?

Core Image is a collection of Objectve-C classes for managing and applying image filters. Although it has an Objective-C API, it is important to realize that Core Image is not a part of Cocoa! Carbon applications are free to use Core Image in their drawings as well. The first code sample in this chapter has been developed as a Carbon application that uses Core Image.

The classes of Core Image let you chain multiple filters together into a single effect and can optimize the application of those filters. In fact, on a moderate system, Core Image is fast enough that the computer can apply an effect chain to full-frame motion video as it plays. The filters are very accurate. The computer carries out the pixel calculations in floating point arithmetic. This avoids the component clamping, which might occur with lower precision pixel formats and allows Core Image to maintain the color fidelity of the image throughout processing.

The power of Core Image comes from its filters. Filters can be written to execute code on the both the system's main CPU and the graphics card's GPU if an appropriate programmable GPU is available. When the computer filters an image, the computer executes the code of the filter on all the pixels in the image. Given that even small images contain thousands of pixels, the filter's code must run very quickly.

The advantage of using the GPU for filter calculations is twofold. First, the parallel pixel channels of the GPU allow the video card to apply the filter to many pixels of the image at once. By processing the image on the GPU, the computer also frees up the CPU for other tasks. In the event that your computer is not equipped with a programmable video card, however, Core Image can translate the code for the GPU into instructions that can run on general purpose vector processing units, like AltiVec or SSE, or even on the CPU if no vector units are available.

The technology that enables this flexibility is embodied in a concept known as the Core Image kernel. The Kernel of a filter is a bit of code, written in a special language know as the Core Image Kernel Language. This language is inspired by another language, the OpenGL Shading Language, that the OpenGL community designed specifically for programming the GPUs of modern video cards. The additional features of the Core Image Kernel Language allow the computer to repurpose the code for a variety of combinations of GPUs and CPUs. This allows the same filter kernel to run on a variety of video cards from different manufacturers. Similarly, Core Image itself frees your application from the tedious details of interacting with OpenGL, and the video card to get the kernel programs downloaded to the GPU.

These filters in Core Image are grouped together into categories. Some of the categories found in the built-in set of filters include

  • Blend Mode Filters These are filters that create a new image by combining two or more images with particular pixel blending operation. These filters include effects found in popular image processing applications like color dodge and color burn.

  • Color Adjustment Filters Filters that alter the color characteristics of an image as a whole. This includes photo processing effects like hue shifting and exposure adjustments, as well as color environment adjustments like changing the white point or gamma curve of an image.

  • Color Effect Filters Filters in this category operate on the colors of individual pixels. A common color effect filter will invert the colors of the pixels in an image. Another popular color effect reproduces an image as monotone images, including sepia tones.

  • Compositing Filters These filters allow you to composite two or more images together using a variety of compositing operators.

  • Distortion Effect Filters Distortion filters modify an image in interesting ways. Applications can use distortion filters to apply special effects like fisheye lens effects and wavy ripple effects.

  • Focus Filters The focus filter category derives its name from the idea of focusing an image through a camera. This category of effects include Gaussian blur filters and motion blur effects.

  • Generator Filters Generator filters are unique in that they are actually create images instead of modifying existing ones. For example, the Lenticular Halo generator creates an image that looks like the halo caused by bright light striking the lens of a camera. The more mundane checkerboard filter generates an image with a two-tone checkerboard pattern on it.

  • Geometry Adjustment Filters These filters adjust the geometry of an image. We've seen how Core Graphics can apply an affine transformation to an image, and indeed there is a Core Image filter that applies an affine transformation to an image. Another popular geometry filter is one that crops an image. The Perspective Transform effect allows you to go beyond the limits of what an affine transformation can accomplish.

  • Gradient Filters This category includes filters that generate smooth blends between one color and another. The effects of these filters include the same kinds of effects you can generate using CGShading objects, but there are filters that can create images beyond those that come from CGShadings.

  • Halftone Effects Filters Halftoning is the technique of using small dots (or other shapes) to reproduce continuous tone images. Perhaps the most common example of halftoning are those used to print pictures in newspapers. The filters in this category reproduce similar effects on the computer screen.

  • Stylize Filters The effect of stylize filters are all interesting and distinct. These filters allow your application to apply a number of artistic effects to an image.

  • Tile Effect Filters These filters take an image and treat it as a single tile and then generate another image by repeating the tile in various ways. In some ways tile effects are very similar to Quartz 2D patterns.

  • Transition Filters Transition filters generate a sequence of images that are designed to be run as an animation. The idea is that you are moving from one image to another over some length of time and want a smooth way of drawing a transition from one image to the other. Common transition effects include cross fades, and wipes.

Obviously there is quite a lot of functionality contained in the basic filters that ship with the system. The full breadth of built-in filters is thoroughly covered by the Core Image documentation. Rather than discuss the particular details of individual filters, our efforts here are concentrated on understanding how to create and manipulate the parameters of a filter. Once you understand the basics, you should be able to use any Core Image filter you like.

The Development Tools in Tiger contain two applications you may find helpful when exploring Core Image. The first is called Core Image Fun House (/Developer/Applications/Graphics Tools/Core Image Fun House). This application allows you to pass an image through a chain of the Core Image filters and view the results. It is an excellent tool for exploring the parameters of filters and examining their effects. It is also encouraging to see the power functionality a program can call into play through a simple application of the Core Image API.

The development tools also contain an application called Quartz Composer. Quartz Composer is is a powerful tool for putting together Core Image filters and other graphics components in complex ways. Quartz Composer not only lets you see the results of the graphics processing pipelines, it generate files that your application can load when it runs. Those files can reconstruct the image processing pipeline you've created in Quartz Composer and play it back, potentially with different parameters, inside your application. For more information please consult the Quartz Composer documentation:

http://developer.apple.com/documentation/GraphicsImaging/Conceptual/QuartzComposer/qc_intro/chapter_1_section_1.html


How Core Image Manipulates Images

For all its power, Core Image is remarkably easy to use. There are a few basic concepts you have to understand, and some classes your application must manage, but once you have a handle on those you can harness a lot of power quickly.

The focus of Core Image is, of course, pixel-based images. The graphics in Core Image are managed through the aptly named CIImage class. Although the name of this class is very similar to CGImage, the two represent very different concepts. Unlike a CGImage, a CIImage does not represent a collection of pixel data! It represents a series of instructions about how to draw an image. The class may have more appropriately been called an image program.

When the computer draws a CIImage, it actually executes the drawing steps that are stored in the CIImage's program. The program generates the pixels that the computer eventually draws onto a graphics context of some kind. But the pixels themselves are neither calculated nor stored until the CIImage is drawn and its image program runs. This lazy evaluation is an important feature of Core Image.

This concept of CIImages, as instructions for drawing an image instead of as a fixed collection of pixels, allows CIImages to have characteristics that are quite different from other images you may encounter. For example, a CIImage can consist of a drawing instruction like "fill the entire drawing plane with a blue and white checkerboard pattern." In this example, the resulting "image" is unbounded. It fills the entire drawing plane and is infinite in extent.

The connection between a CIImage and a CGImage comes about when the CIImage uses the CGImage as a drawing parameter. The simplest CIImage instruction is probably "Draw these pixels." To provide the particular pixels the image program should draw, an application will provide the CGImage containing the pixels. Note that the CIImage itself doesn't necessarily own a frame buffer with color image data in it. The color image data is owned by the CGImage that is a parameter of a CIImage drawing command. The difference is a subtle but important one.

The CIImage can contain more interesting instructions than just "Draw this image." Consider a simple two-step CIImage where the first step is "collect a bundle of pixels," and the second step is "blur those pixels with a Gaussian blur with a radius of 3." When the computer draws this CIImage, it executes the program steps and the result is a blurred version of the image specified in step 1 of the program. The actual pixels that the computer sets down when it draws the CIImage are calculated when the CIImage is drawn and not before.

To add the Gaussian blur or any other effect to the image program inside of a CIImage, you create a filter object. Filter objects are instances of the CIFilter class. A CIFilter object knows how to generate an image program from a set of input parameters. You might think of them as CIImage factories. The factories take in raw materials, bundles of pixels, input parameters and the like, then use those input parameters to write a program that combines them in some way. The resulting program is a CIImage.

To use a CIFilter, your application creates a particular instance of a CIFilter subclass. Each subclass represents a specific image program step you want the computer to take. You then specify the input parameters for that filter. The input parameters might include other images, numbers, strings, and similar elements that the filter needs for processing. Continuing the preceding example, one subclass of CIFilter accepts an input image and a floating point radius as parameters and performs a Gaussian blur on that image with that radius.

After filling in the input parameters to a CIFilter, you can ask it for its output parameters. The most common output parameter your program will take from a CIFilter is the outputImage parameter. The value of this parameter is a CIImage object, an image program, that encapsulates the input parameters and the drawing commands necessary to carry out the behavior of the filter.

The CIImage that is the output of one CIFilter can become an input to another CIFilter. Your application can use this fact to chain many of filters together. The result of this chain is a CIImage that contains all the processing commands of all the filters. Along the way, the computer collects the various drawing steps, with their input parameters, and records them into another CIImage that becomes the output image of the final filter. Keep in mind that the CIImage is not actually generating pixels as you chain the filters together. It is simply recording the drawing commands and parameters for each filter in the chain.

To actually get pixels out of a CIImage, you have to run the image program that it represents. To do that, you draw the CIImage. Since the filters don't do any processing until you draw the final image, the computer is free to optimize your image program as it collapses the collection of filters into a single rendering step. For example, if your image program specifies that the computer should give all the pixels in an image a purplish tint and then scale the result down to half its original size, the computer might rearrange these steps so that the scaling happens first and the tinting second. The advantage to this is that the computer has to modify fewer pixels to colorize the scaled-down image than it does to colorize the full-scale image.

Just as Core Graphics drawing occurs in a CGContext, Core Image has a CIContext. The context references another drawing context, either a CGCon-text or an OpenGL context, the same way the CIImage references a CGImage to supply pixel data. In addition to drawing into a CGContext, the initial version of Core Image allows you to draw into an OpenGL context.


Using a Core Image Filter

You've just seen the basic steps an application goes through to use Core Image. Now you'll see some of the details by looking at sample code. This code sample builds and renders the CIImage that applies a Gaussian blur to an image. The results of the Gaussian blur are illustrated in Figure 10.1.

Figure 10.1. The Image Before and After Blurring

In this case Core Image has been asked to take the lovely beach image and apply a Gaussian Blur filter with a radius of 10. This complete code sample is called CoreImageBasics and is a Carbon application that simply displays the blurred image in a window. The portion of the application that generates the blurred image is given in Listing 10.1.

Listing 10.1. Code Using Core Image to Blur an Image

void DrawBlurredImage(CGContextRef outputContext,
           const CGRect &viewBounds)
{
    // Create an autorelease pool to catch autoreleased objects
    // from Objective-C
    NSAutoreleasePool *autoreleasePool =
            [[NSAutoreleasePool alloc] init];

    // Obtain the source image and create a CIImage with that
    // set of pixels
    CGImageRef sourceCGImage = CreateSourceImage();
    CIImage *sourceCIImage = [CIImage imageWithCGImage: sourceCGImage];

    CGRect sourceBounds = CGRectMake(0, 0,
                   CGImageGetWidth(sourceCGImage),
                   CGImageGetHeight(sourceCGImage));

    // We've done with the source image.
    CFRelease(sourceCGImage);
    sourceCGImage = NULL;

    // Create a filter and assign the parameters
    CIFilter *gaussianBlur =
            [CIFilter filterWithName: @"CIGaussianBlur"];
    [gaussianBlur setValue: sourceCIImage forKey: @"inputImage"];
    [gaussianBlur setValue: [NSNumber numberWithFloat: 10]
                   forKey: @"inputRadius"];

    // extract the resulting CIImage from the blur filter.
    CIImage *result = [gaussianBlur valueForKey: @"outputImage"];

    // Create a CIContext on our graphics context and
    // render the image into it.
    CIContext *outputCIContext = [CIContext
            contextWithCGContext: outputContext options: NULL];
    [outputCIContext drawImage: result
                   inRect: viewBounds fromRect: sourceBounds];
    // release the autorelase pool
    [autoreleasePool release];
    autoreleasePool = NULL;
}

The code begins by creating an NSAutoreleasePool. This is a small concession that Carbon applications must make when working with Objective-C APIs. The autorelease pool catches objects that your application, or the operating system, might autorelease in the course of its run. If this code were running inside of a Cocoa application, the autorelease pool would be part of the main application event loop. The source code releases the autorelease pool at the bottom, which frees any autoreleased objects.

For more information on using Objective-C methods from Carbon applications, please visit the Carbon-Cocoa integration guide at

http://developer.apple.com/documentation/Cocoa/Conceptual/CarbonCocoaDoc/Articles/CocoaUIInCarbon.html

The code calls an external routine, CreateSourceImage, that loads the beach scene from a file in the application's resources using Image I/O (see Chapter 9, "Importing and Exporting Images"). The code uses this CGImage to create a CIImage. The CIImage that it creates has a single instruction which is, essentially, "collect these specific pixels." If you were to draw this CIImage, it would do little more than draw the pixels of the CGImage.

The code extracts a bounding rectangle for the CGImage as well, and at that point we're done with it. By the rules of object ownership, if the CIImage wants the CGImage any more, it should have retained it, but you're done with the image now, so you release it.

The image program has been started and you're ready to filter the image. You create the CIFilter using the filterWithName method of that class. Filters are also organized into categories. Using the methods of the CIFilter class, your application can obtain lists of categories and lists of all the filters in a particular category. The built-in categories are listed in the CIFilter.h header file. You can use these routines to allow your users to create the filter of their choice.

Next, the code sets the parameters for the blur filter. In writing the sample code, the input parameters for the filter in the documentation were looked up.

A CIFilter also includes methods that let you ask them for a list of the input parameter keys for the filter. This might be helpful if your application wants to generate a custom UI to let the user specify values for the input parameters. The Core Image Fun House sample code mentioned earlier in the chapter demonstrates how to do this.

In this case, the Gaussian blur filter accepts two parameters, an image (the image to blur) and the blur radius. Blur radius is simply a value that indicates how blurry you want the image to be. CIFilters use an Objective-C programming technique called "key-value coding" to change the input parameters of the filter. Essentially this means that your code sets the input parameters by name. The method setValue:forKey: is used here to establish the CIImage that is the input to the filter, as well as the inputRadius for the blur filter.

Key-Value Coding is a popular technique for several of the Objective-C based technologies that Apple provides. The basic idea behind key-value coding is that you can access the fields of an object by name rather than having to add accessor methods to your class for every field. If you would like to learn more about key-value coding in Objective-C and Java, please visit the following documentation:

http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/index.html

At this point, you are ready to construct the CIImage that contains both drawing commands. To do this, you ask the filter for its outputImage property, again using the techniques of key-value coding. This property combines the input parameters and generates a new CIImage. This new CIImage knows how to draw the results of applying that filter.

The final step of the sample draws the results of the filtering step into the CGContext that was passed to the routine as a parameter. To draw on the CGContext you have to first create a CIContext that the computer will execute the CIImage's image program in. You can use the contextWithCGContext: method of CIContext to create an appropriate drawing environment and then ask that environment to draw the CIImage.

This sample code runs through all these steps every time the computer wants to draw the image. Ideally, however, the code would cache the CIImage retrieved from the Gaussian blur filter and reuse it to draw the same image each time it was asked to.


Combining Filters and Images

When you retrieve the outputImage property of a CIFilter object, the result is a CIImage. There is nothing that prevents you from taking that CIImage and passing it as the input parameter of another filter. By carefully combining filters, your application can create very complex effects.

Core Image also makes it easy to combine the results of different filter chains. In fact, some of the filters in Core Image are little more than collecting points, meeting places if you will, where you can tie together different filter chains and combine their results. The filters in the Compositing Filter category are interesting examples of this kind of filter.

One particularly useful class that Core Image provides is the CIImageAccumulator. Unfortunately the documentation of CIImageAccumulator invokes some rather heady concepts involving "dynamical systems," which makes it difficult to understand what this class is for. The actual class itself, however, is pretty simple. The CIImageAccumulator is a handy tool to use when you want to create an image by collecting the results of applying small, incremental image filters. The CIAnnotation sample at Apple's web site demonstrates how this is done:

http://developer.apple.com/samplecode/CIAnnotation/CIAnnotation.html

The canonical example of a CIImageAccumulator in action is a program that lets the user paint on a canvas. The basic idea is that the user selects a paintbrush tool that has a particular brush shape. As the user drags the brush across the canvas, the computer draws an image by tracking the mouse and repeatedly drawing the brush shape on the canvas.

In this example, the computer may have a CIFilter that accepts the mouse location as an input parameter and generates a CIImage of paintbrush's shape drawn at that location. By drawing many copies of this filter with different locations and collecting the results into a final image, the end result is as if the user had dragged the brush to those locations. The CIImageAccumulator is the tool that knows how to collect the results of running the brush shape filter at each mouse location.

If you think of the CIImage as an image program, rather than as a collection of pixels, it may be easier to understand the role of the CIImageAccumulator. The image program that draws the results of the user painting on the canvas could, potentially, have a large number of similar steps. Each step would take the "current" image, use it as the input to a small filter, and the new "current image" would be the results of applying that filter. Expressed in pseudocode the image program would contain statements like this

currentImage = ApplySomeFilter(currentImage)

If you want to create a CIImage (write an image program) that contains several steps that follow this pattern, using an CIImageAccumulator will help Core Image optimize the execution of this image program.


Core Image Transitions

Core Image transitions can add a lot of visual appeal to your application. You can see Core Image transitions at work in many modern Mac OS X applications. These effects are not new to the world of computer graphics. Indeed image transitions have been a staple of computer video production for many years. Core Image Transitions are exciting, however, because your application can run them in real-time from current images. This brings transitions out of the realm of motion video applications and makes them available to just about anybody.

When used in the proper context, Core Image transitions can provide your application with some exciting visual appeal with little effort. The same transitions can also quickly become annoying, however, when used indiscriminately. A ripple effect between the slides of a slide show enhances the presentation of the user's content. The same ripple effect, animated every time the user switches between two of the tabs in your tab pane, however, will quickly become tedious and annoying. If you use Core Image transitions in your user interface, please give careful consideration to whether or not the transition is appropriate. When in doubt, either offer the user the option of disabling the transition, or just leave it out altogether. Your users will appreciate your discretion.

From the standpoint of the Core Image API, a transition is "just another filter." Drawing the transition involves drawing the individual frames of an animation. The filter knows how to draw one frame of that animation. To make the transition, you will ask the filter to generate each successive frame and will play those frames back for the user.

The transition filters typically accept at least three parameters. The first two are, of course, the image you want to transition from and the image you want to transition to. The third parameter indicates what frame of the animation you want to render. For the built-in Core Image transitions this input parameter is usually called inputTime, and the value of the parameter will be a floating point number between 0 and 1. A value of 0 indicates that you are at frame zero of the animation and a value of 1 indicates that you have completed the transition. Your application might choose to divide the transition up into 30 frames, so you could step through those frames by starting the inputTime at 0 and incrementing it by 1.0/30.0 for each frame. If you want to run a transition "backward," then it's as easy as beginning your inputTime parameter at 1.0 and stepping backward through the frames.

Once you have created a transition filter and assigned its initial and final images, animating the transition is not any more complicated than repeatedly asking the filter for the current frame by setting the inputTime parameter and then asking the filter for its outputImage. The trickiest part is running the animation loop while keeping the application and the UI responsive. One of the best techniques for doing this is to use timers.


Animation, Timers, and Updates

Mac OS X is a very dynamic operating system. At any given time the computer may be working on several tasks at once. Your application has to be a good citizen in this environment and should not tie up the CPU any more than it has to. If you want to perform some animation in your application, you might do so by providing a loop that draws a frame, pauses for some time interval, and then draws another frame. Unfortunately, your application will be completely occupied while in this loop and might make better use of its time by performing some other calculations in the time that it has paused to render the next frame. Moreover, if the user tries to interact with your program during that pause, and the program is unable to respond, they might perceive your application as unresponsive and sluggish. To work around this problem, Mac OS X includes many different systems that provide timers.

The timer-based animation technique discussed in this chapter is subject to latency problems. For example when your application gets busy and doesn't make it back to the event loop often enough, this technique can lead to animations that display frames irregularly.

Mac OS X 10.4 Tiger introduced Core Video, a graphics library dedicated to handling buffers and timing services for displaying video. Using Core Video you can get very accurate animation timing. This system uses multithreading and a callback to tell the application the ideal time to display each frame so that it synchronizes with the display.

The multithreading that makes this system possible works ideally with OpenGL but doesn't mesh as well with update mechanisms of Carbon HIViews or Cocoa NSViews and their CGContexts. As a rule of thumb, Carbon and Cocoa both expect you to use Quartz 2D when drawing views from the application's main thread.

For applications with simple needs, the techniques described here should be sufficient. If your application needs more sophisticated and exact animation timings, however, you should investigate Core Video.

About Timers

A timer is little more than an entity on the system that calls back to your application at a fixed interval. Your application can set up a timer to call you back at the frame rate of your animation. When you receive the notification that the timer has fired, your application can draw one frame of the animation (or indicate to a view that the next frame needs to be drawn) and return. In the mean time, however, your application can continue to process user events or perform some important calculation. It only needs to worry about managing the animation when that animation needs attention.

One of the hardest parts about using timers is deciding which timing mechanism to use. At the lowest levels of the application frameworks, you have run loop timers, CFRunLoopTimer. Each of the application frameworks also offer timer objects. Carbon application can use Carbon event timers, and Cocoa applications can use NSTimer objects. The actual choice you make depends largely on what type of notification mechanism you prefer. CFRunLoopTimers notifies your application when the timer fires by invoking a callback routine. Carbon event timers, naturally, send a Carbon event when the timer fires. NSTimer objects can invoke a method on an Objective-C object when their timer expires. As a general rule of thumb, you will probably be best off if you use the timing mechanism that matches your application architecture. Just keep in mind that the other timing mechanisms are also there to help you if you need them.

Beginning with Mac OS X 10.4 Tiger, Apple has added a Cocoa class for managing animations called NSAnimation. The class contains several of features specifically aimed at creating transitions for user Interface work. It contains a number of handy features including the ability to run a series of animations, one after another, and tools for running the animation at varying rates over time.

Updating the Animation

The way you draw the frames of the animation is another important consideration on Mac OS X. The simplest way to respond to a timer asking you to draw the next frame of your animation might be to simply draw the frame and return control. However, on Mac OS X, the Carbon and Cocoa application frameworks like to take a more structured approach to drawing. In Carbon, your application usually draws its user interface in response to a kEventControlDraw message sent to a particular HIView. In Cocoa, an application draws an NSView in response to the drawRect: message. As a rule of thumb, in both frameworks, your application shouldn't be drawing outside of these two mechanisms unless it has very special drawing behavior (e.g., if has captured the screen and is drawing in full-screen mode).

To work with the operating system, your application should draw the frames of the transition animation within the appropriate context. All that your timer callback needs to do is induce the operating system to call your view's draw handler. To do that the timer callback mechanism might call HIViewSetNeedsDisplay for a Carbon application or one of the NSView methods that begin with setNeedsDisplay: in Cocoa.

Animation Speed and Refresh Rates

When you watch TV or view a movie, the screen is showing you the individual frames of the picture at a rate that is probably in the range of 24 to 36 frames per second. To prevent flickering as it updates the screen, Mac OS X tries to synchronize the flushing of windows to the refresh rate of the monitor. In contrast, the LCD displays on many Macintosh computers typically refresh their images at a rate of 60 frames per second, while CRT monitors might refresh a little more frequently.

What this means in terms of animation is that if your application is trying to draw an animation, like a Core Image transition, at a rate faster than 60 frames per second, you are not using your time effectively and the user is not likely to see all the frames you are drawing anyway. When you are trying to decide on an animation rate for your transition, unless your application has very special needs, you might start with 30 frames per second and test the transition. Use higher refresh rates if you must, but keep in mind that there is an upper limit on the number of images the user will see.


A Transition Example

To close out this chapter on Core Image, let's look at a sample application that animates a Core Image transition. We'll use the built-in Page Curl transition to switch between two images. Figure 10.2 shows several frames of the animation that the code creates:

Figure 10.2. Frames from the Page Curl Animation

The code draws the animation in a custom subclass of NSView, called transitionView. In addition to drawing the animation, the transitionView keeps track of Core Image objects and animation parameters. This code sample uses Cocoa as its application framework so the animation is driven with an NSTimer. Because it is using a timer, a user can continue to interact with the application, moving windows around and such, while the animation runs simultaneously.

Listing 10.2 shows the class declaration for our custom NSView class.

Listing 10.2. TransitionView Class Declaration

@interface TransitionView : NSView
{
    CIImage        *mSourceImage;
    CIImage        *mDestinationImage;
    CIFilter       *mTransitionFilter;
    NSTimer        *mAnimationTimer;
    float          mCurrentTime;
    float          mAnimationDirection;
}

- (IBAction) runAnimation: (id) sender;

- (void) drawRect: (NSRect) rectToDraw;
- (void) handleTimerEvent: (NSTimer *) timerThatFired;

- (void) loadImages;

@end

The first two fields give the starting and ending images of the transition. The mtransitionFilter field contains the transition object that generates the frames of the animation. The animation timer is the timer responsible for telling the view when to draw a new frame. The mCurrentTime field keeps track of how much of the animation has already been drawn. This value will track from 0.0 to 1.0 as the animation progresses and will become the inputTime for our transition filter. The code loops the animation forward and backward, and the mAnimationDirection field keeps track of which direction the animation is currently running. When this field is 1.0, the animation will run forward from the source image to the destination. When the field is -1.0, the animation runs backward from the destination image back toward the source.

The TRansitionView class has six methods. The first, initWithRect:, sets up the transformation filter. The next is runAnimation:. It is the method that restarts the animation from the beginning. In interface builder, this method is the action of a button on the main window that you can press to start the animation. The drawRect: method is the method that Cocoa calls when it wants you to draw a portion of your view. The handleTimerEvent: method is what the timer will call when our animation should draw the next frame. The loadImages method is a utility method that uses Image I/O to load the two images our transition is moving between. The dealloc method cleans up the instance variables stored in the object.

An examination of this code begins with the initWithRect: method. This method sets up most of the parameters of the animation. It creates the transition and timer objects and initializes the mCurrentTime and mAnimationDirection fields. The code for initWithRect: is given in Listing 10.3.

Listing 10.3. The initWithFrame: Method of the Transition Sample Code

- (id)initWithFrame:(NSRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
           CIVector *imageExtent =
                  [CIVector vectorWithX: 0 Y: 0 Z: 480 W: 640];

           // Initialization code here.
           [self loadImages];
           mCurrentTime = 0;
           mAnimationDirection = 1.0;

           // We use a solid color for the backside of the page curl
           // and the shading image as well. Since the Constant Color
           // Generator generates an image of infinite extent, we
           // also have to crop the image before
           // we can use it.
           CIFilter *solidColor =
                  [CIFilter filterWithName:
                          @"CIConstantColorGenerator"];

           [solidColor setValue:
                  [CIColor colorWithRed: 0.39 green: 0.65 blue: 0.94
                          alpha: 0.75] forKey: @"inputColor"];

           CIFilter *cropColor = [CIFilter filterWithName: @"CICrop"];
           [cropColor setValue: [solidColor valueForKey:
                  @"outputImage"] forKey: @"inputImage"];
           [cropColor setValue: imageExtent forKey: @"inputRectangle"];

           // Create the transition that generates the
           // intermediate frames of our animation
           mTransitionFilter = [[CIFilter
                  filterWithName: @"CIPageCurlTransition"] retain];
           [mTransitionFilter setDefaults];

           [mTransitionFilter setValue: mSourceImage
                  forKey: @"inputImage"];
           [mTransitionFilter setValue: mDestinationImage
                  forKey: @"inputTargetImage"];
           [mTransitionFilter setValue:
                  [cropColor valueForKey: @"outputImage"]
                  forKey: @"inputBacksideImage"];
           [mTransitionFilter setValue:
                  [cropColor valueForKey: @"outputImage"]
                  forKey: @"inputShadingImage"];
           [mTransitionFilter setValue:
                  [NSNumber numberWithFloat: -pi / 4.0]
                  forKey: @"inputAngle"];
           [mTransitionFilter setValue: imageExtent
                  forKey: @"inputExtent"];
   }

   return self;
}

The routine begins by creating an instance of CIVector. A CIVector is simply a collection of two, three, or four floating point numbers. Among other uses, Core Image employs CIVectors as convenient objects for specifying parameters to CIFilters. In this case, the vector we are creating represents the rectangle that our animation will be drawn in. You will see what this information is for very shortly.

The code continues by creating two filters. These filters will define the CIImages used for the backside of the curling page and the shading that the computer applies to the animation. The first filter is a CIConstantColor filter which generates a CIImage that contains nothing more than a solid ARGB color. The output of the constant color filter is a rectangle of solid color that is infinite in size. Because the computer would have a lot of trouble rendering something that is infinite in size, you need to limit the image to just that area of the drawing space that you are interested in. To do that, you create a crop filter.

After the computer has created the crop filter, it assigns the constant color image as the input image and uses the extent CIVector to tell the crop filter what area of the infinite color plane you are interested in using as the backside of your curled page.

Next, the code creates the Page Curl transition filter and stores the result in the field of the view object. The Page Curl transition has many more parameters than the simple blur filter examined in the beginning of the chapter. The code specifies the source and destination images by using the fields of the view that contain those images. It asks the crop filter for its output image and attaches that to the backside and shading images of the filter. The inputAngle parameter tells the filter in which direction the page should curl. This parameter is expressed in radians, but the value used in the sample is equivalent to -45 degrees (or on the compass, from northwest to southeast). Finally, you use the CIVector again to tell the filter how much of the drawing space you want this transition to affect. In this case you use the full size of the images, 480x640 pixels.

The runAnimation: method creates and manages the timer that will drive the animation. The transitionView object creates a single timer and reuses that timer each time the user asks it to run the animation. The routine used to create the timer the first time has a lot of parameters, but they are pretty straightforward. The rate at which the timer fires is given by the constant kFrameInterval, which in the sample, is defined to be 1.0/30.0, or [image] of a second. The target and selector parameters tell the timer that you want it to notify the TRansitionView when the timer fires and that the timer should invoke the handleTimerEvent: method of the transitionView at that time. If the handleTimerEvent: method needed some additional information from the class, you could pass a pointer to that information in the userInfo: argument. Finally, you ask the timer to repeat until you disable it.

If the timer already exists, then you simply tell the timer to call you back at your frame interval. This portion of the code is given in Listing 10.4.

Listing 10.4. The runAnimation: Method of the Transition Sample Code

- (IBAction) runAnimation: (id) sender
{
    // star the animation at time zero
    mCurrentTime = 0;
    [mTransitionFilter setValue:
            [NSNumber numberWithFloat: mCurrentTime]
            forKey: @"inputTime"];

    // If we don't already have an animation timer, create one,
    // otherwise set it to fire in just a bit
    if(NULL == mAnimationTimer) {
            mAnimationTimer =
                    [[NSTimer
                           scheduledTimerWithTimeInterval:
                                  kFrameInterval
                           target: self
                           selector: @selector(handleTimerEvent:)
                           userInfo: NULL
                           repeats: YES] retain];
    } else {
            [mAnimationTimer setFireDate:
                   [NSDate dateWithTimeIntervalSinceNow:
                                  kFrameInterval]];
    }

    [self setNeedsDisplay: YES];
}

The last call of the method is the way you tell the computer you are ready to draw the first frame of the animation. The computer, in turn, will call back to the drawRect: method whenever it is ready for the application to redraw the TRansitionView. This same pattern appears when we examining the way the sample handles the timer callback.

All of the drawing for the transitionView is done in the drawRect: method. This method can be found in listing 10.5.

Listing 10.5. The drawRect: Method of the Transition Sample

- (void)drawRect:(NSRect)rect
{
    const CGRect kSourceRect = CGRectMake(0, 0, 480, 640);
    CIContext *drawContext = [[NSGraphicsContext currentContext]
           CIContext];

    // If there is a transition filter in effect, crop the output
    // image and draw it in the window. If not then we simply draw
    // the starting image.
    if(mTransitionFilter) {
            CIVector *imageExtent =
                    [CIVector vectorWithX: 0 Y: 0 Z: 480 W: 640];
            // Create a crop filter ot limit the drawing area
            CIFilter *cropTransition =
                    [CIFilter filterWithName: @"CICrop"];
            [cropTransition setValue:
                    [mTransitionFilter valueForKey: @"outputImage"]
                    forKey: @"inputImage"];
            [cropTransition setValue: imageExtent
                    forKey: @"inputRectangle"];

            // draw the cropped version of the transition image
            [drawContext drawImage:
                    [cropTransition valueForKey: @"outputImage"]
                    atPoint: CGPointMake(0, 0)
                    fromRect: kSourceRect];
    } else {
            [drawContext drawImage: mSourceImage
                    atPoint: CGPointMake(0, 0)
                    fromRect: kSourceRect];
    }
}

The drawRect: method begins by asking the current graphics context for the CIContext that the view is drawing into. This is the CIContext that you will draw all your CIImages into.

To generate the current frame of the transition, the code needs to ask the transition object for a CIImage. As the first step in doing so, the code creates a CICrop filter. This filter is very similar to the one used in the runAnimation: method.

When the CICrop filter was used before, the goal was to limit the output of a filter that generates an infinitely large image to only the area of the drawing space. The Page Curl filter does not generate an image of infinite extent, but it can generate an image that extends beyond the bounds of the source or destination images. The crop filter chops off the uninteresting pixels that lie outside of the view's extent. The system passes the cropping information to the Page Curl transition. The transition, in turn, may only calculate the values of pixels that fall inside of the cropped area. This improves the performance of the transition filter by avoiding unnecessary work.

The last interesting method of the transitionView class to explore is the handleTimerEvent: method. This is the method that the animation timer invokes each time it fires. The method has two primary responsibilities. It manages the progression of the animation with the mCurrentTime and mAnimationDirection fields of the view. It also lets the system know when the view would like to draw the next frame of the animation. The contents of the handleTimerEvent: method are given in Listing 10.6:

Listing 10.6. The handleTimerEvent: Method of the TransitionView

- (void) handleTimerEvent: (NSTimer *) timerThatFired
{
    // Advance the current frame to the next time interval.
    mCurrentTime += (kFrameInterval * mAnimationDirection);

    // If the current time has reached one of it's bounds, then
    // reverse the animation's direction
    if(mCurrentTime >= 1.0) {
           mAnimationDirection *= -1;
           mCurrentTime = 1.0;
    }

    // when we get back to the beginning, we kill the animation
    // and set the timer to fire centuries from now.
    if(mCurrentTime <= 0) {
           mCurrentTime = 0;
           mAnimationDirection *= -1;

           [mAnimationTimer setFireDate: [NSDate distantFuture]];
    }

    // Set the current time into the transition filter.
    if(mTransitionFilter) {
           [mTransitionFilter setValue:
                   [NSNumber numberWithFloat: mCurrentTime]
                   forKey: @"inputTime"];
    }

    // And ask the view to redraw
    [self setNeedsDisplay: true];
}

The first thing the timer callback does is advance the mCurrentTime field to the next appropriate value. It uses a bit of cleverness regarding the mAnimationDirection field to progress the animation in the proper direction. If mAnimationDirection is 1.0, the current time will increase by the frame interval. When the animation direction is -1.0, the code decreases the current time by the frame interval.

The next two if statements manage the animation at the middle and end of the cycle respectively. The animation time variable starts at 0 and progresses to 1.0 and then reverses direction and returns back from 1.0 to 0. When the animation reaches 1.0, and the computer reverses the animation direction. It clamps the current time to 1.0 just in case the frame rate introduces round-off errors into the current time calculations.

When the current time returns to zero, you want to stop the animation. To do this, you take the timer and set its next fire time to [NSDate distantFuture]. This does not mean that the timer would never fire again. However, you would have to leave the program running for many centuries before the timer actually fired again. If the transition is running, the code sets the inputTime parameter of the transition to the same value as mCurrentTime.

Finally, the handleTimerEvent: method tells the computer that you want the view to redraw a new frame.

Core Image is remarkably easy to use, and this makes it a very interesting technology to play with. This chapter presented some of the basic concepts of Core Image and provided examples of the fundamental techniques an application uses to interact with the framework, but it has only been able to scratch the surface. You can get a lot of interesting behavior very quickly simply by chaining together the built-in filters. This chapter has expanded on a few of the built-in transitions, but there are dozens more for you to explore. Moreover, your application can extend Core Image by providing filters of its own. The goal here was to get you started on what will hopefully be a fruitful exploration of this exciting technology.


Chapter 11. Drawing Text with Quartz 2D

With the powerful graphics capabilities of Quartz 2D, one of the most remarkable statements to make about the library is that, on its own, Quartz 2D is not a very good tool for drawing text. The key to that statement is the qualifier "on its own." In a modern, multinational operating system like Mac OS X, the entire process of displaying it can be a very complex. Drawing the text is only the last phase of that process and that is the part where Quartz 2D comes into play.

This chapter presents a short overview of how Mac OS X displays text. The goal is not to cover the entire text pipeline in detail, but to offer some context (no pun intended) to help understand where Quartz 2D fits into the text drawing pipeline. This chapter will also discuss the Mac OS X systems that drive the text pipeline, and provide pointers to documentation where more information on this topic can be found. Finally, you will be introduced some of the very low-level routines that Quartz 2D provides for drawing text and learn how those low-level routines can help create special text-drawing effects.


The Text Processing Pipeline

To begin the discussion, the text processing pipeline on Mac OS begins with a block of text, often one typed in by the user and ends with that text being drawn into a graphics context. Along the way the computer has to work with the writing system that generated the text, how the text is represented, and visual attributes like fonts and styles.

Storing Text

The first step in rendering text is storing the text itself. Computers take written language and break it down into a series of characters. Each character corresponds to a specific symbol or idea in a particular writing system. For example, when writing English text, the uppercase letter "A," the number three, and punctuation marks like periods and question marks are all characters. There is no way to draw a character; characters represent conceptual elements of the writing system. The computer stores a series of characters in memory using an encoding scheme. ASCII was an encoding scheme for representing the characters used to write English text in early computers. Modern computers use a character encoding scheme called Unicode. By using Unicode, Mac OS X gains the ability to represent and store most of the languages of the world in a single encoding.

If you want to learn more about Unicode and the various ways computers use Unicode to represent text, you can visit the Unicode Consortium web site at

http://www.Unicode.org/

Representing Text Visually

When the computer wants to draw a Unicode string, it has to come up with a group of pictures to draw that represent the characters. These pictures are called glyphs. Figure 11.1 shows several different glyphs that all represent the character for the number 7.

Figure 11.1. Seven Glyphs Representing the Number Seven

In the figure, each of the glyphs came from a different font. A font is simply a collection of related glyphs that the computer uses to draw some characters. Usually all the glyphs in a font will have similar drawing characteristics. The people who design fonts also collect individual fonts together into font families, and each font in a family will often differ from the other fonts with a particular stylistic variation. For example, the Times font family traditionally has four variations or styles. The first is often called the "plain" or "normal" style of the font (it is also called the regular style). Other fonts in the family include a boldfaced variation (a.k.a. bold), an italic variation, and a version that is both bold and italic.

Developers familiar with QuickDraw, who are moving to more modern text drawing APIs, may find themselves confused by the terminology surrounding fonts. In the QuickDraw model, the computer works with "fonts," and "styles." These terms correspond roughly to "families" and "fonts" respectively in modern text systems. QuickDraw had the ability to synthesize some styles, such as bold and italic, from other font information. If a font had additional variations, like an ornaments version or a book style, the computer had to create artificial font names and synthetic styles to try and represent them.

The modern APIs use the families and fonts directly. A particular font family can contain fonts in hundreds of variations like ornaments, black, medium, book, and condensed or extended. This allows the computer to present the fonts as the original typeface designer created them and in some ways simplifies the user interface because the fonts you see are the fonts you have on your computer.

Using fonts and other text drawing information, the computer creates a mapping from the characters in the string to the glyphs used to draw text. This mapping itself can be quite complex. Depending on the writing system and the contents of the string, the mapping between glyphs and characters may not be one-to-one. A particular string might require more glyphs than characters in one font and fewer glyphs than characters in another. This is the situation demonstrated in Figure 11.2.

Figure 11.2. Text Sample with Ligatures

The first word of Figure 11.2 consists of three glyphs. The first glyph in the figure is a ligature. It a single glyph that represents a combination of the "f" and "i" characters. The second word also contains a ligature for the sequence of two "f" characters followed by an "l." Ligatures are a typographer's tool used to improve the look of some text. The point to take away from this example is that the number of glyphs does not always match the number of characters, adding to the complexity of displaying text.

Once the text system has selected glyphs, it arranges the glyphs on a line. During this layout phase, the computer takes into account features like tracking and kerning that affect the spacing of glyphs. It also considers text attributes like the writing direction when laying out the glyphs on the line. If the system is trying to put the text on a line with a fixed width, the computer would determine how much text will fit on the line at the given stage and wrap the text as necessary. With the wrapping done, the layout engine can justify each line within its drawing space if necessary.

This description of the text layout process has been greatly simplified but hits many of the high points. Overall, in order to draw text, the computer has to combine Unicode text with text style information, including fonts, to select a series of glyphs that represent the text. The computer then arranges those glyphs according to very specific rules. Once this is done, the computer can draw the glyphs on the output device. It is only this last part, drawing the glyphs on the output device, that is accomplished with Quartz 2D.

ATSUI and Cocoa Text

ATSUI is an acronym for "Apple Type Services for Unicode Imaging." It is the fundamental technology that manages the text rendering pipeline in Mac OS X. The Cocoa application frameworks include a number of classes, built on top of ATSUI and Quartz 2D, that are collectively referred to as the Cocoa Text system. These classes present the text rendering pipeline with concepts and idioms that will be familiar to Cocoa application developers. Any application that wants to take full advantage of the full-featured text rendering capabilities of Mac OS X will need to go through ATSUI or Cocoa Text.

In addition to ATSUI and Cocoa Text, the applications frameworks contain a number of other programming interfaces and systems for drawing text. Generally these routines allow you to avoid the full complexity of the rendering pipeline when all you want to do is draw a simple text string quickly For example, the HIToolbox of the Carbon framework includes routines like HIThemeDrawTextBox. This routine uses ATSUI to draw text, usually in some variation of the system font, for use on custom controls. Similarly, the NSString class in Cocoa has an extension with methods like drawAtPoint:withAttributes, which allow you to draw a single string easily.

The convenience routines that the application frameworks provide shield your code from a lot of the complexity involved in setting up the text drawing pipeline. Unfortunately, however, they also must set up and tear down the text drawing pipeline on your behalf each time they are called. As a result, these routines are not the most efficient way to draw a great deal of text. If your profiling reveals that text drawing is a performance bottleneck, you might be able to improve the situation by taking more responsibility for the text pipeline.

Both ATSUI and Cocoa Text are feature-rich software systems, worthy of entire books of their own. We won't be able to cover them in depth here. The following links may help you to explore those technologies and incorporate them into your application.

The book "Rendering Unicode Text With ATSUI" is an excellent introduction to the text rendering pipeline. The chapter on Typography Concepts, with its section on Text Layout, is particularly interesting if you want to know more about the glyph layout process. The text also introduces much of the terminology that surrounds fonts and text layout. Understanding this terminology can be an important first step to helping you understand text processing in general. You can read this book on Apple's developer documentation site:

http://developer.apple.com/documentation/Carbon/Conceptual/ATSUI_Concepts/index.html

Cocoa developers may want to look at the first few chapters of the book just mentioned. A good overview of the Cocoa Text system is given in the Cocoa Text System Architecture documentation at

http://developer.apple.com/documentation/Cocoa/Conceptual/TextArchitecture/index.html

A particularly interesting topic to read in this document is "Assembling the Text System by Hand." Even if you never need to individually put together the different classes of the Cocoa text system, reading about how to do it may help you understand how Cocoa works with text.

If your Cocoa application has modest text rendering needs or if you want a very detailed explanation of the Cocoa text rendering process, you should read the Text Layout Programming Guide at

http://developer.apple.com/documentation/Cocoa/Conceptual/TextLayout/index.html

This document is also particularly interesting if you are working with the Cocoa Text system and want to ensure that you are getting the best possible performance.


Drawing Text

ATSUI and Cocoa Text, or one of the application framework utilities that are built on top of them should provide the majority of the functionality that an application needs. Applications with very special text demands can turn to Quartz 2D and draw text directly. There are two different sets of routines for drawing text directly with Quartz 2D. The first set of routines simplify the drawing process by only handling text in a single encoding, Mac Roman. These routines are obviously inappropriate for the broader field of international text. The second set of routines requires the application to supply Quartz 2D with glyph IDs. Applications that want to use these routines must understand quite a bit more about the relationship between characters, encodings, fonts, and glyphs. Both of these routines share some common fields in the graphics state that affect the way they draw.

Text Matrix

The text matrix is an additional affine transformation that Quartz 2D applies to the current transformation matrix when drawing glyphs. You can use this matrix to adjust text independently of the transformation matrix used to draw other graphics. CGContextSetTextMatrix allows you to set the current text matrix, and there is a corresponding Get routine that you can use to retrieve the matrix. These routines accept and return CGAffi neTransformations.

When Quartz applies the text matrix it adjusts the glyphs it is drawing relative to the current text position. The text matrix also affects the way the computer adjusts the current text position after drawing a glyph. This means that if you set the current text matrix to scale the glyphs you draw by 200%, then the computer will also move the advance width by twice the usual distance. All this really means is that the computer adjusts the current text position in accordance with the glyphs that it draws.

Text Position

The translation point stored in the text matrix is called the current text position. When the computer draws text in the context, it places the baseline of the text, in user space, at the current text position. As Quartz 2D draws each glyph, it adjusts the current position to a new location based on the advance width of the glyphs it is drawing. This leaves the current text position where it needs to be to draw the next glyph. Figure 11.3 shows how the current text position changes when the computer draws a character.

Figure 11.3. How Drawing Changes the Text Position

You can set the current text position in a CGContext explicitly using CGContextSetTextPosition. You can also retrieve the current text position with the companion routine CGContextGetTextPosition. If you haven't explicitly set the text position, but the context has a current point, established by using CGContextMoveToPoint for example, the text will draw at that point.

Cocoa Text utility routines draw from a different location than the raw Quartz 2D routines. For example, the point passed to Cocoa drawing routines like drawAtPoint:withAttributes: represent the upper left corner of the bounding box of the text rather than a point on the baseline.

Current Font

As you may guess, the current font determines which font the computer uses when drawing glyphs. There are two routines that allow you to change the current font in the graphics context. The first is CGContextSetFont. This routine accepts an instance of the CGFont opaque data type. A CGFontRef represents a particular font on your system, like Times-Bold. You can create CGFont references using various means including font references from the Apple Type System (ATS). The second routine is CGContextSelectFont. This routine accepts the PostScript name of a font, a font size, and a text encoding as parameters. This routine will attempt to set the current font to the one you specify, but it can fail if the font cannot be found on your system. Unfortunately, the routine doesn't return any indication that it has failed. If you wish to set the text size of the context after selecting a font (using either of the routines just mentioned, you can use the routine CGContextSetFontSize.

Text Drawing Modes

Another field found in the graphics state of a context that you can take advantage of is the text drawing mode. The following list describes the eight text drawing modes supported in Mac OS X 10.4 Tiger:

kCGTextFill Fill the glyph paths.

kCGTextStroke Stroke the glyph paths.

kCGTextFillStroke Fill and then stroke, the glyph paths.

kCGTextInvisible Don't draw the glyphs at all but update the text position as if the glyphs were drawn.

kCGTextClip Do not draw the glyphs but add their shapes to the clipping path of the current context.

kCGTextFillClip Fill the text and then add the glyph outlines to the context's clipping path.

kCGTextStrokeClip Stroke the glyphs and then add the outlines to the context's clipping path.

kCGTextFillStrokeClip Fill and then stroke the text and add the outlines to the text paths.


Drawing Simple Strings

The API of a CGContext includes a few routines that allow you to draw simple strings. These routines handle the entire text pipeline from character to glyph mapping, all the way to drawing in the context. Unfortunately, these routines are only useful for drawing strings in the MacRoman encoding. If your application needs to draw text in any other encoding, you should probably use the higher-level text mechanisms.

The most basic routine in this category is CGContextShowText. This routine simply accepts a pointer to a string and a length and draws that string at the current text position using the current text settings. You can also use the routine CGContextShowTextAtPoint, which allows you to specify a location for the string as parameters rather than relying on the current text position.

These text routines are the ones that were used in the Ace of Hearts bakery example in Chapter 3, "Introduction to Quartz 2D." Our needs for that sample were very basic, and all the text used was MacRoman, which made the simple string routines a perfect choice for that simple example.


Drawing with Glyphs

So far you have been given a glimpse of the process of converting a string of characters into a series of glyph IDs. Glyph IDs are little more than an index into the table of character shapes that are stored in a particular font. As a result, glyph IDs are specific to the particular font you are drawing. Generating a sequence of glyphs from a particular character string can be a complex process. Nevertheless, if your application has custom mechanisms for generating or discovering, glyph IDs, then you can use Quartz 2D to draw text using glyph indexes as well.

There are two fundamental drawing routines that correspond to the simple string drawing routines discussed in the previous section. These are CGContextShowGlyphs and CGContextShowGlyphsAtPoint. Each of these routines combine information from the text settings in the current context with an array of glyph indices to draw text on the context. The CGContextShowGlyphsAtPoint routine also accepts a text position as a parameter, overriding the current text position in the graphics state.

The third routine for drawing text in the current context with glyph IDs is CGContextShowGlyphsWithAdvances. This routine also accepts an array of glyph indices as a parameter. It also takes a corresponding array of floating point values specifying the advance widths to use for the glyphs to be drawn. These advance widths override the values that Quartz reads out of the font tables.

One common technique used by applications that have special drawing needs is to rely on higher-level facilities like Cocoa Text or ATSUI to handle the conversion from characters to glyphs and then to use the glyph drawing routines to actually put the glyphs on a context. For example, ATSUI has the ATSUGetGlyphInfo routine to return information, including the glyph indices of arranged text. Cocoa programmers can use any of a number of routines in an NSLayoutManager that begin with the prefix "getGlyphs."


A Drawing Sample

The sample code for this chapter includes a program called Sunset, which arranges the word Sunset on a semi-circle. As each glyph is drawn, it is also used as a clipping path, and a shaded background is drawn inside the clipped path. The output of the program is shown in Figure 11.4.

Figure 11.4. The Output of the Sunset Sample

For this sample, ATSUI is used to convert the characters into glyphs and typeset those glyphs along a line. Instead of drawing the glyphs as ATSUI has arranged them, however, the code will use Quartz 2D to arrange and draw the glyphs in a context. The sample code contains the complete code for the Sunset sample, two routines are examined here. The first is the routine that extracts glyph information from the ATSUI layout. The second is the routine that arranges and draws the glyphs on the context.

Listing 11.1 is the routine that extracts glyph information from ATSUI.

Listing 11.1. Obtaining Glyphs for the Sunset Sample

void CreateGlyphDrawingData()
{
    // Use ATSUGetUnjustifiedBounds to find out the full length
    // of the text.
    ATSUTextMeasurement before, after, fixedAscent, fixedDescent;
    ATSUGetUnjustifiedBounds(
           gTextLayout,
           kATSUFromTextBeginning,
           kATSUToTextEnd,
           &before,
           &after,
           &fixedAscent,
           &fixedDescent);

    float width = FixedToFloat(after - before);

    // Call GetGlyphInfo once to figure out how big
    // a buffer we need to hold the glyph info
    ByteCount bufferSize;
    ATSUGetGlyphInfo ( gTextLayout, kATSUFromTextBeginning,
            kATSUToTextEnd, &bufferSize, NULL);

    // Allocate the memory for the glyph info
    ATSUGlyphInfoArray *glyphInfo =
           (ATSUGlyphInfoArray *) malloc(bufferSize);

    // Now that we have a buffer ask for the arranged glyphs
    // info "for real"
    ATSUGetGlyphInfo( gTextLayout, kATSUFromTextBeginning,
            kATSUToTextEnd, &bufferSize, glyphInfo);

    // Loop over the glyphs and create our drawing info based on
    // their spacing
    gGlyphDrawingInfo.numGlyphs = glyphInfo->numGlyphs;
    for(short glyphCtr = 0;
            glyphCtr < glyphInfo->numGlyphs; glyphCtr++) {
            float glyphWidth;

            // We use the spacing information to calculate the
            // advance width of each glyph.
            if(glyphCtr == (glyphInfo->numGlyphs - 1)) {
                    glyphWidth = (width -
                            glyphInfo->glyphs[glyphCtr].idealX);
            } else {
                    glyphWidth = (glyphInfo->glyphs[glyphCtr + 1].idealX
                            - glyphInfo->glyphs[glyphCtr].idealX);
            }

            // We calculate the position of the middle of each advance
            // width. From the position we calculate the
            // angular position for the glyph
            float glyphPosition = glyphInfo->glyphs[glyphCtr].idealX +
                    (glyphWidth / 2.0);
            float scaledAngle = (glyphPosition / width) * 180.0;

            // Record the drawing info in our static array.
            gGlyphDrawingInfo.glyphInfo[glyphCtr].glyphID =
                    glyphInfo->glyphs[glyphCtr].glyphID;
            gGlyphDrawingInfo.glyphInfo[glyphCtr].glyphWidth =
                    glyphWidth;
            gGlyphDrawingInfo.glyphInfo[glyphCtr].angle =
                    (90.0 - scaledAngle);
    }

    free(glyphInfo);
}

This code sample creates an ATSUI text layout that contains the string "Sunset" and stores that layout in the global gTextLayout where it can be used repeatedly. The code treats the text as a single, long line. The first thing this routine does is measure the width of the text. The width returned is the width of the drawn glyphs as they would appear before the computer applies justification (e.g., center justification) to them. We will use this width to space the glyphs in their corresponding positions on an arc that subtends 180 degrees.

After measuring the text, the computer needs to get the positions and glyph IDs of each glyph that ATSUI has typeset. It calls ATSUGetGlyphInfo twiceonce to figure out how much data the routine will return and once to retrieve the data itself. ATSUGetGlyphInfo returns a lot of information about the layout of text. The only information being used here is the positions of the glyphs on a line. After getting the glyphs, the code loops over each glyph and uses the glyph's position and the width of the layout to determine the position of each glyph on the arc. The code records the glyph positioning information in a global array. A more complete program would probably allocate this array dynamically rather than using a fixed-size, statically allocated data structure.

After recording the drawing information, the computer needs to draw that information in a context. The routine that draws the glyph info is given in Listing 11.2.

Listing 11.2. Drawing the Sunset Sample

void DrawSunsetSample(CGContextRef inContext, CGRect bounds)
{
    // If the text layout has never been created. Make it
    if(NULL == gTextLayout) {
            gTextLayout = CreateTextLayout(inContext);
    }
    // Create a CGFont from the ATSUI Font used in the text layout
    ATSFontRef atsFontRef = FMGetATSFontRefFromFont(GetFontID());
    CGFontRef cgFont = CGFontCreateWithPlatformFont(&atsFontRef);

    // Save the GState and move the origin to the center of the view.
    CGContextSaveGState(inContext);
    CGContextTranslateCTM(inContext,
           CGRectGetMidX(bounds), CGRectGetMidY(bounds));

    // Set up the font environment.
    CGContextSetFont(inContext, cgFont);
    CGContextSetFontSize(inContext, 72);
    CGContextSetTextDrawingMode(inContext, kCGTextStrokeClip);
    CFRelease(cgFont);
    cgFont = NULL;
    // Calculate the positioning information of the glyph
    CreateGlyphDrawingData();

    // Run through the glyph positions we've calculated and
    // draw the glyphs
    for(short ctr = 0; ctr < gGlyphDrawingInfo.numGlyphs; ctr++) {
            // Save the current rotation angle and the clipping area
            // then draw a glyph. Because of the text mode this clips
            // the context to the glyph and strokes the glyph
            // simultaneously.
            CGContextSaveGState(inContext);
            CGContextRotateCTM(inContext,
                    gGlyphDrawingInfo.glyphInfo[ctr].angle * pi / 180.0);
            CGContextShowGlyphsAtPoint( inContext,
                    -(gGlyphDrawingInfo.glyphInfo[ctr].glyphWidth / 2.0),
                    75, &gGlyphDrawingInfo.glyphInfo[ctr].glyphID, 1);

            // Draw the background of the glyphs while the
            // glyph clip is in force
            DrawGlyphBackground(inContext, bounds);

            CGContextRestoreGState(inContext);
     }

     // We're done drawing so restore the graphics state
     CGContextRestoreGState(inContext);

     // We're done with the text layout
     ATSUDisposeTextLayout(gTextLayout);
     gTextLayout = NULL;
}

After creating the text layout that will arrange the text, this code needs to set the font attributes of the context. To create a CGFont, the computer needs an ATSFontRef and obtains it by converting the ATSUFontID used by the ATSUI layout. After setting the font and size of the context, the code sets the text drawing mode to kCGTextStrokeClip. As mentioned earlier, this means that when you draw text it will be outlined with a stroke but also added to the clipping region of the context.

You then enter a loop that iterates over the glyph drawing information you've calculated. This code rotates the y axis of the context to the angle calculated for the particular glyph you are drawing and draws the glyph centered on the y axis. As the computer draws each glyph, it fills in the clipping area inside each with the background pattern. For our sample, the background image is drawn using a short chain of Core Image filters that give a pleasing gradient effect.

This sample shows one simple example of using Quartz to create a complex text effect. For mundane text drawing needs, your application should rely on ATSUI, Cocoa Text, or similar high-level text technologies. These technologies insulate you from a lot of complex processing that the computer must do when rendering text. However, if you are willing to work more intimately within the complexities of the text rendering process, the low-level text drawing routines in Quartz 2D can be very effective tools.


Chapter 12. Drawing Offscreen

There are many reasons your application might need to draw into an offscreen graphics environment. Perhaps the most common is to create a cache containing a complex drawing. Using the cache, the complex drawing can be drawn to the screen quickly many times over. With a rasterization-based drawing library like Quartz 2D, another popular reason is to create a pixel-based representation of a drawing.

Quartz 2D includes several techniques for drawing offscreen:

  • You can create a graphics metafile using the PDF file format.

  • An application can create an offscreen pixel map and a bitmap graphics context to draw on it.

  • You can use a Quartz object known as a CGLayer to cache drawings offscreen.

The discussion of creating Quartz metafiles is deferred until Chapter 14, "Working with PDF." This chapter concentrates our attention on the other two techniques on this list.

Quartz has always had the ability to create a graphics context capable of drawing into a bitmap. Your application can create a pixel map of its own and ask Quartz 2D to create a graphics context that draws on that pixel map. This technique is very similar to drawing in an offscreen GWorld with QuickDraw or an offscreen DC with GDI.

CGLayers are a new concept that Apple has added to Mac OS X 10.4 Tiger. A CGLayer is a fixed-size, offscreen environment for caching a graphic. Redrawing the layer is much more efficient than redrawing the graphic with primitive routines. CGLayer is the preferred offscreen representation for a graphic you plan to cache off and draw to the screen many times.

Each of these schemes has advantages and disadvantages. In the sections that follow we do our best to outline the basic techniques and offer the information you need to select the technique that most closely matches your needs.


Using a CGBitmapContext

The first discussion point here is the technique of using a CGBitmapContext for offscreen drawing. Quartz 2D can create graphics contexts on just about any pixel buffer. Just as when working with CGImages, an application owns the memory that the context draws into. Given a block of memory and a series of parameters that describe how the pixels should be laid out in that memory, the routine CGBitmapContextCreate builds a drawing context. The result is "just another CGContextRef" of all of the drawing techniques described in this book that can be applied to it.

Given that Quartz does not own the pixel buffer, once the application has finished drawing into the context, it is free to use the pixels in any way. If the offscreen image represents a cached graphic, a common technique is to create a CGImage from the pixel buffer as described in Chapter 8, "Image Basics." Drawing that image will reproduce the graphic on another context. This same CGImage could be written to a file using the processes described in Chapter 9, "Importing and Exporting Images," as well. The application might generate a histogram of the image using the routines in vImage or even swizzle the pixels with custom code. The possibilities are limitless.

Creating an Offscreen Bitmap

Allocating the pixel buffer can be as easy as calling the canonical dynamic memory allocation routine, malloc. The pixel buffer remains the responsibility of the application. In particular the application must make sure that the pixel buffer exists as long as the context is attached to it. The program is also responsible for releasing the pixel buffer after it is no longer needed.

After allocating the memory, Quartz 2D also has to know how the application wants to organize the pixels in the memory space. In the discussion of CGImages in Chapter 8, the text covered the parameters that Quartz 2D uses to describe any pixel buffer that contains image data. Core Graphics uses the same fields to describe the buffers for bitmap contexts. The CGBitmapContextCreate routine accepts parameters that describe the pixel format, size, and color environment of the buffer and returns a CGContext that draws on the pixel buffer.

This technique has been used several times in the code samples of previous chapters. For example, the full source of the Image I/O Export sample in Chapter 9 creates an offscreen bitmap and draws a simple graphic on it. That part of the code was not examined in Chapter 9, but the code that creates the offscreen context is part of the CreateCGImageRef routine in that sample. That routine is shown in Listing 12.1.

Listing 12.1. The CreateCGImageRef Routine from the Image I/O Export Sample

CGImageRef CreateCGImageRef()
{
    // Were going to draw our image into an offscreen bitmap
    // and then use that bitmap to create a CGImage.
    const int kBytesPerPixel = 4;
    const CGSize imageSize = { 512.0, 512.0 };

    // calculate 16 byte aligned row bytes
    unsigned long rowBytes = ((((unsigned int)imageSize.width *
            kBytesPerPixel) + 15) / 16) * 16;
    unsigned long dataLength = (unsigned int)imageSize.height *
            rowBytes;

    // allocate space for the image buffer
    UInt8 *imageBuffer = (UInt8*) malloc(dataLength);

    // we'll be using gray and rgb colors
    CGColorSpaceRef rgbColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGColorSpaceRef grayColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);

    // Create the context. ARGB, 32 bits per pixel
    CGContextRef bitmapContext = CGBitmapContextCreate( imageBuffer,
            (int) imageSize.width, (int) imageSize.height, 8, rowBytes,
            rgbColorSpace, kCGImageAlphaPremultipliedFirst);

    // Use the offscreen context for drawing and create
    // a CGImage from it in the retVal variable.
    //
    // ... code omitted ...
    //

    // we're done with the bitmap context and can release it
    CGContextRelease(bitmapContext);
    CGColorSpaceRelease(rgbColorSpace);
    CGColorSpaceRelease(grayColorSpace);

    return retVal;
}

The code begins by declaring constants for many of the parameters that describe our offscreen pixel buffer. One of the oddest calculations is the dance done to calculate rowBytes for the buffer. The point of this dance is to create a rowBytes value that aligns each row to a 16-byte boundary. This is a performance technique that is directed at letting the graphics system use AltiVec with the pixel buffer. The graphics libraries will find it easier to employ AltiVec if each row of the buffer is aligned to a 16-byte boundary. The first row of the image will be 16 byte aligned by virtue of the fact that malloc aligns the memory it allocates to 16-byte boundaries. By fixing the rowBytes to be an even multiple of 16 bytes, we ensure that each row of the offscreen is 16-byte aligned. This is not strictly necessary, particularly for such a simple example. We have retained it in this code sample just to demonstrate a potential optimization you may be able to use in your own code.

The code allocates the pixel buffer using malloc and creates a couple of color spaces. The code sample creates the Generic RGB color space for use with the bitmap context. (The grayscale color space is used in drawing code that has been omitted from the listing).

With the attributes of the context in hand, you can create the context itself. The parameters to CGBitmapContextCreate ask this sample to treat the memory as a buffer of 32-bit-per-pixel ARGB pixels. The alpha channel will be the first byte of each pixel, and the channels will be premultiplied with the alpha.

As is the case with CGImages, Quartz 2D supports a broad variety of pixel formats for bitmap contexts. The number of possible pixel arrangements is smaller for bitmap contexts than it is for images. The valid pixel formats are documented in Apple's Technical Q&A 1037, which can be found on the web at the URL

http://developer.apple.com/qa/qa2001/qa1037.html

At the time of this writing, this document was incomplete and did not include updated information for Mac OS X 10.4 Tiger. For example, in Tiger, Apple added support for floating point pixels in graphics context.

For Listing 12.1 the code that actually draws into the offscreen context and creates an image from it has been omitted. After using the context, however, the routine releases the context and the color spaces and then returns from the routine.

You will notice that this routine does not release the memory allocated for the offscreen pixel buffer. This code that uses the offscreen buffer creates a CGImage from it. The pixel buffer must exist as long as that image is around to use it. If that were not a requirement, disposing of the pixel buffer would have been as simple as calling free on it.


Working with CGLayers

The CGLayer opaque data type, and the associated API, was added to the system starting with Mac OS X 10.4 Tiger. If your application must support older versions of the operating system, you will have to choose another offscreen drawing method. However in Tiger and above, CGLayer is usually a better method for creating an offscreen context than creating an offscreen bitmap.

Conceptually, a CGLayer is an offscreen cache for a Quartz 2D graphic. Note that a CGLayer is not necessarily an offscreen bitmap. The way the layer stores its graphic is an implementation detail that is private to Quartz 2D. The only assurance offered by a CGLayer is that the computer can reproduce a graphic as fast or faster when drawing the layer than it could by re-executing the drawing commands that placed the graphic on the layer. This means that a CGLayer is an excellent tool for caching graphics that you plan to use repeatedly.

A CGLayer's graphic need not be cached in a bitmap. The technique that the layer uses to store its graphics is entirely up to Quartz 2D itself. Because a CGLayer chooses its own internal representation, it can select a resolution-independent representation when the layer is drawn on a PDF context or a pixel buffer stored on the video card for layers that draw to the screen. This allows the operating system to tailor the operation of drawing the layer to another context so that it executes as quickly as possible.

Two examples illustrate the kinds of optimizations Quartz 2D might use when drawing with layers. If an application creates a CGLayer to store a graphic off-screen and then draws that layer many times to the display, Quartz 2D can recognize that the same layer is drawn repeatedly. It can move a bitmap of the layer's graphics to the video card and then reproduce that graphic on screen very quickly by simply copying the pixels using the power of the graphics card instead of draining the horsepower of the CPU.

When drawing a layer into a PDF context, Quartz 2D can create a separate PDF object to represent the layer's graphics and then re-use that object many times. Because the PDF commands to draw the object are likely to take up less space than the commands that draw the layer's graphics, the result is smaller PDF files. A PDF viewer is quite likely to be able to cache the layer's graphics again when it displays the file. That means that the PDF itself is quite likely to draw faster than if each copy of the layer graphic was redrawn explicitly.

From the programmer's perspective, CGLayers offer some other advantages over offscreen bitmaps. For example, code that uses CGLayers do not have to contend with a lot of the details surrounding the construction of an offscreen bitmap. CGLayers manage their own internal representations, which means that if the CGLayer uses an offscreen bitmap, the layer itself will manage the life cycle of the block of memory that contains the pixels.

Another example is when using an offscreen bitmap to cache an image, the application can draw the cached image by creating a CGImage on the offscreen pixel buffer and drawing that image on an appropriate destination. Because the pixel buffer behind a CGImage is immutable, however, programs using this technique must be careful to ensure that they do not draw into the pixel buffer while the CGImage exists. To put it another way, the CGImage must be recreated each time the application draws in the pixel buffer. CGLayers do not share this restriction. The contents of a CGLayer can accept new graphics at any time.

In contrast, however, an offscreen bitmap allows the program to directly access and manipulate the pixels that make up its image. A CGLayer may not even have a pixel representation backing it up. CGLayers are the preferred offscreen drawing mechanism in many instances, but if an application wants to access the offscreen's pixel buffer, a CGBitmapContext is the better choice.

Using CGLayers

The process of using a CGLayer is very straightforward. The entire CGLayer interface consists of a little more than a handful of routines.

The first step is to create the CGLayer itself. Layers have a fixed size, and they are associated with a particular graphics context. This is the graphics context that the layer will draw on most efficiently. In the second step, the application asks the layer for a graphics context that it can use to draw onto the layer itself. Quartz 2D records drawing commands sent to this context and decomposes them into a convenient representation that it can efficiently replay later. Once the layer has a graphic, drawing the layer will reproduce that graphic quickly. Usually the layer is drawn on to the same context that the program supplied when it created the layer.

Creating a CGLayer

The routine that creates CGLayers is CGLayerCreateWithContext. This routine takes three parameters. The first parameter is an already existing graphics context. Quartz 2D uses this context to obtain hints about how it should construct the layer. The library will choose a representation for the layer's graphics cache so that the layer's graphics can be redrawn on the source context as quickly as possible. The second parameter is a CGSize. This size establishes the bounds of the layer's drawing context. The last parameter is a CFDictionary that may allow you to tailor the layer further in the future. In Tiger's incarnation of the CGLayer API, Quartz does not use this dictionary, and this parameter should be NULL.

The context that you pass to CGLayerCreateWithContext only provides information that helps initialize the layer. When the layer is drawn to any context with the same characteristics, Quartz 2D will be able to draw the layer as quickly as possible. If you draw that layer to a different context, however, Quartz 2D will do its best to reproduce the graphic, but there may be a loss in terms of drawing performance or image quality.

Drawing into a CGLayer

The routine CGLayerGetContext returns a graphics context that Quartz 2D can use to draw on a layer. This is not the same context that was originally passed to CGLayerCreateWithContext. Like the other contexts, the one returned by this routine starts out with its origin in the lower left corner of the layer. The size of the drawing surface is the same as the size passed as a parameter when the program created the layer.

Drawing the Layer's Graphic Elsewhere

The CGContext interface includes two routines that draw the contents of a layer onto their graphics devices. CGContextDrawLayerAtPoint asks the context to draw the layer with its lower left corner coincident with a particular point in user space. CGContextDrawLayerInRect is similar with the obvious exception that it scales the layers graphic to fit into a particular rectangle.


A Layer Drawing Example

The sample code for this chapter demonstrates the technique of creating a simple layer and using that layer as part of a more complex graphic. The image that this code creates is illustrated in Figure 12.1.

Figure 12.1. A Complex Pattern Composed from a Single Layer

The graphic in Figure 12.1 is drawn by repeating a single motif, a star in front of a circle. To create the graphic, the code draws the star and circle onto a layer. It then draws that layer repeatedly while modifying the rotation and scale of the drawing context to create the final image. The routine that makes use of the layer is given in Listing 12.2.

Listing 12.2. Main Drawing Routine of the Star Pattern

void DrawStarBackground(CGContextRef inContext, CGRect bounds)
{
    CGLayerRef starLayer = CreateStarLayer(inContext);

    CGContextTranslateCTM(inContext,
            bounds.size.width / 2,
            bounds.size.height / 2);

    for(float scale = 100; scale > 10; scale -= 10) {
            CGContextSaveGState(inContext);

            for(short ctr = 0; ctr < 20; ctr++)
            {
                   CGContextDrawLayerAtPoint(inContext,
                      CGPointMake(0, 70), starLayer);
               CGContextRotateCTM(inContext, DegreesToRadians(18));
            }

            CGContextRestoreGState(inContext);

            CGContextScaleCTM(inContext, 0.8, 0.8);
            CGContextRotateCTM(inContext, DegreesToRadians(9));
    }

    CGLayerRelease(starLayer);
}

The routine begins by calling the CreateStarLayer. This routine creates the CGLayer object and draws the circle and star graphic onto it. The routine accepts the context that will hold the final graphic as a parameter.

After moving the origin to the center of the drawing area, the code enters a series of nested loops that adjust the transformation of the context. The meat of the routine is the call to CGContextDrawLayerAtPoint. Each call draws a single instance of our pattern motif in the context. After leaving the loops, the code releases the layer.

The CreateStarLayer routine is given in Listing 12.3.

Listing 12.3. Creating the Layer with the Star

CGLayerRef CreateStarLayer(CGContextRef destinationContext)
{
    CGColorSpaceRef grayColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
    float grayColor[] = { 0.6, 1.0 };
    float blackColor[] = { 0.0, 1.0 };

    // Create the layer and extract a drawing context from it
    CGLayerRef layer = CGLayerCreateWithContext(destinationContext,
           CGSizeMake(100, 100), NULL);
    CGContextRef layerContext = CGLayerGetContext(layer);

    // Setup the drawing environment
    CGContextSetStrokeColor(layerContext, grayColor);
    CGContextSetFillColor(layerContext, blackColor);
    CGContextSetLineWidth(layerContext, 2.5);

    // Adjust the origin to the center of the layer
    CGContextSaveGState(layerContext);
    CGContextTranslateCTM(layerContext, 50, 50);

    // Draw a circle
    CGContextAddArc(layerContext, 0, 0, 12.36, 0, 2 * pi, true);
    CGContextStrokePath(layerContext);

    // Draw a star
    DrawStar(layerContext, 20);
    CGContextFillPath(layerContext);

    // Clean up and return
    CGContextRestoreGState(layerContext);
    CFRelease(grayColorSpace);
    grayColorSpace = NULL;

    return layer;
}

This routine creates the layer and draws the star motif on it. The routine begins by setting up a few colors. Immediately after that, the code calls CGLayerCreateWithContext to create the layer that is the focus of this sample. The code uses CGLayerGetContext to get the CGContext for the layer and uses that context in a sequence of drawing commands to create the graphic on the layer. Note that the code does not own the layer's context and does not need to release it. After drawing on the layer, the code cleans up a few loose ends and returns the layer to the caller.

This chapter introduced two of the offscreen graphics techniques built into Quartz 2D. Choosing the correct offscreen drawing mechanism can change depending on what that offscreen graphic will be used for. Applications that want to cache off graphics and draw them to a context quickly, or draw the same graphic many times, can store that graphic in a layer. If the application wants to access the pixels of the offscreen graphic, or export it into a file, then a bitmap graphics context is the proper choice.


Chapter 13. Shadings and Patterns

The shadings and patterns in Quartz 2D are powerful tools for creating popular graphic effects. The first, shadings, allows you to create a smooth blend of color based on a function that you specify. Figure 13.1 presents examples of several shadings.

Figure 13.1. Shading Examples

Figure 13.1 uses some paths as clipping areas and drawn shadings inside of them. Out of necessity, all of the shadings in Figure 13.2 use grays. They could, just as easily, be in any color space that Quartz 2D supports.

Figure 13.2. An Example of a Pattern and Its Cell

A pattern is an efficient way to draw a single, repeated image. Your application draws a single, rectangular cell and Quartz 2D will tile that pattern on the drawing plane for you. You can transform the cell so your patterns can tile with just about any parallelogram. Figure 13.2 shows a rectangle filled with a simple pattern and the cell that created it.

Quartz 2D can generate the same effects that result from drawing patterns and shadings in other ways. An axial (a.k.a. linear) shading might be approximated with a series of multi-colored lines or rectangles. Careful use of transformations and a lot of drawing could easily replace a pattern. However, Quartz can draw shadings and patterns much more efficiently than either of these approximations.

For example, reproducing a pattern by drawing hundreds of copies of a graphic into a PDF context would lead to each copy adding to the size of the PDF. The PDF specification defines its own pattern primitive, which allows it to store a single copy of the pattern cell and some information about how to apply that cell to create the pattern. When drawing shading with lines or rectangles to a bitmap display, the drawing will likely show obvious bands of color instead of a smooth gradient. Quartz 2D can draw the shading at the pixel level and make the colors change smoothly.

This chapter explores both shadings and patterns and examines how to create the Core Graphics objects that represent these drawing elements, as well as discusses some of the performance issues associated with using them.


Shadings

Shadings are much easier to understand than they are to describe. The shadings in Figure 13.1 should give you some idea of what they look like. In general shadings are a smoothly blended field of color. Quartz 2D supports two shapes for the fields, axial and radial. An axial shading provides linear bands of color that run perpendicular to a line. Radial shadings are formed as smooth blends between two circles. Figure 13.3 shows the anatomy of linear and a radial shadings.

Figure 13.3. Anatomy of Axial and Radial Shadings

The shape of the axial shading on the left is defined by the start and end points of the line indicated. Note that the bands of color extend to the edges of the context perpendicular to the defining line. The radial shading on the right is defined by two circles. The colors of the shading are drawn as if the circle were blended from the start to the end. The circular shading shown is a bit unusual. Most of the time, the smaller circle would be inside the larger one, but Quartz is not limited to that condition alone.

In both shadings, Quartz 2D obtains the colors for the shading from the application. The library passes the function a parameter that indicates a position between the starting point and the ending point of the blend that it needs the color for. The function returns the color that Quartz should use in the blend at that position.

The process of creating shading is relatively straightforward. An instance of the CGFunction abstract data type represents the color function for the shading. This object becomes part of an instance of the CGShading opaque data type. That shading object also encodes information about the shape of the shading and the locations and sizes of its starting and ending shapes. Drawing the shading is as easy as calling CGContextDrawShading and passing the shading object as a parameter.

Creating the Color Function

The first step in creating shading is defining the color function. Unfortunately, if you read the description of the CGFunction opaque data type in the header file, you may find yourself lost in a sea of mathematical rigor. The actual idea that a CGFunctionRef embodies is actually pretty simple.

CGFunction is actually a generic mechanism for representing any function that takes an arbitrary number of floating point inputs and returns an arbitrary number of floating point outputs. When you create a CGFunction object, you indicate how many inputs and how many outputs your function will have. You also provide valid ranges for the input and output values.

The part of the CGFunctionRef that performs the color calculations is a callback routine in your application that has the following signature:

void CGFunctionEvaluateCallback(void *info, const float *in, float *out)

The first parameter to this routine is a block of arbitrary information that your application defines for its own purposes while evaluating the function. The second and third parameters represent C-style arrays of floating point values. These arrays are where your function receives its input values and stores the results of the function. In the case of the color function for a shading, the function you create takes a single input, the position between the start and end of the blend, and returns the components of a color. The number of color components the function must return depends on the color space the shading draws in. For example in an RGB shading, the function will return four values, three for the color channels and one for the alpha value.

Shadings can have partially transparent areas if the color function simply specifies an alpha value in the color returned by the color function. Unfortunately, many printers have trouble printing graphics containing alpha channels, including shadings. If you run into this problem in your own application, you can work around the problem by drawing the shading into an image and drawing that image in the place of the shading. Unfortunately, this representation of the shading will draw with a fixed resolution.

If the function accepts a block of user data, the CGFunction object can keep track of another callback to dispose of the data when the CGFunction is no longer needed. That callback has the signature

void CGFunctionReleaseInfoCallback(void *info)

The system will call not call this routine until it destroys the CGFunction object. This gives the application the opportunity to clean up any resources stored in that data.

All of these input values come together in a call to CGFunctionCreate. Listing 13.1 shows how you might create a CGFunction for supplying the color values of an RGB shading.

Listing 13.1. Creating a CGFunction for RGB Shadings

// Tell the OS that our function expects one input value,
// in the domain [0,6], and returns values in the range [0,1]
const float kValidDomain[2] = { 0, 6 };
const short kChannelsPerColor = 4; // R, G, B, A
const float kValidRange[kChannelsPerColor][2] = {
                           { 0, 1 },
                           { 0, 1 },
                           { 0, 1 },
                           { 0, 1 } };

CGFunctionCallbacks kRGBShadingCallbackRoutines =
{
    0,                     // Callback Structure Version
    RainbowShading,        // Evaluation Callback
    NULL                   // Release User Data Callback
};
CGFunctionRef rainbowShading =
    ::CGFunctionCreate(
           NULL,
           1,
           kValidDomain,
           kChannelsPerColor,
           (float *) kValidRange,
           &kRGBShadingCallbackRoutines);

The constants at the top of Listing 13.1 define the logistics of the function you're creating. The function will accept a single input, and kValidDomain defines the valid range for that input. For reasons that will become clear, you need to define the function so that the input extends from 0 at the start of the shading to 6.0 at its end. As mentioned previously, the function will return four output values representing an RGB color with Alpha. The kValidRange array provides the valid ranges for each output value. Because these represent the color components of a Quartz color, they fall in the range 0 to 1. The callbacks passed to a CGFunction are collected into a CGFunctionCallbacks structure. The first field of this structure is a version placeholder that allows Apple to change the definition of this structure in the future. In this case structure version 0 is being used. The next field is the function evaluation callback. RainbowShading is the name of a routine that computes the shading color. This shading function doesn't pass application data to the evaluation routine, so the callback that releases the info data structure is not used.

The routine CGFunctionCreate builds the final object from all of these elements. The first parameter to CGFunctionCreate is the application-specific information pointer. Because it's unused, NULL is passed. The argument is the number of input parameters the function expects and the valid range for those parameters. Following this, the routine call specifies the number of output values and their valid domains. The final parameter, of course, is the evaluation routine.

The CGFunctionRef just created uses the RainbowShading callback to evaluate the function. That routine is given in Listing 13.2.

Listing 13.2. A Sample Evaluation Callback

void RainbowShading(void *info, const float *in, float *out)
{
   // colors of the knots in parameter space
   static float const shadingColors[7][4] = {
           { 1.0, 0.0, 0.0, 1.0}, // red
           { 1.0, 0.5, 0.0, 1.0}, // orange
           { 1.0, 1.0, 0.0, 1.0}, // yellow
           { 0.0, 1.0, 0.0, 1.0}, // green
           { 0.0, 0.0, 1.0, 1.0}, // blue
           { 0.5, 0.0, 1.0, 1.0}, // indigo
           { 0.5, 0.0, 0.5, 1.0}, // violet
   };

   // Retrieve the function's input (a single float in the
   // domain [0,6])
   float parameterValue = *in;

   // Use the input to find the index of the color on the left side
   // of the current sub-interval
   int colorIndex = (int)parameterValue;
   if(colorIndex == 6) {
           colorIndex = 5;
   }

   // We subtract of the integer part and get a sub-interval scale
   // between 0 and 1.0
   float t = parameterValue - colorIndex;

   // Use all those calculations to create a color value that is the
   // result of this function. We copy each component into the function
   // result array.
   for(unsigned short ctr = 0; ctr < 4; ctr++) {
           out[ctr] =
                   (1.0 - t) * shadingColors[colorIndex][ctr] +
                   (t) * shadingColors[colorIndex + 1][ctr];
   }
}

This routine defines a color sequence that has all the colors of the rainbow. The routine begins with an array of color values that will make the shading. The input parameter will be between 0 and 6 because that's the way the function was set up when it was created. This value is used to select the interval inside of the color array and the position within that interval that the code is looking for. The loop at the end of the array calculates an interpolated value for each component of the color to be returned. This particular shading example uses a fixed array of colors, if the array of source colors was passed in as part of the info parameter, but the code could create many function objects that use the same callback to generate shadings with many different colors.

When Quartz sends your shading object to a PDF, it tries to convert your shading function into a representation that can be stored in the file directly. Unfortunately, it is possible for your application to create functions that are too complex for this mechanism. If you are drawing a shading into a PDF and find that the colors in the PDF do not match your color function, you may have a function that is too complex. If you run into this problem, one potential solution is to render the shading into an image or CGLayer and then draw that into the PDF.


Creating Shadings

With the CGFunction in hand, the next step in drawing a shading is to create the shading object itself. Quartz 2D includes separate routines for creating axial and radial shadings. The primary difference in the parameters between the two routines is the geometrical information that defines where the shading will draw in the context.

There is another option that affects the way Quartz 2D draws shadings that has not yet been discussed. As part of creating a shading, you indicate whether or not you want the colors of the shading to extend beyond its end points. You extend the blend on the starting and ending sides independently. Figure 13.4 shows the effect of extending colors for both shading shapes.

Figure 13.4. Extending Axial and Radial Shadings

The shadings in Figure 13.4 are drawn on top of a context that is filled with a checkerboard pattern. The shadings on the left do not have the shadings extended. The shadings stop at the bounds of their defining geometry. The shadings on the right include extensions. The colors at either end of the shading extend as far as they can filling the context and obscuring the checkered background.

Axial Shadings

As seen earlier, Quartz defines the geometry of an axial shading with a line segment that runs perpendicular to the colors in the shading. Correspondingly, when you create the shading, you supply the endpoints of that line. In addition, you define the color space that the shading will use and the function that returns colors in that space. A complete example of creating a shading is shown in Listing 13.3.

Listing 13.3. Creating an Axial Shading

CGRect shadingStage = mediaBox;
CGPoint startPoint = CGPointMake(
    shadingStage.origin.x + (shadingStage.size.width / 4),
    shadingStage.origin.y + (shadingStage.size.height / 4));
CGPoint endPoint = CGPointMake(
    CGRectGetMaxX(shadingStage) - (shadingStage.size.width / 4),
    CGRectGetMaxY(shadingStage) - (shadingStage.size.height / 4));

CGColorSpaceRef colorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);

CGShadingRef shadingObject =
    CGShadingCreateAxial(colorSpace, startPoint,
                           endPoint, rampShading,
                           false, false);

CGColorSpaceRelease(colorSpace);
colorSpace = NULL;

This code creates the unextended axial shading in Figure 13.4. The code begins by selecting a start and end points for the shading based on the bounding box of the illustration. It then creates the generic gray color space used by this particular shading. The routine that creates the shading object itself is CGShadingCreateAxial. The color space and endpoints are accompanied by the CGFunctionRef, named rampShading, and two boolean parameters. The booleans ask Quartz not to extend the colors of the shading.

Radial Shadings

Creating a radial works the same way as creating an axial shading. The only difference is that the defining geometry is two circles instead of a single line. Each of the circles is defined by a center point and a radius. Listing 13.4 is the code that created the shadings for the unextended radial portion of Figure 13.4.

Listing 13.4. Creating a Radial Shading

CGColorSpaceRef colorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
CGShadingRef shadingObject =
    CGShadingCreateRadial(
           colorSpace,
           CGPointMake(50, 50), 10,
           CGPointMake(50, 50), 50,
           rampShading, false, false);

This listing is much shorter than the axial sample, primarily because the geometry is easier to create. The two circles that define the shading share a center point, and only their radii change. The starting circle uses a radius of 10, while the ending circle uses a radius of 50. Beyond that, the parameters for CGShadingCreateRadial are identical to those used in Listing 13.3.

Drawing Shadings

Drawing a shading is a very simple operation after you've created it. To draw a shading you simply call the CGContext routine CGContextDrawShading. This routine takes two parameters, a context, and the shading you want to draw into it.

If you look at the shadings in Figure 13.4, you will notice that they fill the context, particularly those where you've indicated that the colors should extend beyond the edges of the shading's geometry. Unless you are using shading as a background, you will probably set up a clipping path first and then draw the shading inside of that.


Patterns

In many ways, creating patterns is similar to creating shadings. Quartz 2D patterns are constructed from a single seed image called a pattern cell. The pattern is an efficient mechanism for repeating that cell on a regularly spaced grid to color an area of the graphics context. The application controls the contents of the cell and the spacing of the cells in both the horizontal and vertical directions. Patterns are drawn in their own coordinate system, and you can also control the mapping between that coordinate system and user space. Your pattern cells can contain any Quartz 2D graphics including images.

Quartz 2D allows you to create two different kinds of patterns, a colored pattern and a stencil pattern. The computer can draw a colored pattern with no additional information other than the colors specified by the cell's image. With a stencil pattern, the pattern cell defines a shape but contains no color information. In this sense it is very much like a resolution independent image mask. When drawing a stencil pattern, your application must supply a color for the pattern to be drawn with.

One challenging thing to grasp about patterns is the fact that Quartz 2D treats a pattern as a custom color in a very specialized color space when drawing. In that sense, a pattern is a lot like the polka-dotted paint that cartoon characters sometimes use. Regardless of where they apply that paint, the polka dot pattern is always reproduced faithfully. Because Quartz 2D treats patterns as special kinds of colors, Quartz 2D can apply them when stroking and filling graphics.

This chapter explores all these characteristics of patterns. You will learn how to use Quartz 2D to create patterns and use them in your drawings.

Cells, Spacing, and Transformations

A cell is simply a rectangular drawing space that contains Quartz 2D graphics. For colored patterns, the cell's graphic will define both color and shape information. For a stencil pattern, the cell's graphic includes shapes but no colors. Figure 13.5 contains a very simple example of a cell that contains some basic line art.

Figure 13.5. A Simple Cell

In this figure, and in some of the figures to follow, a dotted line has been added that shows where the boundary of the cell is. In this case the bounding rectangle of the cell is 48 points square. For this example, and cell, a color cell is used.

If you asked Quartz 2D to draw this cell in a pattern with a cell spacing of 48 points in the x and y directions, the results might look something like this

Figure 13.6. Pattern Drawn with 48 Point Spacing

The left side of this illustration shows the cells with their boundaries marked. The rectangle on the right shows how Quartz 2D might draw this pattern when it is used to fill a rectangle.

One of the drawing options you can change when you create a pattern is the spacing of the cells. You can change the horizontal and vertical spacing of the pattern's cell independently. If you ask Quartz to draw the pattern with a horizontal spacing of 72 points and a vertical spacing of 60 points, the resulting pattern might look something like this

Figure 13.7. Pattern Drawn with a Different Spacing

The computer takes the additional spacing and uses it to insert blank space between the pattern cells.

In this example, the spacing is the same size as the cell's height and width, but you could just as easily use values that are smaller. If the graphics of adjacent cells overlap, Mac OS X will still draw the pattern to the best of its ability. However, the order in which Quartz draws the pattern is undefined. The effect you get from drawing an overlapping pattern to the screen may not match the effect of drawing that pattern to a PDF or the printer.

In addition to changing the spacing of a pattern, you can also apply a transformation to it. The tiled graphic of a pattern is drawn in its own coordinate space, the pattern space. A Quartz 2D application can supply an affine transform that defines how the computer maps the tiled graphic from pattern space to user space. You can use this to achieve patterns where the tiling consists of just about any parallelogram. If you draw the tiling of Figure 13.6 into a rectangle, applying a transformation between pattern space and user space at the same time, the results might look something like Figure 13.8.

Figure 13.8. The Pattern Drawn with a Transformation

When you draw a pattern with a transformation, the mathematics involved can introduce small errors into the spacing of a pattern, particularly when working with bitmap contexts. Quartz can compensate for these spacing errors by slightly warping the cells in-place within the pattern. Your application has some control over whether or not the computer makes this trade-off. Quartz 2D defines three constants that affect the spacing versus shape trade-off.

kCGPatternTilingNoDistortion - This option asks Quartz to keep the shape of the pattern cells consistent as possible. This option comes with a cost of the pattern spacing. Some cells may be off by as much as a pixel in either direction.

kCGPatternTilingConstantSpacingMinimalDistortion - With this option, the computer keeps the spacing of cells constant but might have to distort some of the cells (again, by as much as a pixel in either direction).

kCGPatternTilingConstantSpacing - This option is very similar to the previous one. This option asks Quartz 2D to keep the spacing constant, but it does not perform the rigorous calculations needed to keep the distortion of the cells to a minimum. As a result, this option will allow the computer to draw your pattern more quickly, but individual cells may be distorted more than if you had used kCGPatternTilingConstantSpacingMinimalDistortion.

Colored and Stencil Patterns

In introducing patterns the difference between colored and stencil patterns were briefly discribed. A color pattern is one where the color of the cell is defined as part of that cell. In a stencil pattern, your cell defines the shapes that you want to draw, but all of the shapes in the cell are drawn in the same color. You supply the color when you draw the pattern, not when the cell is created.

The cell defined in Figure 13.5 is a color pattern cell. The cell itself contains two colors, a gray and black. The figures that follow show the effects of using this cell in a colored pattern.

Figure 13.9 shows a stencil cell and several rectangles that are filled with a stencil pattern using that cell. In each rectangle is drawn the stencil pattern with a different color. Each rectangle was drawn using the same pattern, with just a different color supplied each time pattern was drawn.

Figure 13.9. A Stencil Pattern Drawn with Several Colors

Previous Page
Next Page


Creating Patterns

Quartz 2D keeps track of all the information needed to draw a pattern in an instance of the CGPattern opaque data type. To draw a pattern, you crate an instance of this opaque data type by calling CGPatternCreate and supplying all the relevant options.

The Pattern Cell Callback

A callback routine provides the graphics for a pattern cell. The callback has the routine signature

void DrawPatternCellCallback(void *info, CGContextRef cgContext);

Like the callback for a CGFunction, the pattern cell callback accepts a pointer to a block of data that the application can use for its own purposes. The second parameter is a CGContext. The callback draws the graphics of the cell into that context it receives in the second parameter. As is the custom in Quartz 2D, when the system passes in the context, the origin is at the lower left corner of the cell. As usual, however, transformations can relocate the origin to make it easier to draw into the cell.

The pattern also can store a pointer to the routine that disposes of the application specific info when the pattern gets destroyed. The signature of that function is

void ReleasePatternInfoCallback(void *info);

When you create a CGPattern, you provide these callbacks to Quartz 2D by packing the routine pointers into an instance of the CGPatternCallbacks structure. This structure also has a field that indicates the version of the structure you are using.

Putting It Together

Listing 13.5 contains the code used to create the pattern drawn in Figure 13.2 at the beginning of the chapter.

Listing 13.5. A CGPatternCreate Sample

const float kPatternWidth = 100;
const float kPatternHeight = 100;
const bool  kPatternIsColored = true;

const CGRect patternBounds =
        CGRectMake(0, 0, kPatternWidth, kPatternHeight);
const CGPatternCallbacks kPatternCallbacks =
{
        0,
        DrawCirclesAndTriangle,
        NULL
};

CGAffineTransform patternTransform = CGAffineTransformIdentity;
patternTransform = CGAffineTransformMakeRotation( -pi / 4.0);
patternTransform = CGAffineTransformScale(patternTransform,
        0.33333, 0.5);
patternTransform = CGAffineTransformRotate(patternTransform,
        pi / 4.0);

CGPatternRef pattern = CGPatternCreate(
        NULL,
        patternBounds,
        patternTransform,
        kPatternWidth, // used as horizontal spacing
        kPatternHeight,// used as vertical spacing
        kCGPatternTilingNoDistortion,
        kPatternIsColored,
        &kPatternCallbacks);

There are many constants in the code above which can lead to the impression that it is more complex than it is. The focus of the listing is the call to CGPatternCreate at the end. The first parameter is the application specific info data that Quartz will pass to your pattern callback. That info is not used in this sample, so NULL is passed in. The second parameter is a rectangle that defines the bounds of the pattern cell. The third parameter is the CGAffineTransform that maps the pattern's graphics from pattern space to user space. In this case a simple transformation that skews the graphic slightly is passed.

The pattern width and pattern height are used as the spacing parameters. This corresponds to the options used to draw Figure 13.6. You pass the spacing constant that asks Quartz to ensure your shapes are not distorted, at the expense of potential spacing inaccuracies.This pattern procedure is going to draw a colored cell, so you create a colored pattern instead of a stencil pattern.

The last parameter, of course, is the structure that describes the pattern callbacks. The routine that draws the cell in this case is DrawCirclesAndTriangle.


Drawing with Patterns

Quartz 2D treats patterns as special types of color. Once you have created a pattern, setting up a context to draw it is very similar to setting up the context to draw a color. Patterns "live" in special pattern color spaces. The first step in drawing a pattern is to create a pattern color space. You can use that pattern color space as the stroke or fill pattern in the current context. From that point, using the pattern is as easy as stroking or filling the paths you want to draw.

Pattern Color Spaces

To create a pattern color space, you call the routine CGColorSpaceCreatePattern. This routine accepts a single parameter. For colored patterns, you should pass NULL as the base color space. If you are using a stencil pattern, you should pass a traditional color space like Generic RGB or the color space of the main display. When you draw the stencil pattern you will specify the color with which Quartz should draw the stencil by specifying the components of a color from this color space.

After creating the pattern color space, your code can use CGContextSetFillCol orSpace and CGContextSetStrokeColorSpace to indicate that you want to use a pattern for fills or strokes, respectively.

Setting the Current Pattern

After you have the color space settings of the context set up, you can set the current fill pattern for the context by calling CGContextSetFillPattern and the current stroke pattern with CGContextSetStrokePattern. Each of these routines accept the context you wish to change, a CGPatternRef, and an array of floating point values.

The values you supply in the floating point array depend on what type of pattern you are working with. If you are working with a colored pattern, then you should pass in a single floating point value that indicates the alpha value you want to use for your colored pattern. For example, if you pass in 0.5 as the alpha value, the computer will draw your pattern with 50% translucency.

If you are working with a stencil pattern, the floating point array is where you specify the color Quartz should apply to the stencil. You will need to specify the components of a color in the color space you provided as a base to the pattern color space you created. For example, if you indicated that your pattern's base space was Generic CMYK, your floating point array would have five values in itone for each of the four color components and one for the alpha value.

Drawing Patterned Graphics

After calling CGContextSetFillPattern or CGContextSetStrokePattern, any strokes or fills that your code calls for will draw with the current pattern. Listing 13.6 ties together the entire process of drawing with a colored pattern. This is part of the code used to draw Figure 13.2.

Listing 13.6. Drawing a Colored Pattern

float opaque = 1.0;
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(inContext, patternSpace);
CGContextSetFillPattern(inContext, pattern, &opaque);
CGContextAddRect(inContext, bounds);
CGContextFillPath(inContext);
CGColorSpaceRelease(patternSpace);
CGPatternRelease(pattern);

You are drawing a colored pattern so you pass NULL to CGColorSpaceCreatePattern. You then tell the computer that we are interested in using the color space you've created as the fill color space. The pattern passed to CGContextSetFillPattern is the same pattern created in Listing 13.5. To use the pattern, you simply add a rectangular path to the current context and then call CGContextFillPath. Quartz will fill the path with your pattern. Upon returning, you clean up the pattern space and the pattern.

Listing 13.7 is a block of code that draws a stencil cell in seven different colors. This listing was taken from the Patterns code sample from the code sample CD.

Listing 13.7. Drawing a Stencil Pattern in a Variety of Colors

// To draw the pattern we need a pattern color space
CGColorSpaceRef baseColorSpace = CGColorSpaceCreateWithName(
        kCGColorSpaceGenericRGB);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(
        baseColorSpace);
CGColorSpaceRelease(baseColorSpace);
baseColorSpace = NULL;

CGContextSetFillColorSpace(inContext, patternSpace);
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;

static float const rainbowColors[7][4] = {
        { 1.0, 0.0, 0.0, 1.0 }, // red
        { 1.0, 0.5, 0.0, 1.0 }, // orange
        { 1.0, 1.0, 0.0, 1.0 }, // yellow
        { 0.0, 1.0, 0.0, 1.0 }, // green
        { 0.0, 0.0, 1.0, 1.0 }, // blue
        { 0.5, 0.0, 1.0, 1.0 }, // indigo
        { 0.5, 0.0, 0.5, 1.0 }, // violet
};
// Add a path to fill with our pattern and fill it.
float rectHeight = bounds.size.height / 7;
for(short ctr = 0; ctr < 7; ctr++) {
        CGRect patternBounds = CGRectMake(
                       bounds.origin.x,
                       bounds.origin.y + ctr * rectHeight,
                       bounds.size.width,
                       rectHeight);

        CGContextSetFillPattern(inContext,
               pattern, rainbowColors[ctr]);
        CGContextAddRect(inContext, patternBounds);
        CGContextFillPath(inContext);
}

CGPatternRelease(pattern);
pattern = NULL;

This code listing begins much like the previous one, with the construction of a pattern color space. In this case, because you are using a stencil pattern, you need to provide a base color space (Generic RGB has been chosen here). As before, after creating the pattern color space, you set up the context to use that the fill color space for the port.

The large array in the middle of the code example simply defines seven RGBA colors (the same ones used in the shading example). The loop at the bottom is responsible for drawing the pattern in a series of seven rectangles. To draw each rectangle, you set the current fill pattern and call CGContextSeFillPattern to pass one of the seven colors as the color the computer should apply through the stencil. The pattern is drawn, then, using CGContextFillPath.


Chapter 14. Working with PDF

Adobe's Portable Document Format (PDF) is the file format that Adobe created to facilitate the distribution of multi-page documents. As seen in Chapter 2, "From QuickDraw to Quartz 2D," Quartz 2D bases its imaging model on the graphics systems of PostScript and PDF. In the early days of PDF, a program called the Distiller would execute PostScript code and collect all the drawing commands into a PDF file. When a PDF viewer played back those commands, the user would see all of the drawing that would have been done by the original PostScript program without having to reinterpret that program itself.

Quartz 2D uses PDF as its metafile format. A metafile captures a series of drawing commands that create an image, rather than the image itself. The PDF files that Quartz generates record the paths, images, and text that an application draws into a PDF graphics context. This preserves the color fidelity, resolution independence, and other hallmark features associated with Quartz 2D graphics.

Not every PDF file is a Quartz 2D metafile, but Quartz 2D can draw the contents of PDF files that are not metafiles. This is a powerful feature that allows many applications on the system to enjoy the benefits of PDF's vector graphics with the investment of a few lines of code.

The metafiles generated by Quartz 2D are PDF files, but Quartz is not a generalized tool for writing PDF files. Core Graphics handles many of the graphics and interactive features that are defined in the PDF spec. For example, the PDF specification includes shadings that you cannot create through Quartz 2D. PDF files also can contain interactive forms. Quartz 2D might display the contents of the forms but does not offer any features for creating those forms.

Quartz 2D does provide some routines that allow you to access the structure and content of a PDF file in its raw form. If you need very low-level access to the contents of a PDF file, you can use the CGPDFScanner API to get at the guts of PDF files. To scan the PDF file, your application registers a series of callbacks that you want Quartz 2D to invoke when it encounters certain PDF operators in a PDF stream. You can then call on Quartz to parse the PDF stream and invoke your callbacks using the CGPDFScanner API. If you want more information on using this mechanism to explore the structure and contents of PDF files, please read the "Parsing PDF Content" chapter of Apple's Quartz 2D Programming Guide.

http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/index.html

Mac OS X includes an additional technology know as PDFKit that allows you to access features of PDF that Quartz 2D does not. Through the PDF kit your application can access and display PDF file annotations and work with the outlines embedded in PDF documents. For more information on PDFKit you can visit the Apple Developer Connection web site.

http://developer.apple.com/documentation/GraphicsImaging/Conceptual/PDFKitGuide/index.html

This chapter examines the facilities in Quartz 2D for creating new PDF documents and drawing existing documents as well as some of the information that Quartz 2D allows you to extract from PDF files.


Drawing PDF Documents

The technique of drawing PDF files in Quartz 2D is very similar to the technique for drawing bitmap images discussed in Chapter 8, "Image Basics." Instead of creating a CGImage, an application must create an instance of the opaque data type CGPDFDocument. The process used to create CGImages and CGPDFDocuments are very similar, though. With a PDF document object in hand, Quartz 2D can retrieve individual pages of the file as instances of the CGPDFPage abstract data type. Drawing that page onto a CGContext is as easy as calling CGContextDrawPDFPage.

Creating a CGPDFDocument

A CGPDFDocument obtains its PDF data from a CGDataProvider. CGDataProviders were first looked at in Chapter 8 when creating CGImages from bitmap data. Just to recap, Quartz 2D can create data providers for any of a number of data sources, and Quartz provides utility routines for creating data providers from popular sources. For example, two popular data sources are blocks of data in memory, which you can access with CGDataProviderCreateWithData, and files on disk you can access with a data provider created with CGDataProviderCreateWithURL. For custom data sources, the library can create a data provider called CGDataProviderCreatewithDirectAccess that uses a set of callbacks to obtain data.

Once you have created a data provider, the routine CGPDFDocumentCreateWithProvider will extract the PDF data and create an CGPDFDocument instance.

The case of reading PDF data from a file is so common that Quartz 2D also offers a utility routine, CGPDFDocumentCreateWithURL. This routine creates a data provider on your behalf and returns a CGPDFDocument that uses that data provider. As with CGImages, you will probably want to pass this routine a URL to a local resource most of the time. Core Graphics can handle URLs for remote files, but it has no mechanism to pass you progress information and errors. To provide a proper user experience with download progress bars and proper error handling, you are probably better off using another Mac OS X mechanism to download the file data.

Retrieving Pages

Quartz 2D does not draw PDF documents; it draws the pages in PDF documents. After creating a PDF document, you will have to extract the pages you want to draw from it. The routine CGPDFDocumentGetNumberOfPages returns the number of pages in a CGPDFDocument. An instance of the CGPDFPage opaque data type identifies an individual page in the document. Calling CGPDFDocumentGetPage retrieves the CGPDFPage from a document by its page number.

Drawing Pages

The CGContextDrawPDFPage method does just as its name implies; it draws a CGPDFPage object into the context. The only parameter to this routine is the CGPDFPageRef it should draw. Quartz 2D draws the page so that the lower left corner of the page's mediaBox attribute is at the origin and aligned to the coordinate axes.

Speaking of the mediaBox, the PDF file format keeps track of several different rectangles, and the mediaBox is just one of them. The mediaBox is the rectangle that defines the physical boundaries of the media (usually paper) that the file's author intended that page to be printed on. For example, if the page was intended for US letter sized paper, then the mediaBox would be 8.5 x 11 inches or 612 x 792 points.

In addition to the mediaBox, Quartz includes routines to retrieve any of these rectangles:

Crop box This is a hint to the program displaying the PDF that the page's image should be clipped to this rectangle. If no crop box is specified in the PDF data, the crop box defaults to the mediaBox.

Bleed box This box has great merit for PDFs used in professional printing but will probably be of little use to applications outside of that realm. The bleed box specifies an area of the page that, when printed, contains important marks about how to process the sheet of paper the page was printed on after it comes off the press. For example, if the PDF contains a graphic that is to be printed on a cereal box, the bleed box might include labels that indicate what month the box was printed, where to cut away the excess cardboard, and where the box must fold to form its final shape. If no bleed box is specified in the PDF data, its value defaults to the crop box.

Trim box The trim box is another professional printing tool. Professional printers will often print their images on large sheets and then cut those sheets down (trim them) to their final size. The trim box represents how much of the page will be left after trimming. Most applications will not need to work with the trim box, but Quartz 2D makes it available if you need it. If the trim box is not found in the PDF data, it also uses the crop box as its default.

Art box Simply put, the art box is that area of the page that the PDF author considers to have meaningful content. Exactly what constitutes the art box is at the discretion of the program that generated the file. This box also defaults to the crop box if it is otherwise unspecified.

Because Quartz 2D always draws the page at the origin when you call CGContextDrawPDFPage, coordinate system transformations are the only way to relocate it on the output device. The routine CGPDFPageGetDrawingTransform simplifies the task of creating an appropriate transformation. To use the routine, you select one of the boxes just described and the rectangle in user space the box will map into when you draw the page. The parameters to the routine can rotate the page by a multiple of 90 degrees, changing landscape pages to portrait orientations and vice-versa. The routine can also preserve the aspect ratio of the PDF box if the transform has a scaling component. Naturally CGPDFPageGetDrawingTransform returns a CGAffineTransform that should be concatenated onto the CTM of a context before a call to CGContextDrawPDFPage.

A PDF Drawing Example

To demonstrate how this process comes together, the following sample draws a collage of the pages in a PDF document. Figure 14.1 shows the effect of running this code on a PDF with three pages.

Figure 14.1. Fanned Pages of a PDF File

[View full size image]

The fan-shaped collage is created by reading a PDF from a file and then drawing successive pages with the appropriate transformations. This code is part of the DrawPDF sample application.

Listing 14.1. Drawing a PDF Document

void DrawTravelBrochure(CGContextRef cgContext, CGRect viewBounds)
{
    float backgroundColor[] = { 0.7, 1.0 };

    CGColorSpaceRef grayColorSpace =
            CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);

    /* draw the background color */
    CGContextSetFillColorSpace(cgContext, grayColorSpace);
    CGContextSetFillColor(cgContext, backgroundColor);
    CGContextAddRect(cgContext, viewBounds);
    CGContextFillPath(cgContext);

    // Adjust the origin for the rest of the image
    CGContextTranslateCTM(cgContext,
           CGRectGetMidX(viewBounds),
           viewBounds.origin.y + viewBounds.size.height / 8.0);

    // Grab a reference to the PDF file
    CFURLRef pdfURL = CFBundleCopyResourceURL(
            CFBundleGetMainBundle(),
            CFSTR("TravelBrochure"),
            CFSTR("pdf"), NULL);

    // Create a CGPDFDocument from it
    CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(pdfURL);

    // Count the number of pages in the document and calculate
    // a rotation angle from it
    size_t numPages = CGPDFDocumentGetNumberOfPages(pdfDocument);
    float angleStep = pi / (3.0 * (float)numPages);

    // Calculate a "nice" size for the page
    for(short pageCtr = numPages; pageCtr > 0; pageCtr--) {
            // Draw the current page
            CGPDFPageRef currentPage =
                    CGPDFDocumentGetPage(pdfDocument, pageCtr);
    // Run a simple calculation to get a suggested drawingRect
    CGRect mediaBox =
            CGPDFPageGetBoxRect(currentPage, kCGPDFMediaBox);
    float suggestedHeight = viewBounds.size.height * 2.0 / 3.0;
    CGRect suggestedPageRect = CGRectMake(0, 0,
           suggestedHeight *
           (mediaBox.size.width / mediaBox.size.height),
           suggestedHeight);

    CGContextSaveGState(cgContext);

    // Calculate the transform to position the page
    CGAffineTransform pageTransform =
           CGPDFPageGetDrawingTransform(
                   currentPage,
                   kCGPDFMediaBox,
                   suggestedPageRect,
                   0, true);
    CGContextConcatCTM(cgContext, pageTransform);

    // Erase a rectangle with a shadow where the page will go
    CGContextSaveGState(cgContext);
    CGContextSetShadow(cgContext, CGSizeMake(8, 8), 5);
    CGContextAddRect(cgContext, mediaBox);
    CGContextFillPath(cgContext);
    CGContextRestoreGState(cgContext);

    // Draw the PDF page
    CGContextDrawPDFPage(cgContext, currentPage);

    // Draw a frame around the page
    CGContextAddRect(cgContext, mediaBox);
    CGContextStrokePath(cgContext);

    CGContextRestoreGState(cgContext);

    CFRelease(currentPage);
    currentPage = NULL;
           CGContextRotateCTM(cgContext, angleStep);
    }
}

The code begins by drawing the gray background using simple line art drawing techniques. It then positions the origin to the center of the drawing area and close to the bottom. This point will become the pivot of the fan.

The sample application stores its PDF in the application's resources. The code retrieves a URL for the file and creates a CGPDFDocumentRef by calling CGPDFDocumentCreateWithURL. The code uses the routine CGPDFDocumentGetNumberOfPages to find out how many pages are in the PDF. The code uses the page count to control the loop that draws the individual pages.

Using the Painter's Model, the loop stacks the pages from the last page to the first one. Inside of the loop, the computer retrieves a page from the document using CGPDFDocumentGetPage. There is a short calculation to determine a "nice" bounding rectangle for the page. The seed of that calculation is the mediabox retrieved by CGPDFPageGetBoxRect.

The drawing routine wants to scale the PDF to fit within the rectangle it just calculated. It uses the routine CGPDFPageGetDrawingTransform to request a transformation to fit the mediaBox to the "nice" rectangle. The CGContextConcatCTM incorporates this transformation into the context.

The short segment of code that follows uses Quartz 2D's drop-shadow capability to draw a gray rectangle with a drop shadow where the computer will draw the PDF page. The CGContextDrawPDFPage routine draws the page's image onto the context, and the drawing ends with a simple frame around the page to emphasize its boundaries.

After drawing the page, the code at the end of the loop simply adjusts the coordinate system for the next page.


Creating PDF Documents

Creating a PDF with Quartz 2D is a straightforward task. The first step is to create a graphics context using the methods of the CGPDFContext opaque data type. Any graphics drawn into this context will be recorded into the PDF file.

The CGPDFContext is different from other graphics contexts. Just as Quartz 2D draws pages of a PDF, not documents, it also draws in pages of a PDF Context and not the context alone. The routine CGPDFContextBeginPage creates a page in the PDF context. After drawing onto that page, a call to CGPDFContextEndPage tells the context that the page is complete.

Another behavior peculiar to the CGPDFContext is the fact that releasing the context is a vital part of the drawing process. Quartz 2D will not finish writing the PDF data collected by a context to its destination until it ensures that all the pages have been added. Releasing the context is the signal that tells Quartz 2D no more graphics are forthcoming. The library will finish generating and writing the PDF data.

Creating PDF Contexts

When reading PDF documents and images, Quartz 2D relies on a CGDataProvider to supply PDF data. When writing PDF data, the library uses an analogous object known as a CGDataConsumer. A data consumer is an abstract representation of some mechanism that accepts a stream of data and (presumably) stores it. The routine CGDataConsumerCreate makes the most generic data, one that invokes callback routines whenever the system generates PDF data. The callback routines can store the PDF data in any way they like.

Most applications will want to store PDF data into a file or into a block of memory. Quartz 2D provides two utility routines that create data consumers for each case. CGDataConsumerCreateWithURL allows you to create a data consumer whose destination is any particular URL. If you use a file URL, then Quartz 2D will write the PDF data into a file. You can use network URLs, but as was mentioned before, you will have no way to trap networking errors when Quartz 2D is handling your networking code. If you want to create PDF data in memory, the data consumer API offers the CGDataConsumerCreateWithCFData routine. This data consumer will write the PDF data into an instance of the CFMutableData opaque data type.

One you have a data consumer to catch the PDF data, the routine CGPDFContextCreate returns a new graphics context. In addition to the data consumer, this routine accepts a rectangle and a dictionary as arguments. The rectangle is the default mediaBox for the PDF document. If the mediaBox parameter is NULL, Quartz will use a US Letter page by default. The default page size is 612 x 792 points, or 8.5 x 11 inches.

The dictionary that you pass to CGPDFContextCreate allows you to specify metadata about the PDF file. This metadata is stored inside the PDF so that it is available to other PDF processing systems. Adding dictionary entries with the keys kCGPDFContextTitle, kCGPDFContextAuthor, and kCGPDFContextCreator will add information about the origin of the document. Keys like kCGPDFContextOw nerPassword, kCGPDFContextUserPassword, kCGPDFContextAllowsPrinting, and kCGPDFContextAllowsCopying let you inject access control information into the PDF. There are additional keys that control professional printing and PDF/X options that pre-press applications use to manage PDF documents. All of these keys are documented in the CGPDFContext.h header file and in the developer documentation.

Adding Pages

Quartz 2D has two routines for adding pages to a PDF context, CGContextBeginPage, and CGPDFContextBeginPage. CGContextBeginPage is the older of the two routines and has fewer options than CGPDFContextBeginPage.

As indicated by its name, CGContextBeginPage is a part of the abstract CGCon-text API. This routine takes a rectangle as a parameter. The rectangle represents the mediaBox of the new page. This rectangle supersedes the mediaBox that the context was created with. If the parameter is NULL, page's mediaBox will match the mediaBox of the context.

The CGPDFContextBeginPage offers more control over the attributes of the page it creates. This routine accepts a dictionary of information about the page. One obvious key this dictionary might contain is kCGPDFContextMediaBox. Naturally, this key specifies the mediaBox for the page and overrides the mediaBox supplied by the context. The page information dictionary can also include keys that specify the other PDF page rectangles like the cropBox, or the artBox.

As you finish drawing each page, you should notify Quartz using CGContextEndPage or CGPDFContextEndPage. Be sure to pair each routine with the corresponding function that you used to begin the page. These routines are essential because they give Quartz the chance to update its internal data structures.

Drawing the PDF

Once the context contains a page, the same routines that draw in any other context will generate PDF data. Quartz records and encodes each drawing command into the PDF data that is sent to the data consumer.

There are some simple drawing techniques for PDF contexts that can help Quartz optimize the contents of a PDF file. For example, if the PDF will contain multiple copies of the same image, the resulting file will be smaller if each call to CGImageDraw uses the same CGImageRef. This gives the system the opportunity to take note of the fact that the same image is drawn multiple times. Quartz can optimize the output stream so that only one copy of the image's data will be written out.

In a similar fashion, repeatedly drawing a CGLayerRef into the context allows the system to store a single copy of the graphic in the file and reuse it every time it appears.

Release the Context

After all the pages have been drawn, a call to CGContextRelease prompts Quartz 2D to collect all the information about the pages and write the document's data. As mentioned in the overview at the beginning of this chapter, releasing the context is a vital part of successfully creating a PDF. If the context is not released and the system writes anything to the destination, it might not contain a complete PDF.

An Example That Creates a PDF

Listing 14.2 is a short code sample that creates a PDF file using Quartz 2D. Many of the illustrations in this book were created by sending Quartz 2D graphics to PDF files using similar code.

Listing 14.2. Creating a Simple PDF

void CreatePDFFile()
{
    // Ask the user where we should save the file.
    CFURLRef saveLocation = CopySaveLocation();
    if(NULL != saveLocation) {
            CGRect mediaBox = CGRectMake(0, 0, 576.0, 576.0);
            // Create a dictionary to store our document attributes
            CFMutableDictionaryRef attributes =
                    CFDictionaryCreateMutable(
                           NULL, 3,
                           &kCFTypeDictionaryKeyCallBacks,
                           &kCFTypeDictionaryValueCallBacks);

            CFDictionaryAddValue(attributes, kCGPDFContextAuthor,
                    CFSTR("Scott Thompson"));
            CFDictionaryAddValue(attributes, kCGPDFContextTitle,
                    CFSTR("Sample PDF"));
            CFDictionaryAddValue(attributes, kCGPDFContextCreator,
                    CFSTR("CreatePDF Sample Code"));

            // Create a PDF Context that we will draw the graphics into
            CGContextRef pdfContext =
                    CGPDFContextCreateWithURL(saveLocation,
                           &mediaBox, attributes);

            CFRelease(attributes);

            // Begin a PDF page
            CGPDFContextBeginPage(pdfContext, NULL);

            DrawImageToExport(pdfContext, mediaBox.size);

            // End the PDF page
            CGPDFContextEndPage(pdfContext);

            // Finalize the PDF document
            CGContextRelease(pdfContext);
    }
}

The code sample begins by calling the CopySaveLocation routine. This routine puts up a standard system dialog asking the user to choose a destination for the PDF file. The value returned is a file URL. You create a CFDictionary and fill out three of the attribute keys. These keys will become part of the metadata contained in the PDF file.

Using the URL that was selected, you create a PDF document at that location using CGPDFContextCreateWithURL. Naturally, you pass in the metadata dictionary. You also supply a mediaBox rectangle for the PDF file. This box will also serve as the default page size. You then release the attributes dictionary at this point because it is not used again.

To draw in the PDF file, you have to add a page and the sample calls CGPDFContextBeginPage to do just that. Next you pass in NULL for the page info dictionary, which tells Quartz 2D that this page needs to inherit its mediaBox from the document.

At this point any drawing commands sent to the PDF context will be recorded on the first page. The routine DrawImageToExport is used to create a simple graphic on the PDF page. After drawing, the code simply ends the page using CGPDFContextEndPage.

Before leaving the routine, the code is very careful to release the PDF context so that Quartz 2D can finalize the PDF data and ensure that it is all written into the file.


Adding Hyperlinks to PDFs

Some PDF readers allow you to navigate using hyperlinks attached to hot spots in the PDF. Quartz 2D can annotate its metafiles with hyperlinks as well. The Quartz 2D API allows you to create hyperlinks both to locations within the PDF document and to URLs that are external to the PDF.

Internal Links

Creating a link within a PDF document is a two-step process. The first step is to annotate the PDF document with anchor points. An anchor point is simply a named location in the document that is the destination of an internal link. Quartz 2D allows you to create an anchor point anywhere on a page. The routine that creates an anchor point is CGPDFContextAddDestinationAtPoint. This routine accepts the name of the anchor point and its location on the current page.

To identify a hot spot in the PDF that will take you to an anchor point, your application can call the routine CGPDFContextSetDestinationForRect. This routine accepts the rectangle on the current page that becomes the hot spot. It also accepts the name of the anchor that is the destination of the link.

URL Hyperlinks

In addition to hyperlinks that navigate within a document, Quartz 2D allows you to add hyperlinks to network URLs. To create a URL hot spot, you call the Quartz routine CGPDFContextSetURLForRect. This function accepts the URL that is the destination of the hyperlink and a rectangle on the current page as parameters. The area under the rectangle becomes a hot spot in the resulting PDF.

Quartz 2D is intimately tied to the PDF standard. Not only does it derive its imaging model from PDF, it offers applications an easy to use mechanism for creating simple PDFs and drawing many more. You can even add some interaction features to the PDFs you create using Quartz 2D. The resolution independence of PDF graphics make them attractive in printing environments. They may also play an important role in the future as high-resolution monitors and other devices become commonplace. By adopting the PDF metafile capabilities in Quartz 2D, your application will be ready for these advancements today.


Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]


A

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Accelerate Framework
accessing Quartz 2D
from Cocoa
adding
hyperlinks to PDFs
pages to PDFs
paths
Adobe Systems Inc.
PostScript [See PostScript. ]
advance width (glyphs)
affine invariance
affine transformations 2nd 3rd
alpha
alpha channels
performance and
pixel alpha channels
premultiplying
alternative APIs
HTML Canvas tags
Python interface
AltiVec
AltiVec programming unit
animation
refresh rates
timers
updating
antialiasing 2nd 3rd
in complex paths
APIs
alternative APIs
HTML Canvas tags
Python interface
Core Graphics API
Core Image
appendBezierPathWithArcFromPoint
appendBezierPathWithArcWithCenter
Apple Type System for Unicode Imaging (ATSUI)
art box
ASCII encoding
Atkinson, Bill
ATSUGetGlyphInfo routine
ATSUI (Apple Type Services for Unicode Imaging) 2nd
drawing text example
autorelease pools
axial shadings


B

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

back buffer
backing store
BCPs (bezier control points)
control polygon
bevel joins
bezier control points (BCPs)
bezier curves 2nd
affine invariance
features of
Bezier, Pierre
bitmap contexts
CGBitmapContext class
obtaining CGContexts
bleed box
blend mode filters (Core Image)
blending modes
boxes
butt end caps


C

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

caching graphics, performance and
callback routines, creating data providers
CCD (Charge-Coupled Device)
cell spacing
cells
CFRunLoopTimer class
CGAffine transforms
CGAffineTransform 2nd
matrix representation
CGAffineTransformIdentity
CGAffineTransformMakeRotation
CGAffineTransformMakeScale
CGAffineTransformMakeTranslation
CGBitmapContext
CGBitmapContext class
CGBitmapContextCreate routine
CGColorRef
CGColorSpace opaque data type 2nd
CGColorSpaceCreateWithName
CGColorSpaceGetNumberOfComponents method
CGContext 2nd
creating for simple drawings
graphics state
obtaining NSView
CGContextAddArc
CGContextAddArcToPoint
CGContextAddCurveToPoint
CGContextAddEllipseInRect
CGContextAddLineToPoint
CGContextAddQuadCurveToPoint
CGContextBeginPage
CGContextBeginPath
CGContextClipToMask routine
CGContextClosePath
CGContextDrawImage
CGContextDrawImage routine
CGContextDrawLayerAtPoint routine 2nd
CGContextDrawLayerInRect routine
CGContextDrawPDFPage 2nd
CGContextEndTransparencyLayer
CGContextEOFillPath
CGContextFillPath
CGContextFlush
CGContextGetTextMatrix routine
CGContextGetTextPosition routine
CGContextGetUserSpaceToDeviceSpaceTransform
CGContextMoveToPoint
CGContextRef
CGContextRelease 2nd
CGContextReplacePathWithStrokedPath
CGContextRestoreGState 2nd 3rd
CGContexts
obtaining
bitmap contexts
HIView
PDF contexts
printing contexts
QuickDraw ports
CGContextSaveGState 2nd 3rd
CGContextSeFillPattern
CGContextSelectFont routine
CGContextSetAllowsAntialiasing
CGContextSetCMYKFillColor
CGContextSetFillColor
CGContextSetFillColorWithColor
CGContextSetFillPattern
CGContextSetFont routine
CGContextSetFontSize routine
CGContextSetGrayFillColor
CGContextSetInterpolationQuality routine
CGContextSetLineJoin
CGContextSetLineWidth
CGContextSetMiterLimit
CGContextSetRGBFillColor
CGContextSetShouldAntialias
CGContextSetShouldSmoothFonts
CGContextSetStrokePattern
CGContextSetTextMatrix routine
CGContextSetTextPosition routine
CGContextShowGlyphs routine
CGContextShowGlyphsAtPoint routine
CGContextShowGlyphsWithAdvances routine
CGContextShowText routine
CGContextShowTextAtPoint routine
CGContextSynchronize
CGDataConsumer
CGDataConsumer opaque data type 2nd
CGDataConsumerCreateWithURL
CGDataProvider
CGDataProvider opaque data type 2nd 3rd
CGDataProviderCreateWithData routine 2nd
CGDataProviderCreatewithDirectAccess
CGDataProviderCreateWithURL routine
CGFunction
CGFunctionCreate
CGFunctionRef
CGImage class [See also images.]
CIImage class versus
creating CGImages
drawing images
NSImage class versus
CGImageAlphaInfo enumeration
CGImageCreate routine
CGImageCreateCopyWithColorSpace routine
CGImageCreateWithImageInRect routine
CGImageCreateWithJPEGDataProvider routine
CGImageCreateWithMask routine
CGImageCreateWithPNGDataProvider routine
CGImageDestination opaque data type 2nd
CGImageGetColorSpace object
CGImageMaskCreate routine
CGImageRefs, reusing
CGImageSource opaque data type
CGImageSourceCreateWithURL routine
CGLayer opaque data type 2nd
example
CGLayerCreateWithContext routine 2nd
CGLayerGetContext routine 2nd
CGLayerRef
CGPath
CGPathAddArcToPoint
CGPathAddEllipseInRect
CGPatternCreate 2nd
CGPDFContextBeginPage
CGPDFContextCreate
CGPDFContextCreateWithURL
CGPDFContextEndPage
CGPDFDocument
CGPDFDocumentCreateWithProvider
CGPDFDocumentCreateWithURL
CGPDFDocumentGetNumberOfPages
CGPDFDocumentGetPage
CGPDFPageGetBoxRect
CGPDFPageGetDrawing
CGPDFPageGetDrawingTransform 2nd
CGPDFScanner
CGPoint
CGPointApplyAffineTransform
CGRect
CGRectApplyAffineTransform
CGShadingCreateAxial
CGSizeApplyAffineTransform
changing coordinate axes
characters, storing text
Charge-Coupled Device (CCD)
checkerboards, drawing
CIContext class
CIFilter class
usage
CIImage class
CIImageAccumulator class
circular arcs
CIVector class
ClipCGContextToRegion
clipping information
clipping with image masks
Close Subpath
Cocoa
accessing Quartz 2D
drawing paths
NSBezierPath [See NSBezierPath. ]
NSView
printing
Cocoa Text
Cocoa Text utility
text position
color adjustment filters (Core Image)
color channels
color correcting images 2nd
color effect filters (Core Image)
color function, creating
color management
integrated color management
color patterns
color samples [See also pixels.]
storing
color sequences
color space settings, graphics state
color spaces
CGImage and
color correcting images 2nd
color management
pixel color spaces
ColorSync
ColorSync Utility application
composite curves
compositing filters (Core Image)
compositioning, defined
concatenating transformation, order
concatenating transformations
contours
control points
control polygon
convex hull
coordinate axes, changing
coordinate systems
left-handed coordinate system
right-handed coordinate system
scaling
setting up for simple drawings
transforming
user space
coordinate transformations
CopyPathWithTransformation
CopySaveLocation routine
Core Foundation API
Core Graphics
alpha channel
Mac OS X
naming conventions
reference counting
Core Graphics APIs
drawing paths
Core Graphics objects
Core Image 2nd 3rd
CIContext class
CIFilter class
CIImage class
filter objects
filters
applying
combining with images
types of
GPUs
Mac OS X
operational overview
transitions
example
Core Image Fun House application
Core Image Kernel Language
Core Video
Mac OS X
CPUs
Crayon model, Quartz 2D drawing model
CreateImageWindow routine
crop box
CTM (Current Transformation Matrix) 2nd
cubic
current font
current path, graphics state
current paths
current patterns
current text position
Current Transformation Matrix (CTM) 2nd
Curve To operation
curved segements of paths
curves
Bezier curves
composite curves


D

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

dashes [See line dash. ]
data providers, creating
data sources [See data providers, creating. ]
data types
CGContext
CGContextRef
FSRef
DataRefs
decode array
degrees
Desktop Publishing
Device CMYK
device independence, Quartz 2D
Device RGB
device space
devices supported by Quartz 2D
direct access data providers
Display PostScript
distortion effect filters (Core Image)
drawing
CGImages
with glyph IDs
with NSBezierPath
numbers on roulette wheel
offscreen
CGBitmapContext class
CGLayer opaque data type
reasons for
paths
patterns
pattern color spaces
patterned graphics
setting current patterns
stencil patterns
PDFs 2nd
CGPDFDocument
drawing pages
example
retrieving pages
with Quartz 2D
shadings
strings
text
with transformations
drawing primitives, Quartz 2D drawing model
drawings, simple drawings
creating CGContext
drawing
checkerboards
hearts
text
example of
first bit of drawing
setting up coordinate systems
writing PNG files
drawRect: method
Duff, Tom


E

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

ellipses (ovals)
embarrassingly parallel
end caps 2nd
butt end caps
erasing graphics
Even/Odd winding rules
examples of simple drawings
creating CGContext
drawing
checkerboards
hearts
text
first bit of drawing
setting up coordinate systems
writing PNG files
exporting images
with Image I/O
with QuickTime


F

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

filling 2nd
filling routines
fills
filling routines
winding rules
Even/Odd winding rules
Non-Zero winding rules
filter objects (Core Image)
filters (Core Image)
applying
combining with images
transitions
example
types of
flatness
floating point geometry
floating point numbers
flushing
focus filters (Core Image)
font families
font smoothing 2nd 3rd
fonts
current font
FSRef


G

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Gaussian Blur filter (Core Image), applying
GDI library
general processing on GPUs
general settings, graphics state
generator filters (Core Image)
geometry adjustment filters (Core Image)
Geschke, Chuck
GetGraphicsImporterForDataRef routine 2nd
GetPixBaseAddr routine
GetPixRowBytes routine
glyph IDs, drawing with
glyphs 2nd [See also text.]
drawing modes [See drawing; text. ]
Gosling, Jim
GPUs (Graphics Processing Units)
Core Image
Core Image filter calculations
gradient filters (Core Image)
graphics [See also images.]
caching and performance
erasing
graphics context 2nd
Graphics Processing Units [See GPUs (Graphics Processing Units). ]
graphics programming in the modern age
GPUs
programmable graphics cards
vector processing
graphics state
clipping information
color space settings
CTM
current path
general settings
image settings
line art settings
text setting
graphics state stack
GraphicsExportDoExport routine
GraphicsExportRequestSettings routine
GraphicsExportSetInputCGImage routine
GraphicsExportSetSettings routine
GraphicsImportCreateCGImage routine


H

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

half-pixel line problem
halftone effects filters (Core Image)
hardware
Mac OS X
managing
hearts, drawing
Herzfeld, Andy
hexagonal paths
HIView system
HIView, obtaining CGContexts
HIViewDrawCGImage
homogenous coordinates
HTML Canvas tags
hyperlinks, adding to PDFs


I

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

icons, meanings of
Image I/O 2nd
exporting images
importing images
image masks
clipping with
combining with images
creating
defined
pixel format information and
image processing, tools for
image settings, graphics state
Image Units
images [See also graphics; Core Image.]
CGImage class
creating CGImages
drawing images
NSImage class versus
color correcting 2nd
combining with filters (Core Image)
creating
from other images
from pixel buffers
from PNG or JPEG data
from QuickDraw PixMap
with combined images and masks
data providers, creating
defined
exporting
wiht QuickTime
with Image I/O
importing
with Image I/O
with QuickTime
interpolation quality
performance
pixel format information
resolution independence
sampled images
imaging model
immutability of pixels
importing images
with Image I/O
with QuickTime
integrated color management
interleaving planar pixel data
internal links, adding to PDFs
interpolation algorithm
interpolation quality
performance and
inverse transformations


J

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Java 2D
Jobs, Steve
JPEG data, creating images from


K

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

kCGPatternTilingConstantSpacing
kCGPatternTilingConstantSpacingMinimalDistortion
kCGPatternTilingNoDistortion
kernal, Mac OS X
Kernel (Core Image)
kEventControlDraw
kEventParamCGContextRef
key-value coding


L

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

layers, offscreen drawing
example
layout of text
left-handed coordinate system
libraries
ATSUI (Apple Type System for Unicode Imaging)
Cocoa Text
Java 2D
NeXT
QuickDraw
vImage
ligatures
limitations of PostScript
line art
paths [See paths. ]
line art settings, graphics state
line dash
line joins
Line To operation
line width
PostScript
lines
listings
A CGPatternCreate Sample
A Sample Evaluation Callback
Code to Flip the HIView Coordinate System
Code Using Core Image to Blur an Image
Combining Rotations and Translations to Draw the Numbers
Converting an Image to the Display's Color Space
Creating a CGFunction for RGB Shadings
Creating a CGImage from a QuickDraw PixMap
Creating a Context for Drawing a PDF File
Creating a Radial Shading
Creating a Simle Off-Screen Bitmap
Creating a Simple PDF
Creating an Axial Shading
Creating an Image from a Pixel Buffer in Memory
Creating an Image from a PNG Resource
Creating the Layer with the Star
Draw Content Handler that Mixes Quartz and QuickDraw
Drawing a Colored Pattern
Drawing a PDF Document
Drawing a Stencil Pattern in a Variety of Colors
Drawing the Central Radial Lines
Drawing the Sunset Sample
Exporting an Image Using Image I/O
Exporting an Image via QuickTime
Importing a CGImage using Quick-Time
Importing an Image with a CGImageSource
Initializing the Roulette Wheel Context
Main Drawing Routine of the Star Pattern
Obtaining a CGContext for an HIView
Obtaining a CGContext Inside of an NSView's Draw Method
Obtaining a Printing Context During Carbon Printing
Obtaining Glyphs for the Sunset Sample
Python Code to Draw the Ace of Hearts Napkin Artwork
Routine to Draw the Circular Cog Path
Routine to Generate the Bottle Profile Path
The CreateCGImageRef Routine from the Image I/O Export Sample
The drawRect: Method of the Transition Sample
The handleTimerEvent: Method of the TransitionView
The initWithFrame: Method of the Transition Sample Code
The runAnimation: Method of the Transition Sample Code
TransitionView Class Declaration
LockPixels routine
loop timers


M

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Mac OS X
Core Graphics
Core Image
Core Video
graphics system
kernel and hardware
OpenGL
QuickTime
managing hardware complexity
mapping
masks [See image masks. ]
matrices as transformations
homogenous coordinates
matrix representations of transforms
measuring rotations
mediaBox
memory buffers [See also pixel buffers.]
creating data providers
memory organization of pixels
metafiles 2nd
microprocessors, vector processing
miter limits
Moore, Gordon
Moore's Law
Move To operation


N

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

naming conventions, Core Graphics
NeWS (Network extensible Windowing System)
NeXT, libraries
Non-Zero winding rules
NSAffine Transform
NSAffineTransformStruct, matrix representations
NSAnimation class
NSBezierPath 2nd
API differences to building paths from segments
drawing
functionality
relativeCurveToPoint
relativeLineToPoint
relativeMoveToPoint
NSFrameRect 2nd
NSGraphicsContext
NSImage class, CGImage class versus
NSView class
NSView, obtaining CGContext


O

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Objective-C API
key-value coding
using from Carbon
Objective-C applications
objects
Core Graphics objects
releasing
obtaining CGContexts
bitmap contexts
HIView
NSView
PDF contexts
printing contexts
QuickDraw ports
offscreen bitmaps, caching graphics and performance
offscreen drawing
CGBitmapContext class
CGLayer opaque data type
example
reasons for
OpenADefaultComponent routine
OpenGL Shading Language
OpenGL, Mac OS X
optimization [See performance. ]
OSType
ovals


P

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

pages
adding to PDFs
Painter's model
PathApplier
paths 2nd 3rd
adding
antialiasing in complex paths
Bezier curves
building
building from segments
API differences
Close Subpath
Curve To operation
example
Line To operation
Move To operation
Quad Curve To operation
components of
contours
control points
current paths
curved segments
drawing
many lines and rectangles
fills [See fills. ]
half-pixel line problem
hexagonal paths
transforming
PathTransformer
pattern cell callback
pattern cells
pattern color spaces
pattern spacing
patterned graphics
patterns 2nd
cell spacing
cells
color patterns
creating
pattern cell callback
drawing
pattern color spaces
patterned graphics
setting current patterns
stencil patterns
stencil patterns
transformations
PDF contexts, obtaining CGContexts
PDF files and images
PDFKit
PDFs (Portable Document Format) 2nd
adding hyperlinks
creating
adding pages
example
from drawings
PDF contexts
drawing 2nd
CGPDFDocument
drawing pages
example
retrieving pages
releasing context
shading
performance
images
paths
antialiasing in complex paths
drawing many lines and rectangles
half-pixel line problem
pixel alpha channels
pixel buffers
CGBitmapContext class
creating images from
pixel color spaces
pixel data
pixel maps, creating images from images
pixel-based graphics [See images. ]
pixels [See also images.]
defined
format information
immutability of
memory organization
planar pixel data, interleaving
Quartz 2D drawing model
scaling
sub-pixel sampling
PixMap [See QuickDraw PixMap. ]
planar pixel data, interleaving
PMSessionBeginPage
PMSessionEndPage
PMSessionGetGraphicsContext
PMSessionSetDocumentFormat-Generation
PNG data, creating images from
PNG files, writing
Point
polygons, control polygon
Portable Document Format (PDFs)
adding hyperlinks
creating
adding pages
example
from drawings
PDF contexts
drawing 2nd
CGPDFDocument
drawing pages
example
retrieving pages
releasing context
shading
Porter, Thomas
ports, QuickDraw
PostScript
Display PostScript
limitations of
line width
NeWS
on screen
premultiplying alpha channels
Printer's Point
printing Cocoa applications
printing contexts, obtaining CGContexts
programmable graphics cards
Python interface


Q

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

QDBeginCGContext
QDEndCGContext
QTNewDataReferenceFromFSRef routine 2nd
Quad Curve To operation
quadratic
Quartz 2D 2nd
Quartz 2D drawing model
Crayon model
drawing primitives
graphics context
pixels
Quartz Composer application
Quartz Extreme
QuickDraw 2nd 3rd
CGContexts, obtaining
drawing primitives
fonts in
scaling
QuickDraw PixMap
creating CGImages
creating images from
QuickTime 2nd
exporting images
as image processing tool
importing images
Mac OS X


R

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

radial distance
radial lines
radial shadings
radians
rasterization
rectangles
reference counting in Core Graphics
refresh rates
for animation
relativeCurveToPoint
relativeLineToPoint
relativeMoveToPoint
releasing
context, PDFs
Rendering Intent 2nd
resampling
images and performance
resolution independence
Quartz 2D
user space
retrieving pages
reusing CGImageRefs
RGB, color samples
rich text support
right-handed coordinate system
rotation
measuring
rotation matrix
rowBytes (in images)


S

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

sampled images
scale transform, matrix representation
scaling
coordinate systems
pixels
QuickDraw
segments, building paths
API differences
Close Subpath
Curve To operation
example
Line To operation
Move To operation
Quad Curve To operation
setDefaultLineWidth
setLineCapStyle
setMiterLimit
shading 2nd
PDFs
shadings
color function, creating
creating
axial shadings
radial shadings
drawing shadings
transparent area
shadow settings
shapes
CGContext functionality
circular arcs
creating
ellipses (ovals)
lines
NSBezierPath functionality
rectangles
SIMD vector processors
simple drawings
checkerboards
creating CGContext
example of
first bit of drawing
hearts
setting up coordinate systems
text
writing PNG files
simplicity and image performance
sips (scriptable image processing system)
stacks, graphics state stacks
stencil patterns 2nd
storing text
stream-based data providers
strings, drawing
strokes 2nd
end caps
flatness
line dash
line joins
line width 2nd
miter limits
stylize filters (Core Image)
sub-pixel sampling
subdivision
SyncCGContextOriginWithPort
synchronization


T

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

text
ATSUI (Apple Type Services for Unicode Imaging)
Cocoa Text system
drawing 2nd
example
glyph IDs, drawing with
glyphs
rich text support
storing
strings, drawing
text drawing modes
text matrix 2nd
text position
text setting, graphics state
tile effect filters (Core Image)
timers for animation
transformations 2nd
affine transformation 2nd 3rd
basic transformations
CGAffine transforms
concatenating transformations
order
in context
coordinate transformations
drawing with
inverse transformations
as matrices
homogenous coordinates
NSAffine Transform
patterns
rotation
scaling
translations
transforming
coordinate systems
paths
transforms, matrix representations
transistors
transition filters (Core Image)
transitions (Core Image)
example
translations
transparency
transparency layers
transparent areas, shadings
trim box


U

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Unicode encoding
Uniform Type Identifiers (UTIs)
UnlockPixels routine
updating animation
URL hyperlinks, adding to PDFs
user space 2nd
coordinate systems
floating point geometry
UTIs (Uniform Type Identifiers)


V

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

vector processing
Velocity Engine
video cards 2nd 3rd
video memory and performance
vImage
VRAM and performance


W

Previous Page
Next Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

Warnock, John
winding rules
Even/Odd winding rules
Non-Zero winding rules
writing PNG files


X

Previous Page

Index

[A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X]

XLib library