Tải bản đầy đủ (.pdf) (30 trang)

Lập trình ứng dụng cho iPhone part 19

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (827.14 KB, 30 trang )

366
Graphics: Quartz,
Core Animation,
and OpenGL
As we saw in the last chapter, creating and displaying images often isn’t enough. In
games and other more complex programs, you’ll also want to manipulate those
images in various ways at runtime. The iPhone
OS
offers two major ways to do this.
The first is through Quartz
2D
, a two-dimensional drawing library that allows for
complex line drawings, much as Canvas did on the web. It’s also the heart of the
Core Graphics frameworks. We already touched upon Quartz in the previous chap-
ter, when we drew images straight to the
CALayer
of a
UIView
; it’ll be the focus of
the majority of this chapter. Quartz also supports Core Animation functions, which
we’ll address somewhat more briefly.
This chapter covers

Using Quartz 2D for drawing

Understanding context, paths, and state

Using Core Animation

Learning about OpenGL ES
367The Quartz context


The second major way to manipulate images is through the Open
GL

ES

API
. This
cross-platform
API
, originally developed by Silicon Graphics, could be the topic of its
own book, so we’ll just show you how to get started with it.
But most of this chapter is going to be about Quartz, a topic that we’re going to
dive into immediately.
19.1 An introduction to Quartz 2D
Quartz
2D
is a two-dimensional drawing library that’s tightly integrated into the
iPhone
OS
. It works well with all the relevant iPhone frameworks, including Core Ani-
mation, Open
GL

ES
, and the
UIKit
.
Fundamentally, Quartz’s drawings depend upon three core ideas: context, paths,
and state, each of which will be the topic of a future section.


Context is a description of where the graphics are being written to, as defined by
a
CGContextRef
. You’ll usually be writing to a
UIView
or to a bitmap.
– Layers are a little less important for this overview, but they’re where Quartz
drawing occurs. They can be stacked one on top of another, creating a com-
plex result. When working with the iPhone, you’ll often only have a single
layer associated with each of your
UIKit
objects.

Paths are what you’ll typically be drawing within Quartz. These are collections of
lines and arcs that are drawn in advance, and then are “painted” to the screen
by either stroking or filling the path in question (or, possibly, by clipping it).

State saves the values of transformations, clipping paths, fill and stroke settings,
alpha values, other blending modes, text characteristics, and more. The current
state can be stored with
CGContextSaveGState
and restored with
CGContextRe-
storeGState
, allowing for easy switching among complex drawing setups.
Quartz is built on the older Core Foundation framework that we’ve met a few times
over the course of this part of the book. This means that you’ll need to use older styles
of variables to integrate with Cocoa Touch using toll-free bridging, and to respect
Core Foundation’s memory-management techniques. Take a look at the “Using Core
Foundation” sidebar in chapter 16 if you need a refresher on these topics.

If you need more information on any Quartz topic, your should reference the
“Quartz
2D
Programming Guide” at Apple’s developer website. It’s a fine introduction
to Quartz, though not as focused on the iPhone as you’d probably like, a deficiency
that we’ll correct in this chapter.
Using Quartz requires little special setup. It can be easily integrated into any tem-
plate and any project that you want. Just be sure to include the Core Graphics frame-
work and the CoreGraphics/CoreGraphics.h include file before you get started.
With that said, we’re ready to dive into our first major Quartz topic: the context.
19.2 The Quartz context
A graphical context is a description of where Quartz will be writing to. This could include
a printer, a
PDF
file, a window, or a bitmap image. On the iPhone, you’re only likely to
make use of two of these possibilities.
368
C
HAPTER
19
Graphics: Quartz, Core Animation, and OpenGL
Most frequently, you’ll work with the graphical context that is automatically associ-
ated with the
CALayer
(Core Animation layer) of each
UIView
. That means that you
can use Quartz to draw to most
UIKit
objects. To do so, you override the

