This chapter covers |
|
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 chapter, 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.
The second major way to manipulate images is through the OpenGL 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.
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 Animation, OpenGL 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.
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 template and any project that you want. Just be sure to include the Core Graphics framework 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.
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.
Most frequently, you’ll work with the graphical context that is automatically associated 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 UIGraphicsBeginImageContext and UIGraphicsEndImageContext functions.
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 coordinates 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 methods 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.
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.
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 PDFs.
Function |
Arguments |
Summary |
---|---|---|
UIGraphicsGetCurrentContext |
(none) |
Returns current context, which is usually the context of the current UIKit object, but could also be a context that you created by hand |
UIGraphicsBeginImageContext |
CGSize |
Creates a bitmap context |
UIGraphicsEndImageContext |
(none) |
Pops a bitmap context 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 |
We won’t be covering PDFs in this book, but we’re going to look at how to use each of the UIKit context styles, starting with the UIView.
In chapter 18, we offered an introductory example of how to write to a UIView graphical 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);
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 looking oddly abstract, which shows how Quartz draws continuous 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 functions 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 creation, but you can also draw pictures without displaying them immediately. That’s done with 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 bitmap 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 altitude 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.
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 , and you immediately retrieve that context’s variable for use . Following that, you do whatever drawing work you want. When you’re done, you turn the bitmap into a UIImage and close out your context . 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.
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.
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 CGContextAddArcToPoint, 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. |
context, x, y |
Creates a line from the current point to the designated end point. The more complex CGContextAddLines function allows the addition of an array of lines. |
|
CGContextAddRect |
context, CGRect |
Creates a rectangle. The more complex CGContextAddRects function adds a series of rectangles. |
CGContextMoveToPoint |
context, x, y |
Moves to the point without drawing. |
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 commands 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 reference. If you’re unfamiliar with Bezier and quadratic curves, take a look at our explanation 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 simple possibility of closing it and drawing it.
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.
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 command 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. |
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 difference is that our circles wouldn’t have been filled in.
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.
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 reusability and multiplicity. Reusable paths will probably be particularly useful in animations 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.
CGPath Function |
CGContext Function |
---|---|
CGPathCreateMutable |
CGContextBeginPath |
CGPathAddArc |
CGContextAddArc |
CGPathAddEllipseInRect |
CGContextAddEllipseInRect |
CGPathAddLineToPoint |
CGContextAddLineToPoint |
CGPathAddRect |
CGContextAddRect |
CGPathMoveToPoint |
CGContextMoveToPoint |
CGPathCloseSubpath |
CGContextClosePath |
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 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 example 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.
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 functions take care of the path creation, drawing, and painting for you in a single step.
Function |
Arguments |
Summary |
---|---|---|
CGContextClearRect |
context, CGRect |
Erases a rectangle. |
CGContextFillRect |
context, CGRect |
Draws a filled rectangle. The more complex variant CGContextFillRects allows you to fill a whole array of rectangles. |
CGContextStrokeRect |
context, CGRect |
Draws a stroked rectangle. |
CGContextStrokeRectWithWidth |
context, CGRect, width |
Draws a stroked rectangle, with the stroke being the designated width. |
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.
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.
Function |
Arguments |
Summary |
---|---|---|
CGContextSaveGState |
context |
Pushes state onto a stack |
CGContextRestoreGState |
context |
Pops state off of a stack |
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.
In Quartz, you select colors by setting the fill color, the stroke color, or both in the current 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 CGColor (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.
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 |
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 listing 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 UIColors, 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.
Transformations modify how you draw to your graphical context. They do this by changing the grid upon which you’re drawing by moving its origin, rotating, or resizing.
Why would you want to do these 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 drawing done in your current graphical state. These functions are described in table 19.8.
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 |
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.
Clearly, you need to be careful and think about ordering when you’re applying CTM transformations.
But CTM transformations aren’t the only way to change your drawing space.
Just as you can create a reusable path and then apply that to the context with the CGContextAddPath function, you can also create a reusable transformation matrix (using the affine transformation functions) and then apply that to the context with the CGContextConcatCTM function. This is managed by a set of six core functions, listed in table 19.9. Half of them create a new matrix, applying a transformation at the same time, and the other half apply a transformation to an existing matrix. The last function is the one that applies an affine transformation to your current graphical state.
Function |
Arguments |
Summary |
---|---|---|
CGAffineMakeRotation |
radian rotation |
Makes an array with the rotation |
CGAffineMakeScale |
x-scale, y-scale |
Makes an array with the scale |
CGAffineMakeTranslation |
x-change, y-change |
Makes an array with the translation |
CGAffineTransformRotate |
array, radian rotation |
Rotates the array |
CGAffineTransformScale |
array, x-scale, y-scale |
Scales the array |
CGAffineTransformTranslate |
array, x-change, y-change |
Translates the array |
CGContextConcatCTM |
context, array |
Applies the transformation |
The following code applies a rotation followed by a translation using a reusable affine matrix:
CGAffineTransform myAffine = CGAffineTransformMakeRotation(.25*M_PI);
CGAffineTransformTranslate(myAffine, 100, 100);
CGContextConcatCTM(ctx, myAffine);
Besides being able to create reusable affine transformations, you can also modify the transforms at a much lower level. Any affine transformation is constructed from a 3x3 matrix that is then multiplied across the individual vectors of your path using matrix multiplication. If you have specific needs, you can use the CGAffineTransformMake function to create a matrix by hand. Using it looks like this:
CGAffineTransform flip = CGAffineTransformMake(1,0,0,-1,0,0);
Information on how the matrix works and on some other functions can be found in the CGAffine reference.
The next sort of state you might want to change is one that makes fairly large-scale changes to your drawings: the clipping path.
We already spoke about clipping paths in section 19.3. You create a path as usual, but then you clip it, rather than filling it or stroking it. Anything that you paint on the screen afterward (within that graphical state) will only appear if it’s inside the clipping path.
For example, the following code causes later painting to only appear inside a large circle centered on the iPhone screen:
CGContextBeginPath(ctx);
CGContextAddArc(ctx,160,240,160,0,2*M_PI,1);
CGContextClip(ctx);
Figure 19.3 shows what a full-screen image might look like before the clipping occurred, and then after.
As with most of these Quartz functions, there are some opportunities for subtleties when using clipping paths. The CGContext reference offers a few additional functions for creating and modifying clipping paths.
So far we’ve discussed all the big-picture options for modifying your graphic state. There are many smaller things you can do too.
There are a wide variety of additional settings that can be used as part of the graphic state. Table 19.10 lists many of the most interesting ones.
Arguments |
Summary |
|
---|---|---|
CGContextSetAlpha |
context, alpha |
Sets alpha transparency |
CGContextSetBlendMode |
context, CGBlendMode |
Sets blending to one of almost 30 values, which specify how objects laid on top of each other interact with each other |
CGContextSetFlatness |
context, flatness |
Defines the accuracy of curves |
CGContextSetLineCap |
context, CGLineCap |
Defines how to draw the end of a line |
CGContextSetLineDash |
context, phase, lengths array, count |
Describes how to draw dashes along a stroke |
CGContextSetLineJoin |
context, CGLineJoin |
Defines how lines come together |
CGContextSetLineWidth |
context, width |
Describes the width of a stroke |
CGContextSetShadow |
context, CGSize, blur |
Sets a shadow behind all drawings |
CGContextSetShadowWithColor |
context, CGSize, blur, color |
Sets a colored shadow behind all drawings |
A number of more complex state changes can also be found in the CGContext class reference, but we’ve described the ones you’re most likely to use in the course of an average program.
We’re drawing to a close on the topic of graphical state, so let’s step back for a moment and look at how graphical state works.
When you use any of the various functions that modify the graphical state, you’re changing how you paint inside your current graphical context. The functions change the colors you’re using, they transform your underlying grid, they clip the area you’re allowed to paint within, or they make various smaller changes.
You can constantly reset these variables as your needs change, but this can get annoying. That’s why you’ll want to use the stack of states. It allows you to make a whole bunch of changes to state and then revert to a previous setup that you were happy with. We’ve already shown the two functions that do this in table 19.6.
Remember to save the state before you make a big change, such as adding a clipping path or running a whole bunch of graphical state functions. Then restore the state when you’re done with that. If you want, you can even be clever and slowly build up a whole set of states in your stack, and move back through them appropriately.
You should now understand the three most important elements of drawing with Quartz: contexts, which specify where to draw; paths, which specify what to draw; and graphical states, which specify how to draw. There are numerous more advanced things you can do in Quartz, and although we’re not going to get to all of them, the next section covers the most interesting ones.
Quartz has a number of advanced capabilities that go beyond simple line drawings. In this section we’re going to look at using gradients, images, and words.
Gradients are a core part of SDK design, because they’re a clearly evident aspect of the standard user interface. Unfortunately there’s no UIKit level class for creating gradients; instead, you have to fall back on Quartz.
There are two ways to create gradients inside Quartz: using a CGShadingRef object or a CGGradientRef object. As is often the case in Core Foundation functions, the difference is in complexity. CGGradientRef allows you to draw pretty simple gradients, and CGShadingRef requires you to define a CGFunctionRef object to precisely calculate how the colors in the gradient are displayed. As you’ve probably guessed, we’re going to talk about CGGradientRef here and point you to the Apple class references for CGShadingRef.
Table 19.11 shows the important functions required to draw gradients with CGGradientRef.
Function |
Arguments |
Summary |
---|---|---|
CGColorSpaceCreateWithName |
color space constant |
Creates a color space by name |
CGGradientCreateWithColors |
color space, color array, location array |
Creates a gradient using pregenerated colors |
CGGradientCreateWithColorComponents |
color space, color components array, location array, color count |
Creates a gradient with an array of color parts |
CGContextDrawLinearGradient |
context, gradient, start CGPoint, end CGPoint, options |
Draws a linear gradient |
context, gradient, start center, start radius, end center, end radius, options |
Draws a radial gradient |
|
CGColorSpaceRelease |
color space |
Frees up a color space object |
CGGradientRelease |
gradient |
Frees up a gradient object |
Drawing a gradient is a four-step process:
1.
Define your color space, which will usually be kCGColorSpaceGenericRGB for the iPhone.
2.
Define your gradient by listing colors and where they appear in the gradient, from 0 to 1. There are two ways to do this. You can hand off an array of CGColors (which might be useful if you want to generate them using UIColors) or you can hand off a longer array that defines the colors using another method, such as RGBA.
3.
Draw your gradient as a linear gradient (going from point to point) or a radial gradient (going from the center to the edge of a circle).
4.
Free up your memory.
Listing 19.4 shows all the steps required to draw a three-color linear gradient that spans the entire iPhone screen.
CGColorSpaceRef myColorSpace =
CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGFloat components[12] = {1,0,0,1,
0,1,0,1,
0,0,1,1};
CGFloat locations[3] = {0,.5,1};
CGGradientRef myGradient =
CGGradientCreateWithColorComponents(myColorSpace,
components, locations, (size_t)3);
CGContextDrawLinearGradient(ctx, myGradient, CGPointMake(0,0),
CGPointMake(320,480), (CGGradientDrawingOptions)NULL);
CGColorSpaceRelease(myColorSpace);
CGGradientRelease(myGradient);
This code steps through the steps we just listed, defining the color space, creating the parts of the gradient, drawing it, and cleaning up after it. As usual, you can find some more info on gradients in the CGGradient reference. For now, though, we’re ready to move on to the next advanced category of Quartz work: images.
In the last chapter, we saw one way to work with images, using methods that largely hid the specifics of graphical contexts from you as a programmer. Now that you’re fully immersed in Quartz, you can choose to use the Core Graphic functions instead.
There are two major Core Graphic functions for drawing, listed in table 19.12.
Function |
Arguments |
Summary |
---|---|---|
CGContextDrawImage |
context, CGRect, image |
Draws an image scaled to fit the rectangle |
CGContextDrawTiledImage |
context, CGRect, image |
Draws an image scaled to fit the rectangle but filling the current clip region |
These functions both require a CGImageRef, but remember that you can use the CGImage property of a UIImage to produce one. Alternatively, you can use the commands described in the CGImage reference, which offer more precise functionality, to create a new CGImage. Our suggestion is to go with what you know, which means using the UIKit methods, unless they can’t do what you need.
There’s one big gotcha to using the Quartz-related image-drawing functions: at the time of this writing, they produce a flipped image because they use Quartz’s native coordinate system internally. We’ll show you how to fix that momentarily.
Often you’ll want to turn an image into a bitmap and modify it before displaying it on the screen, most frequently so that you can make multiple uses of the image. We’re going to offer a quick example of crossing out a picture here.
Part of what’s unique about this example is that you can do all your drawing work without ever showing the image to the user (unlike if you were drawing on a UIView), thus opening up the possibility of many image-editing functions. When you do decide to display your newly saved image, you’ll see results like the image in figure 19.4.
The code needed to accomplish this simple crossing-out is shown in listing 19.5.
The process of modifying an image involves relatively few steps. You start off by creating your bitmap context . Next, you apply any transformations that you want to use for the picture . If you want to rotate or scale the original picture, here’s where you do it. Likewise, you could use a combination of translations and the context size to easily crop an image. In this example, you flip the picture over by applying a rotation and a translation, to account for the fact that CGContextDrawImage produces an inverted picture. (We’ll see an alternative way to do this in our next example.)
When your transformations are done, you can draw your image , and then draw whatever you want on top of it (or modify it in some other way). Finally, you save the new picture .
We’ll return to the idea of drawing on pictures in section 19.6 (though we’ll do it in a much more interactive way), but in the meantime we’re ready to draw words.
Unlike Canvas, Quartz does support drawing words on top of your pictures. The functions required are pretty intricate, though, and we generally suggest using UILabel or other UIKit objects, and placing them on top of your Quartz objects. But if you need words inside Quartz (either because you’re interweaving your words with other Quartz content or because you’re adding words to a picture), you’ll need to make use of the CGContext text options.
The majority of the text-related functions modify the graphical state, as described in table 19.13. The last two functions in the table draw your text.
Function |
Arguments |
Summary |
---|---|---|
CGContextSelectFont |
context, font name, size, text encoding |
Sets a font for the graphical state |
CGContextSetTextDrawingMode |
context, CGTextDrawingMode |
Defines how to draw text in the graphical state |
context, affine transform |
Places a transformation matrix in the graphical state for drawing only text |
|
CGContextSetSetPosition |
context, x, y |
Sets where to draw in the graphical state |
CGContextShowText |
context, string, length |
Draws the text at the current position |
CGContextShowTextAtPoint |
context, x, y, string, length |
Draws the text at the specified position |
You can find several other text-related functions in the CGContext reference. Most notably, if you need more control over your fonts (and particularly if you want to link up to UIFonts), you should use CGContextSetFont and CGContextSetFontSize instead of the CGContextSelectFont function that’s noted here—but keep in mind that you can’t use CGContextShowTextAtPoint when you set your font in this alternative way.
Listing 19.6 shows a simple example of printing text in Quartz.
CGContextSelectFont (ctx, "Helvetica",20,kCGEncodingMacRoman);
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGAffineTransform flip = CGAffineTransformMake(1,0,0,-1,0,0);
CGContextSetTextMatrix(ctx, flip);
CGContextShowTextAtPoint(ctx, 20, 85, "A Quartz Example", 16);
The only thing of note here is the creation of the affine transformation matrix, flip. We’ve already pointed out that the text-drawing functions don’t use the iPhone coordinate system at present. Instead, they’re still stored in an inverted manner, so you need to flip them over to use them correctly. (We hope that this changes in some future release of the iPhone OS.)
The affine transformation shown here describes the matrix using the CGAffineTransformMake function. It effectively does the same thing as our two-part transformation in listing 19.5. In our view, it’s a bit simpler, but less clear.
That’s only the basics of using text, but it should be enough to get you started when you need to draw within Quartz.
Quartz 2D is a fully featured drawing and painting language that we can only briefly touch on in this chapter. Among the other topics that you might want to research if you’re going to do more advanced work with Quartz are patterns, transparency layers, layer drawing, and PDF creation. As we’ve mentioned previously, Apple’s “Quartz 2D Programming Guide” is an excellent introduction to these topics.
We’re not quite done with Quartz yet. Before we finish up this chapter, we’re going to put together a more fully featured example combining some of the Quartz lessons from this chapter with some of the photographic work we covered in chapter 18.
To put together the lessons we’ve covered, you’re going to create a program that allows a user to load up a picture, draw on it, and then save the results. Figure 19.5 shows our intended result.
As usual, you’ll start in Interface Builder, but you only have two simple things to do here:
Once you get into Xcode, the programming will look a lot like the collage program in the last chapter, but with some nuances related to your greater understanding of Quartz.
You’ll be doing your coding in two parts. The overall structure of the program will go in photodrawViewController.m and the drawing specifics will go in drawView.m.
The view controller manages an image selector as well as several toolbar buttons, including the “action” button that you created in Interface Builder and Save and Cancel buttons that will appear later on. The code is shown in listing 19.7. We’ve omitted some of the view controller’s overall structure and focused on the code that’s involved when the user pushes the action button and activates choosePic:.
This is a pretty simple snippet of code because it shows the view controller acting as a traffic cop, accepting input from controls and sending off messages to other objects, which is pretty much the definition of what a view controller should do.
For once, you don’t have any setup in viewDidLoad:. Instead, the toolbar created in Interface Builder will initiate your program’s actions. At startup, the user has only one choice, to click the action button and start the image picker . When the picker returns, you modify the UIButtonBar to now give options for Save and Cancel, and then you send the picture off to drawView to be dealt with . Alternatively, you clear the image picker away if the user canceled it .
The save picture routine works the same way as the one you wrote in the collage program . The only difference is that this one includes a callback, which ends the program after the saving is done . The clear drawing method , meanwhile, makes a call to the drawView object again.
To learn what’s done with the initial picture, how drawing occurs, and what happens when the drawing is cleared, we need to look at this program’s other major class.
As we saw in the previous section, the view controller hands off three responsibilities to the view: displaying a picture, responding to touch events, and clearing the drawing. We’re going to step through these functions one at a time.
Listing 19.8 shows what’s done when a user picks an image.
-(void)drawPic:(UIImage *)thisPic {
myPic = thisPic;
[myPic retain];
[self setNeedsDisplay];
}
This routine is simple: it saves the picture to an instance variable and then alerts the UIView that its CALayer must be drawn.
We are going to save the CALayer’s drawRect: method for last, so we’ll look now at how the drawView class interprets touch events. This is shown in listing 19.9.
The overall concept here is pretty simple. You maintain an NSMutableArray called myDrawing as an instance variable. Within that, you create a number of NSMutableArray subarrays, each of which contains an individual path. You set up a new subarray when a touch starts and then add the current point when the touch moves or ends . The result is an array that contains a complete listing of all touches. But once again, we’re going to have to wait a bit to see how that’s drawn.
It’s notable that we tell drawView to draw (via the setNeedsDisplay method) both when a touch moves and when it ends. That’s because whenever the touch moves, you want to provide instant gratification by drawing what the user has sketched out so far. When the touch ends, you do the same thing.
Listing 19.10 shows the method that clears all current drawings. Its functionality is pretty obvious now that we know that the list of drawings is held as an array.
-(void)cancelDrawing {
[myDrawing removeAllObjects];
[self setNeedsDisplay];
}
At this point, your drawView object is maintaining two different instance variables: myPic contains the current picture and myDrawing contains an array of paths. Putting them together into a coherent whole just requires using some of the Quartz functions that we’ve discussed in the last two chapters. The results are shown in listing 19.11.
The main bulk of this method is spent iterating through the information that you saved in other methods. There are five Quartz functions that do the drawing work. First, you draw the selected image. We’ve gone back to using the UIKit methods from the last chapter , so that your image doesn’t end up upside-down. Then, you start working through the myDrawing array. Each subarray results in your program beginning a new path and moving to the start . As you move through the array, you add lines . Finally, when a subarray is complete, you stroke the path .
The result allows for drawing simple lines on a picture, which can then be saved, as we saw back in the view controller.
But is it possible to do more with this example? As usual, the answer is Yes.
If you wanted to expand this example into a more complete application, there are several routes you could take.
The first and most obvious expansion would be to select a color before drawing a line. The hard part here would be to create a color picker, though you could make a stand-alone class that you could then reuse elsewhere. With that in hand, it would be simple to add a color variable to your line arrays, probably by always saving it as the 0 element of a subarray.
The program might also benefit from a more sophisticated line-drawing algorithm that tosses out nearby points and smoothes the lines into curves, removing some of the sharp edges that show up in the current program.
In any case, that ends our look at Quartz 2D. There’s lots more you can learn, but you should have the foundation that you need to move forward.
There are two other ways that you can draw using the SDK: Core Animation and OpenGL. We don’t have the space in this introductory book to give full-length attention to either, but we’ll at least introduce them and show you where to go for more information, beginning with Core Animation.
Core Animation is a fundamental technology on the iPhone. It’s what manages all the nifty scrolls, pivots, zoom-ins, zoom-outs, and other bits of animation that make up the iPhone user interface. As you’ve already seen, many UIKit classes give you an option to use animation or not, usually by having an animated: argument as part of a method.
Core Animation is also tightly integrated with Quartz. As you’ve seen, each UIView is linked up to a graphical layer called the CALayer, which is the Core Animation layer. Though you’ve just used it to depict simple graphics and images so far, you can also use it to manage more complex changes.
But you don’t have to use Quartz at all to create animations. There’s a CALayer behind every UIView, and because almost everything on the iPhone is built on a UIView, you can animate your existing UIViews, possibly including pictures that you’ve loaded into UIImageViews. For example, figure 19.6 shows how you could use Core Animation to show an approaching plane, by moving its UIImageView and turning it opaque as it approaches.
This is the example that we’re going to show later in this section, using two different means to create the animation.
When we speak of animation using Core Animation, what we’re talking about is changing the properties of the CALayer and then smoothly animating those property changes. The CALayer class reference lists which properties can be animated; they include anchorPoint, backgroundColor, opacity, position, transform, and several others. This means that you can use Core Animation to animate the position of an object, its color, its transparency, and also its CGAffine transformations.
Before we get further into Core Animation, we want to talk about its fundamentals—those terms and ideas that you’ll meet throughout this section:
You can also create much more complex animations, such as redefining how implicit animations work, collecting animations into transactions, and building complex animation layer hierarchies. For more information, look at the “Core Animation Programming Guide” and the “Core Animation Cookbook,” both available from Apple.
To use Core Animation, make sure that you add Quartz Core, the framework required for animation, to your project. You’ll also want to include QuartzCore/QuartzCore.h, the main header file for Core Animation.
With that done, you’re now ready to try out the two simplest types of animation: a simple implicit animation and an explicit animation.
Implicit animations are the simplest type of animation, because they just require starting an animation block and then changing CALayer-level properties. Listing 19.12 shows a simple example of this, involving a UIImageView called plane that contains a clipart picture of a plane. The image starts out at the top-left corner of the screen with 25 percent opacity and moves downward while growing more opaque.
[UIView beginAnimations:nil context:NULL];
CGAffineTransform moveTransform
= CGAffineTransformMakeTranslation(200, 200);
[plane.layer setAffineTransform:moveTransform];
plane.layer.opacity = 1;
[UIView commitAnimations];
Between them, beginAnimations:context: and commitAnimations define an animation block.
Within the block, you set two properties to animate. setAffineTransform: is a special CALayer method that allows the setting of its transform property using an affine transform matrix, which you’re already familiar with; opacity is a more obvious property.
As soon as you close out the block, the animation begins. Your plane will move and grow more distinct. That’s all there is to it!
But sometimes an implicit animation won’t give you as much control as you want. That’s where explicit animations come in.
When you’re working with explicit animations, instead of defining a bunch of changes to a CALayer and executing them all, you define animations one by one using the CABasicAnimation class. Each of these animations can have its own value for duration, repeatCount, and numerous other properties. You then apply each animation to a layer separately, using the addAnimation:forKey: method.
Listing 19.13 executes an animation similar to the one shown in listing 19.12, but with more control.
CABasicAnimation *opAnim = [CABasicAnimation
animationWithKeyPath:@"opacity"];
opAnim.duration = 3.0;
opAnim.fromValue = [NSNumber numberWithFloat:.25];
opAnim.toValue= [NSNumber numberWithFloat:1.0];
opAnim.cumulative = YES;
opAnim.repeatCount = 2;
[plane.layer addAnimation:opAnim forKey:@"animateOpacity"];
CGAffineTransform moveTransform
= CGAffineTransformMakeTranslation(200, 200);
CABasicAnimation *moveAnim = [CABasicAnimation
animationWithKeyPath:@"transform"];
moveAnim.duration = 6.0;
moveAnim.toValue= [NSValue valueWithCATransform3D:
CATransform3DMakeAffineTransform(moveTransform)];
[plane.layer addAnimation:moveAnim forKey:@"animateTransform"];
This example is definitely longer than our implicit animation example, but you get to define the two animations with separate durations, which is the first step to creating a more beautiful and better-controlled animation. Note that you also make use of yet another way to change an affine transform matrix into a Transform3D matrix of the type used by Core Animation: the CATransform3DMakeAffineTransform function.
The code does include a bit of a kludge: to keep the plane opaque through the last 3 seconds, it keeps counting opacity up cumulatively, making it climb from 1.0 to 1.75 the second time through. A better solution would be to have created three key frames for opacity: .25 at 0 seconds, 1.00 at 3 seconds, and 1.00 at 6 seconds. That’s why you might want to use a key-frame animation of the sort we alluded to at the start of this section, rather than a basic animation.
These simple methods for using Core Animation can take you far. Look through the CALayer class reference for everything that you’re allowed to animate. For more details, read though the two Apple guides we pointed out.
Before we leave graphics entirely behind, there’s one other toolkit that we want to briefly touch on: OpenGL.
OpenGL is SGI’s standardized 2D and 3D graphical drawing language. The iPhone more specifically uses OpenGL ES, or OpenGL for Embedded Systems, which features a reduced API for use on devices like mobile phones. For full information on using OpenGL, you should pick up a book on the topic or read Apple’s “OpenGL ES Framework Reference,” which links to the most important documents available from Apple. We’re going to cover some of the general information you’ll need to access OpenGL through the iPhone OS.
The iPhone manages OpenGL through EAGL, a class that interfaces between the iPhone’s views and OpenGL’s drawing functions. It allows for the writing of OpenGL functions onto an EAGLView, which is the CAEAGL layer of a UIView, showing the same layer-based paradigm we met when using Core Animation.
To simplify your programming of OpenGL projects, Xcode supplies a standard template to use, which sets up all the OpenGL defaults for you. It’s the OpenGL ES Application template, the only Xcode template that we have yet to examine. This template includes all the basic setup of OpenGL, which is pretty extensive. That includes the setup of a timer, the creation of frame buffers, and the code needed to draw something. To do basic OpenGL programming, all you have to do is write your code into the drawView method of the EAGLView class.
Rather than giving a completely insufficient overview of this enormous library, we’ll instead point you toward a few bits of sample code. The OpenGL template comes complete with a rotating square as an example. There are also three OpenGL samples currently available from Apple: GLGravity shows simple OpenGL rendering related to accelerometer output, GLSprite demonstrates texturing, and GLPaint explores another way to allow finger painting.
These examples should be sufficient to get you started if you already have a strong basis in OpenGL and need to see how it’s integrated into the iPhone.
Graphics are one of the most important elements for making your iPhone projects look great. Not only does the iPhone OS support high-quality graphics, but it also gives you a wide variety of options, depending on the needs of your program.
Quartz 2D will be your main workhorse for most graphical programs. If you’re already familiar with the Canvas library for the web, you’ll see that Quartz is quite similar. You’ll be able to draw paths and use many graphical state variables to modify exactly how that path is painted. This chapter includes a pretty extensive look at Quartz.
Core Animation is an expansion to Quartz that was created for the iPhone. You’ve already seen it integrated into numerous programs native to the iPhone, and now you can use it yourself. Core Animation is built around the idea of automated animations: you tell it the endpoints, and Core Animation fills in the rest for you. Again this is much as you may have seen on the web, with the WebKit’s various styles of implicit and explicit animation. This chapter covers the basics of how to use the simpler forms of Core Animation.
OpenGL is a whole new graphics library that has been imported into the iPhone, much as SQLite is a third-party library that Apple made available to iPhone users. The difference here is that Apple has made OpenGL easier to use, thanks to the creation of the EAGL framework. Though this chapter suggests how to get started with OpenGL, the topic is large enough that you’ll need to pick up a book on OpenGL to fully explore the topic.
With graphics covered, there’s one last topic that we need to complete our SDK toolkit: the internet. How do you access the net and how do you make use of various protocols that will allow you access to the ever-growing social network of the web? That’s the topic of our last chapter, bringing this book full circle.
18.189.195.229