drawRect:
method and, inside the object in question, you use
UIGraphicsGetCurrentContext
to
retrieve the current context.
You might alternatively create a bitmap context in order to create or modify an
image that you’ll use elsewhere in your program. You do this by using the
UIGraph-
icsBeginImageContext
and
UIGraphicsEndImageContext
functions.
There are a variety of Core Graphics functions that can be used to access other sorts of
contexts—types that you won’t usually use on an iPhone. The functions required to
capture a
PDF
context are one such example. These have two deficits that you should
be aware of: they depend more heavily on the Core Foundation frameworks and they
use Quartz’s inverted coordinate system.
One thing to note about graphical contexts is that they’re created in a stack: when
you create a new context, it’s pushed on top of a stack, and when you’re done with it,
it’s popped off. This means that if you create a new bitmap context, it’ll be placed on
top of any existing context, such as the one associated with your
UIView
, and will stay
there until you’re done with the bitmap.
Warning: inverse coordinate system ahead
By now, you should be familiar with the standard iPhone coordinate system. It has
the origin at the top left of the screen, with the main axes running to the right and
down. Quartz’s default coordinate system is inverted, with the origin at the bottom

left of the screen and the main axes running right and up.
This won’t
usually
be a problem. The Cocoa Touch methods that you’ll be using to
create and write to graphical contexts will usually transform Quartz’s default coordi-
nates so that they look like iPhone coordinates to you.
Once in a while, though, you’ll run into a situation where you’ll draw to a UI-derived
context and find your content flipped upside down (and in the wrong position). This is
a result of accessing Quartz in a way that hasn’t been transformed.
As of this writing, we’re aware of two situations where you’ll have to correct Quartz’s
coordinate system by yourself, even when using one of the UI-derived contexts: if you
import images using the native Quartz functions (as opposed to the
UIImage
meth-
ods that we saw in the last chapter), and if you write text. We’ll talk about each of
these when we get to them.
Personally, we consider these coordinate inversions bugs, and it’s our expectation
that they’ll eventually be corrected, perhaps even by the time this book is published.
If you create a context without using Cocoa Touch, expect
everything
to be inverted.
This is something that we don’t expect to change in the future.
369The Quartz context
Table 19.1 lists these context-related functions, including both the standard
UI
context functions and the older Core Graphics function that you’re most likely to
use—for
PDF
s.
We won’t be covering

PDF
s in this book, but we’re going to look at how to use each of
the
UIKit
context styles, starting with the
UIView
.
19.2.1 Drawing to a UIView
In chapter 18, we offered an introductory example of how to write to a
UIView
graphi-
cal context using the
drawRect:
method. That example was somewhat simplified
because the
UIKit
draw image commands mostly hide the idea of graphical contexts
from you. They automatically write to the current context, which inside
drawRect:
is
the context related to the
UIView
. For most other functions, you’ll need to do a bit
more work: retrieving the graphical context and passing that context along to any
drawing commands that you use.
Listing 19.1 shows how to draw a simple abstract face using this technique.
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
Table 19.1 Methods for graphical context creation

Function Arguments Summary
UIGraphicsGetCurrentContext
(none) Returns current con-
text, which is usually
the context of the cur-
rent
UIKit
object,
but could also be a
context that you cre-
ated by hand
UIGraphicsBeginImageContext CGSize
Creates a bitmap
context
UIGraphicsEndImageContext
(none) Pops a bitmap con-
text off the stack
UIGraphicsGetImageFromCurrentImageContext
(none) Returns a bitmap as
a
UIImage

*
; used
with a bitmap context
only
CGPDFContextCreate CGDataConsumerRef
,
CGRect
,

CGDictionaryRef
Creates a PDF
context
Listing 19.1 A few arcs drawn inside an existing context
370
C
HAPTER
19
Graphics: Quartz, Core Animation, and OpenGL
CGContextAddArc(ctx,110,50,30,0,2*M_PI,1);
CGContextAddArc(ctx,210,50,30,0,2*M_PI,1);
CGContextAddArc(ctx,160,110,15,0,2*M_PI,1);
CGContextAddArc(ctx,160,210,25,0,2*M_PI,1);
CGContextFillPath(ctx);
}
This example is fairly simple. You create a
UIView
subclass, and then you go to its
drawRect:
method. Once there, you capture the current context and use it to do
whatever Quartz
2D
drawing you desire.
The function calls won’t be familiar to you, but they’re
calls to draw a bunch of circles; we’ll discuss them in the
next section. As shown in figure 19.1, the art ends up look-
ing oddly abstract, which shows how Quartz draws continu-
ous paths. You see lines connecting one circle to the next, as
if the pencil never comes off the page, a topic we’ll talk
about more in the next section.

Leaving aside those specifics for a moment, this shows
one of the two ways that you can use all of the Quartz func-
tions described in this chapter: by painting a
UIView
. And
remember that a
UIView
can be almost any
UIKit
object,
due to inheritance.
Drawing to a
UIView
allows for on-screen picture cre-
ation, but you can also draw pictures without displaying
them immediately. That’s done with a bitmap.
19.2.2 Drawing to a bitmap
The main reason to create a bitmap rather than draw directly to a view is to use your
graphic several times in your program—perhaps all at the same time. For example,
Apple offers a sample program that draws the periodic table by creating a standard bit-
map that’s used for all the elements, and then repeating it. You might similarly create
billiard balls using bitmaps if you were programming a billiards game. In chapter 17,
we could have used Quartz to create the red dots that we used in our gravity and alti-
tude programs as bitmaps, so that we didn’t have to separately create them outside of
the program.
The process of creating a bitmap and turning it into a
UIImage
is relatively simple.
You create a graphical context, draw in that context, save the context to an image, and
close the context. Listing 19.2 shows how to create a red dot image like the one you

used in earlier programs.
- (void)viewDidLoad {
[super viewDidLoad];
UIGraphicsBeginImageContext(CGSizeMake(20,20));
Listing 19.2 A new context created to hold an image
Creates bitmap
context
B
Figure 19.1 The iPhone
does abstract art.
371Drawing paths
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextAddArc(ctx,10,10,10,0,2*M_PI,1);
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
CGContextFillPath(ctx);
UIImage *redBall =
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *redBallView = [[UIImageView alloc] initWithImage:redBall];
redBallView.center = CGPointMake(160,330);
[self.view addSubview:redBallView];
}
Again, this example is simple. You could do this work anywhere you wanted, but we’ve
elected to use the
viewDidLoad
setup method. To start the process, you create an image
context, which is to say a bitmap
B
, and you immediately retrieve that context’s variable

for use
C
. Following that, you do whatever drawing work you want. When you’re done,
you turn the bitmap into a
UIImage

D
and close out your context
E
. You can then
manipulate the image as you see fit; here it was turned into a
UIImageView
.
You now know two ways to use contexts in the Quartz environment. With that in
hand, you’re ready to dive straight into what Quartz can do, starting with paths, which
will be the foundation of most Quartz work.
19.3 Drawing paths
The path is what Quartz will be drawing. If you’re familiar with Canvas, this will look
familiar, because both libraries use the same drawing paradigm. A path is a set of lines,
arcs, and curves that are all placed continuously within a graphical context. You only
“paint” a path when it’s complete, at which point you can choose to either fill it or
stroke it.
Many of the functions required to define and draw paths are listed in table 19.2.
Table 19.2 A variety of simple drawing functions that allow for vector-based graphics
Function Arguments Summary
CGContextBeginPath context
Creates a new path.
CGContextAddArc context
,
x

,
y
,
radius
,
startangle
,
endangle
,
clockwise
Creates an arc, with the angles defined in radians.
A line will be drawn to the start point if there are
previous entries in the path, and from the end
point if there are additional entries.
The more complex functions
CGContextAdd-
ArcToPoint
,
CGContextAddCurveTo-
Point
, and
CGContextAddQuadCurveTo-
Point
allow for the creation of tangential arcs,
Bezier curves, and quadratic Bezier curves.
CGContextAddEllipseInRect context
,
CGRect
Creates an ellipse that fits inside the rectangle.
Retrieves new

context’s pointer
C
Saves bitmap
context to an image
D
E
Closes bitmap context
372
C
HAPTER
19
Graphics: Quartz, Core Animation, and OpenGL
CGContextMoveToPoint
is the one function that deserves some additional discussion.
As you’ll recall, we said that a path was a continuous series of lines and arcs that you
draw without picking the pen up off the paper. But there is a way to pick the pen up,
and that’s with the
CGContextMoveToPoint
function, which is vital when you want to
draw unconnected objects as part of a single path.
For example, to avoid drawing a line between the first two circles in listing 19.1,
you’d use the following code:
CGContextAddArc(ctx,110,50,30,0,2*M_PI,1);
CGContextMoveToPoint(ctx, 240, 50);
CGContextAddArc(ctx,210,50,30,0,2*M_PI,1);
After drawing the first circle, you move your virtual pencil to the point where you’ll
begin drawing the arc of the second circle, which is 240, 50.
The rest of the functions are largely self-explanatory. We already saw the arc com-
mands in some of our earlier examples, and the others work in similar ways. For more
information on the more complex functions, take a look at the

CGContext
class refer-
ence. If you’re unfamiliar with Bezier and quadratic curves, take a look at our expla-
nation of the nearly identical Canvas functions in chapter 6 (section 6.2.2; particularly
figure 6.4, which depicts what both sorts of curves look like).
We’re going to move on from these simple drawing commands to the question of
what you do once you have a path. There are several options, beginning with the sim-
ple possibility of closing it and drawing it.
19.3.1 Finishing a path
As we’ve already noted, the path functions define the points and lines that make up a
drawing. When you’ve got that in hand, you have to do something with it. There are
three main choices: stroke the path, fill the path, or turn it into a clipping path. These
functions are all listed in table 19.3.
You’ll usually either stroke (outline) a path or fill it when you’re done. We used a
fill in each of our previous examples, but a stroke could have been substituted; the dif-
ference is that our circles wouldn’t have been filled in.
CGContextAddLineToPoint context
,
x
,
y
Creates a line from the current point to the desig-
nated end point.
The more complex
CGContextAddLines
func-
tion allows the addition of an array of lines.
CGContextAddRect context
,
CGRect

Creates a rectangle.
The more complex
CGContextAddRects
func-
tion adds a series of rectangles.
CGContextMoveToPoint context
,
x
,
y
Moves to the point without drawing.
Table 19.2 A variety of simple drawing functions that allow for vector-based graphics (continued)
Function Arguments Summary
373Drawing paths
A clipping path is a bit more complex, in that you don’t draw something on the
screen. Instead, you define an area, which corresponds to the area inside the path that
you’d have filled in, and you only show later drawings that appear inside that clipping
path. We’ll talk about clipping paths more, and show an example, when we get to
graphical states. For now, note that you create them from paths.
19.3.2 Creating reusable paths
So far, you’ve created paths by drawing them directly to a context, be it a
UIView
or a
bitmap. But it’s also possible to create reusable paths that you can quickly and easily
apply later. This has many of the same advantages as creating a bitmap: you get reus-
ability and multiplicity. Reusable paths will probably be particularly useful in anima-
tions and programs where you use the same graphic on multiple pages.
To create reusable paths, you use the
CGPath
commands rather than the

CGCon-
text
commands. There are equivalents to many of the simple
CGContext
functions, as
shown in table 19.4.
When you’re working with reusable paths, you first use the
CGPathCreateMutable
function to create a
CGPathRef
, and then you use
CGPath
commands to add lines or
Table 19.3 Functions for finishing a path
Function Arguments Summary
CGContextClosePath context
Draws a line from the end point of your path to the start
point, and then closes it. This is an optional final com-
mand that’s usually used when you’re stroking a path.
CGContextFillPath context
Closes your path automatically, and paints it by filling it
in.
CGContextEOFillPath
is an alternative that
does the filling in a slightly different way.
CGContextStrokePath context
Paints your path by stroking it.
CGContextClip context
Turns the current path into a clipping path.
Table 19.4

CGPath
commands and their
CGContext
equivalents
CGPath
Function
CGContext
Function
CGPathCreateMutable CGContextBeginPath
CGPathAddArc CGContextAddArc
CGPathAddEllipseInRect CGContextAddEllipseInRect
CGPathAddLineToPoint CGContextAddLineToPoint
CGPathAddRect CGContextAddRect
CGPathMoveToPoint CGContextMoveToPoint
CGPathCloseSubpath CGContextClosePath
374
C
HAPTER
19
Graphics: Quartz, Core Animation, and OpenGL
arcs to that
CGPathRef
. Your reusable path can include multiple, discrete subpaths
that don’t have to connect to each other. You can end one subpath and start another
with the
CGPathCloseSubpath
function.
Note that there are no painting functions associated with the reusable paths.
That’s because they’re storage devices. In order to use one, you add it onto a normal
path with the

CGContextAddPath
function, which draws your stored path to your
graphical context, where it’ll abide by the normal rules.
Listing 19.3 shows how to use a mutable path to replace the
CGContext
commands
that we previously used in listing 19.1 to draw an abstract face. A more realistic exam-
ple would probably hold on to the path for use elsewhere; we released it here to
remind you of how Core Foundation memory management works.
- (void)drawRect:(CGRect)rect {
CGMutablePathRef myPath = CGPathCreateMutable();
CGPathAddArc(myPath,NULL,110,50,30,0,2*M_PI,1);
CGPathMoveToPoint(myPath,NULL, 240, 50);
CGPathAddArc(myPath,NULL,210,50,30,0,2*M_PI,1);
CGPathAddArc(myPath,NULL,160,110,15,0,2*M_PI,1);
CGPathAddArc(myPath,NULL,160,210,25,0,2*M_PI,1);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextAddPath(ctx,myPath);
CGContextStrokePath(ctx);
CFRelease(myPath);
}
Of note here is the
NULL
that’s constantly being sent as a second argument to the
CGPath
commands. This argument is intended to be a
CGAffineTransform
variable. It
allows you to apply a transformation to the element being drawn, which is something

we’ll discuss shortly.
Now that we’ve looked at two different ways to create complex paths, we’re going
to take a step back and look at how to draw much simpler objects in a simpler way.
19.3.3 Drawing rectangles
Drawing paths takes some work, but if you want to draw a rectangle, Quartz makes it
easy. All you have to do is use one of a few functions listed in table 19.5. These func-
tions take care of the path creation, drawing, and painting for you in a single step.
Listing 19.3 A drawing with
CGPath
Table 19.5 Specific functions allow you to draw rectangles
Function Arguments Summary
CGContextClearRect context
,
CGRect
Erases a rectangle.
CGContextFillRect context
,
CGRect
Draws a filled rectangle.
The more complex variant
CG-
ContextFillRects
allows you
to fill a whole array of rectangles.
375Setting the graphic state
The
CGContextClearRect
function can be particularly useful for erasing a window
when you’re ready to draw something new to it. Now that we’ve told you how to draw
objects in the simplest way possible, we’re ready to move on and start talking about

how to draw objects in more complex ways—by modifying state.
19.4 Setting the graphic state
The graphic state is how Quartz will be drawing. It includes a variety of information
such as what colors are being used for fills or strokes, which clipping paths constrain
the current drawing path, what transformations are being applied to the drawing, and
a number of other less important variables.
State is maintained in a stack. You can save a state at any time; it doesn’t change
how things are being drawn, but it does push that current state onto the top of a stack
for later retrieval. Later, you can restore a state, which pops the top state off the stack,
putting things back to how they were before the last save. We’ve mentioned these
functions before, but we’ve also listed them here in table 19.6.
As we’ve already noted, there are a lot of things that you can store in graphic state.
We’re going to cover many of them here, starting with colors.
19.4.1 Setting colors
In Quartz, you select colors by setting the fill color, the stroke color, or both in the cur-
rent graphical state. Once you’ve done this, any fill or stroke commands following the
color commands will appear in the appropriate colors. Note that color is irrelevant
while you are drawing the individual elements of a path—the color commands apply
only to the painting of the complete path at the end.
You can select colors from a variety of color spaces, which are different ways to
choose colors. They include
RGB
(red-green-blue),
RGBA
(red-green-blue-alpha),
CMYK
(cyan-magenta-yellow-black), and
CGC
olor (the underlying Core Graphics color
model). On the iPhone, you’ll usually want to either use the

RGBA
color space or use a
command that lets you select a color using standard
UIKit
methods. Table 19.7 lists
the four most relevant of these functions.
CGContextStrokeRect context
,
CGRect
Draws a stroked rectangle.
CGContextStrokeRectWithWidth context
,
CGRect
,
width
Draws a stroked rectangle, with the
stroke being the designated width.
Table 19.6 State-related functions that help define how you draw
Function Arguments Summary
CGContextSaveGState context
Pushes state onto a stack
CGContextRestoreGState context
Pops state off of a stack
Table 19.5 Specific functions allow you to draw rectangles (continued)
Function Arguments Summary
376
C
HAPTER
19
Graphics: Quartz, Core Animation, and OpenGL

The two RGB functions allow you to set a color using values from 0 to 1 for each of
red, green, blue, and alpha transparency (opacity). We saw an example of this in list-
ing 19.2:
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
The last two functions in table 19.7 allow you to set the color using any
CGColor
, and
you’ll understand how useful that is when you realize that you can read a
CGColor
property from any
UIColor
you create:
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
Given that you’re already familiar and comfortable with the
UIColor
s, we expect that
this latter function will be a popular one.
Having now covered the main ways to apply colors to your graphic state, we’re
ready to move on to the next topic: how to change how you draw through graphical
state transformations.
19.4.2 Making transformations
Transformations modify how you draw to your graphical context. They do this by chang-
ing the grid upon which you’re drawing by moving its origin, rotating, or resizing.
Why would you want to do these transformations?

They can be useful for drawing photographs (or other images), because the
transformations allow you to scale or rotate the picture.

They can make it a lot easier to do certain types of mathematical drawing. For
example, it’s probably easier to draw a symmetric mathematical construct if you’ve

got your origin in the center of the screen rather than up at the top left corner.

They can allow you to flip your screen if you end up in a context (or using a
function) with an inverse coordinate system.
CTM TRANSFORMATIONS
The simplest way to apply a transformation is to use one of the functions that modify
the current transformation matrix (
CTM
), which is a matrix that’s applied to all draw-
ing done in your current graphical state. These functions are described in table 19.8.
Table 19.7 The most important of numerous coloring functions
Function Arguments Summary
CGContextSetRGBFillColor context
,
red
,
green
,
blue
,
alpha
Sets the fill to the RGBA
value
CGContextSetRGBStrokeColor context
,
red
,
green
,
blue

,
alpha
Sets the stroke to the
RGBA value
CGContextSetFillColorWithColor context
,
CGColor
Sets the fill to the
CGColor
CGContextSetStrokeColorWithColor context
,
CGColor
Sets the stroke to the
CGColor
377Setting the graphic state
There are two gotchas that you should watch for.
First, note that the ordering of translations is somewhat pickier than the order of
color commands. You need to start your transformation before you add the relevant
lines to your path, and you need to maintain it until after you paint that path.
Second, although these transformations can be applied in any sequence, order
matters. Following are two transformation commands that could be applied together:
CGContextTranslateCTM(ctx, 100, 100);
CGContextRotateCTM(ctx, .25*M_PI);
These functions move a drawing 100 to the right and 100 down and rotate it by 45
degrees. Figure 19.2 shows the untransformed picture (which we’ve seen before), the
results if these commands are applied with the translation before the rotation, and the
results if they’re applied in the opposite order.
Table 19.8 CTM transformation functions that allow you to change how you draw
Function Arguments Summary
CGContextRotateCTM context

,
radian rotation
Rotates the grid
CGContextScaleCTM context
,
x-scale
,
y-scale
Scales the grid
CGContextTranslateCTM context
,
x-change
,
y-change
Moves the origin
Figure 19.2 As these variant transformations show, order matters. The left picture is untransformed;
the middle one is translated and then rotated; and the right one is rotated and then translated.

×