You set up a gradient by creating a CGShading object calling the function CGShadingCreateAxial or CGShadingCreateRadial, supplying the following parameters:
A CGColorSpace object that describe the color space for Quartz to use when it interprets the color component values your callback supplies.
Starting and ending points. For axial gradients, these are the starting and ending coordinates (in user space) of the axis. For radial gradients, these are the coordinates of the center of the starting and ending circles.
Starting and ending radii (only for a radial gradient) for the circles used to define the gradient area.
A CGFunction object, which you obtain by calling the function CGFunctionCreate, discussed later in this section. This callback routine must return a color to draw at a particular point.
Boolean values that specify whether to fill the area beyond the starting or ending points with a solid color.
The CGFunction object you supply to the CGShading creation functions contains a callbacks structure and all the information Quartz needs to implement your callback. Perhaps the trickiest part of setting up a CGShading object is creating the CGFunction object. When you call the function CGFunctionCreate, you supply the following:
A pointer to any data your callback needs.
The number of input values to your callback. Quartz requires that your callback takes one input value.
An array of floating-point values. Quartz supplies your callback with only one element in this array. An input value can range from 0, for the color at the start of the gradient, to 1, for the color at the end of the gradient.
The number of output values provided by your callback. For each input value, your callback must supply a value for each color component and an alpha value to designate opacity. (As of Mac OS X v10.3, you can create gradients using non-opaque colors.) The color component values are interpreted by Quartz in the color space you create and supply to the CGShading creation function. For example, if you are using an RGB color space, you supply the value 4 as the number of output values (R, G, B, and A).
An array of floating-point values that specify each of the color components and an alpha value.
A callbacks data structure that contains the version of the structure (set this field to 0), your callback for generating color component values, and an optional callback to release the data supplied to your callback in the info parameter. If you were to name your callback myCalculateShadingValues, it would look like this:
void myCalculateShadingValues (void *info, const float *in, float *out)
After you create the CGShading object, you can set up additional clipping if you need to do so. Then, call the function CGContextDrawShading to paint the clipping area of the context with the gradient. When you call this function, Quartz invokes your callback to obtain color values that span the range from the starting point to the ending point.
When you no longer need the CGShading object, you release it by calling the function CGShadingRelease.
“Painting an Axial Gradient Using a CGShading Object” and “Painting a Radial Gradient Using a CGShading Object” provide step-by-step instructions on writing code that uses a CGShading object to draw a gradient.
Painting an Axial Gradient Using a CGShading Object
Painting a Radial Gradient Using a CGShading Object
Axial and radial gradients require you to perform similar steps. This example shows draw an axial gradient using a CGShading object, create a semicircular clipping path in a graphics context, then paint the gradient to the clipped context to achieve the output shown in Figure 8-11.
To paint the axial gradient shown in the figure, follow these steps:
You can compute color values any way you’d like, as long as your color computation function takes three parameters:
void *info. This is NULL or a pointer to data you pass to the CGShading creation function.
const float *in. Quartz passes the in array to your callback. The values in the array must be in the input value range defined for your CGFunction object. For this example, the input range is 0 to 1; see Listing 8-7.
float *out. Your callback passes the out array to Quartz. It contains one element for each color component in the color space, and an alpha value. Output values should be in the output value range defined for your CGFunction object. For this example, the output range is 0 to 1; see Listing 8-7.
For more information on these parameters, see CGFunctionEvaluateCallback.
Listing 8-6 shows a function that computes color component values by multiplying the values defined in a constant array by the input value. Because the input value ranges from 0 through 1, the output values range from black (for RGB, the values 0, 0, 0), through (1, 0, .5) which is a purple hue. Note that the last component is always set to 1, so that the colors are always fully opaque.
Listing 8-6 A function that computes color component values
static void myCalculateShadingValues (void *info, |
const float *in, |
float *out) |
{ |
float v; |
size_t k, components; |
static const float c[] = {1, 0, .5, 0 }; |
components = (size_t)info; |
v = *in; |
for (k = 0; k < components -1; k++) |
*out++ = c[k] * v; |
*out++ = 1; |
} |
After you write your callback to compute color values, you package it as part of a CGFunction object. It’s the CGFunction object you supply to Quartz when you create a CGShading object. Listing 8-7 shows a function that creates a CGFunction object that contains the callback from Listing 8-6. A detailed explanation for each numbered line of code appears following the listing.
Listing 8-7 A function that creates a CGFunction object
static CGFunctionRef myGetFunction (CGColorSpaceRef colorspace)// 1 |
{ |
size_t components; |
static const float input_value_range [2] = { 0, 1 }; |
static const float output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 }; |
static const CGFunctionCallbacks callbacks = { 0,// 2 |
&myCalculateShadingValues, |
NULL }; |
components = 1 + CGColorSpaceGetNumberOfComponents (colorspace);// 3 |
return CGFunctionCreate ((void *) components, // 4 |
1, // 5 |
input_value_range, // 6 |
components, // 7 |
output_value_ranges, // 8 |
&callbacks);// 9 |
} |
Here’s what the code does:
Takes a color space as a parameter.
Declares a callbacks structure and fills it with the version of the structure (0), a pointer to your color component calculation callback, and NULL for the optional release function.
Calculates the number of color components in the color space and increments the value by 1 to account for the alpha value.
Passes a pointer to the components value. This value is used by the callback myCalculateShadingValues.
Specifies that 1 is the number of input values to the callback.
Provides an array that specifies the valid intervals for the input. This array contains 0 and 1.
Passes the number of output values, which is the number of color components plus alpha.
Provides an array that specifies the valid intervals for each output value. This array specifies, for each component, the intervals 0 and 1. Because there are four components, there are eight elements in this array.
Passes a pointer to the callback structure declared and filled previously.
To create a CGShading object, you call the function CGShadingCreateAxial, as shown in Listing 8-8, passing a color space, starting and ending points, a CGFunction object, and a Boolean value that specifies whether to fill the area beyond the starting and ending points of the gradient.
Listing 8-8 Code that sets up a CGShading object for an axial gradient
CGPoint startPoint, |
endPoint; |
CGFunctionRef myFunctionObject; |
CGShadingRef myShading; |
startPoint = CGPointMake(0,0.5); |
endPoint = CGPointMake(1,0.5); |
colorspace = CGColorSpaceCreateDeviceRGB(); |
myFunctionObject = myGetFunction (colorspace); |
myShading = CGShadingCreateAxial (colorspace, |
startPoint, endPoint, |
myFunctionObject, |
false, false); |
When you paint a gradient, Quartz fills the current context. This is different from working with colors and patterns, which are used to stroke and fill path objects. As a result, if you want your gradient to appear in a particular shape, you need to clip the context accordingly. The code in Listing 8-9 adds a semicircle to the current context so that the gradient is painted into that clip area, as shown in Figure 8-11.
If you look carefully, you’ll notice that the code should result in a half circle, whereas the figure shows a half ellipse. Why? You’ll see, when you look at the entire routine in “A Complete Routine for an Axial Gradient Using a CGShading Object,” that the context is also scaled. More about that later. Although you might not need to apply scaling or a clip in your application, these and many other options exist in Quartz 2D to help you achieve interesting effects.
Listing 8-9 Code that adds a semicircle clip to the graphics context
CGContextBeginPath (myContext); |
CGContextAddArc (myContext, .5, .5, .3, 0, |
my_convert_to_radians (180), 0); |
CGContextClosePath (myContext); |
CGContextClip (myContext); |
Call the function CGContextDrawShading to fill the current context using the color gradient specified in the CGShading object:
CGContextDrawShading (myContext, myShading); |
You call the function CGShadingRelease when you no longer need the CGShading object. You also need to release the CGColorSpace object and the CGFunction object as shown in Listing 8-10.
Listing 8-10 Releasing objects
CGShadingRelease (myShading); |
CGColorSpaceRelease (colorspace); |
CGFunctionRelease (myFunctionObject); |
The code in Listing 8-11 shows a complete routine that paints an axial gradient, using the CGFunction object set up in Listing 8-7 and the callback shown in Listing 8-6. A detailed explanation for each numbered line of code appears following the listing.
Listing 8-11 A routine that paints an axial gradient using a CGShading object
void myPaintAxialShading (CGContextRef myContext,// 1 |
CGRect bounds) |
{ |
CGPoint startPoint, |
endPoint; |
CGAffineTransform myTransform; |
float width = bounds.size.width; |
float height = bounds.size.height; |
startPoint = CGPointMake(0,0.5); // 2 |
endPoint = CGPointMake(1,0.5);// 3 |
colorspace = CGColorSpaceCreateDeviceRGB();// 4 |
myShadingFunction = myGetFunction(colorspace);// 5 |
shading = CGShadingCreateAxial (colorspace, // 6 |
startPoint, endPoint, |
myShadingFunction, |
false, false); |
myTransform = CGAffineTransformMakeScale (width, height);// 7 |
CGContextConcatCTM (myContext, myTransform);// 8 |
CGContextSaveGState (myContext);// 9 |
CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1));// 10 |
CGContextSetRGBFillColor (myContext, 1, 1, 1, 1); |
CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1)); |
CGContextBeginPath (myContext);// 11 |
CGContextAddArc (myContext, .5, .5, .3, 0, |
my_convert_to_radians (180), 0); |
CGContextClosePath (myContext); |
CGContextClip (myContext); |
CGContextDrawShading (myContext, shading);// 12 |
CGColorSpaceRelease (colorspace);// 13 |
CGShadingRelease (shading); |
CGFunctionRelease (myShadingFunction); |
CGContextRestoreGState (myContext); // 14 |
} |
Here’s what the code does:
Takes as parameters a graphics context and a rectangle to draw into.
Assigns a value to the starting point. The routine calculates values based on a user space that varies from 0 to 1. You’ll scale the space later for the window the Quartz draws into. You can think of this coordinate location as x at the far left side and y at 50% from the bottom.
Assigns a value to the ending point. You can think of this coordinate location as x at the far right side and y at 50% from the bottom. As you can see, the axis for the gradient is a horizontal line.
Creates a color space for device RGB because this routine draws to the display.
Creates a CGFunction object by calling the routine shown in Listing 8-7 and passing the color space you just created.
Creates a CGShading object for an axial gradient. The last two parameters are false, to signal that Quartz should not fill the area beyond the starting and ending points.
Sets up an affine transform that is scaled to the height and width of the window used for drawing. Note that the height is not necessarily equal to the width. In this example, because the two aren’t equal, the end result is elliptical rather than circular.
Concatenates the transform you just set up with the graphics context passed to the routine.
Saves the graphics state to enable you to restore this state later.
Sets up a clipping area. This and the next two lines of code clip the context to a rectangle that is filled with white. The effect is that the gradient is drawn to a window with a white background.
Creates a path. This and the next three lines of code set up an arc that is half a circle and adds it to the graphics context as a clipping area. The effect is that the gradient is drawn to an area that is half a circle. However, the circle will be transformed by the height and width of the window (see step 8), resulting in a final effect of a gradient drawn to a half ellipse. As the window is resized by the user, the clipping area is resized.
Paints the gradient to the graphics context, transforming and clipping the gradient as described previously.
Releases objects. This and the next two lines of code release all the objects you created.
Restores the graphics state to the state that existed before you set up the filled background and clipped to half a circle. The restored state is still transformed by the width and height of the window.
This example shows how to use a CGShading object to produce the output shown in Figure 8-12.
To paint a radial gradient, follow these steps:
There is no difference between writing functions to compute color values for radial and axial gradients. In fact, you can follow the instruction outlined in “Set Up a CGFunction Object to Compute Color Values.” Listing 8-12 calculates color so that the color components vary sinusoidally, with a period based on frequency values declared in the function. The result seen in Figure 8-12 is quite different from the colors shown in Figure 8-11. Despite the differences in color output, the code in Listing 8-12 is similar to Listing 8-6 in that each function follows the same prototype. Each function takes one input value and calculates N values, one for each color component of the color space plus an alpha value.
Listing 8-12 A function that computes color component values
static void myCalculateShadingValues (void *info, |
const float *in, |
float *out) |
{ |
size_t k, components; |
double frequency[4] = { 55, 220, 110, 0 }; |
components = (size_t)info; |
for (k = 0; k < components - 1; k++) |
*out++ = (1 + sin(*in * frequency[k]))/2; |
*out++ = 1; // alpha |
} |
Recall that after you write a color computation function, you need to create a CGFunction object, as described in “Set Up a CGFunction Object to Compute Color Values.”
To create a CGShading object or a radial gradient, you call the function CGShadingCreateRadial, as shown in Listing 8-13, passing a color space, starting and ending points, starting and ending radii, a CGFunction object, and Boolean values to specify whether to fill the area beyond the starting and ending points of the gradient.
Listing 8-13 Code that sets up a CGShading object for a radial gradient
CGPoint startPoint, endPoint; |
float startRadius, endRadius; |
startPoint = CGPointMake(0.25,0.3); |
startRadius = .1; |
endPoint = CGPointMake(.7,0.7); |
endRadius = .25; |
colorspace = CGColorSpaceCreateDeviceRGB (); |
myShadingFunction = myGetFunction (colorspace); |
CGShadingCreateRadial (colorspace, |
startPoint, |
startRadius, |
endPoint, |
endRadius, |
myShadingFunction, |
false, |
false); |
Calling the function CGContextDrawShading fills the current context using the specified color gradient specified in the CGShading object.
CGContextDrawShading (myContext, shading); |
Notice that you use the same function to paint a gradient regardless of whether the gradient is axial or radial.
You call the function CGShadingRelease when you no longer need the CGShading object. You also need to release the CGColorSpace object and the CGFunction object as shown in Listing 8-14.
Listing 8-14 Code that releases objects
CGShadingRelease (myShading); |
CGColorSpaceRelease (colorspace); |
CGFunctionRelease (myFunctionObject); |
The code in Listing 8-15 shows a complete routine that paints a radial gradient using the CGFunction object set up in Listing 8-7 and the callback shown in Listing 8-12. A detailed explanation for each numbered line of code appears following the listing.
Listing 8-15 A routine that paints a radial gradient using a CGShading object
void myPaintRadialShading (CGContextRef myContext,// 1 |
CGRect bounds); |
{ |
CGPoint startPoint, |
endPoint; |
float startRadius, |
endRadius; |
CGAffineTransform myTransform; |
float width = bounds.size.width; |
float height = bounds.size.height; |
startPoint = CGPointMake(0.25,0.3); // 2 |
startRadius = .1; // 3 |
endPoint = CGPointMake(.7,0.7); // 4 |
endRadius = .25; // 5 |
colorspace = CGColorSpaceCreateDeviceRGB(); // 6 |
myShadingFunction = myGetFunction (colorspace); // 7 |
shading = CGShadingCreateRadial (colorspace, // 8 |
startPoint, startRadius, |
endPoint, endRadius, |
myShadingFunction, |
false, false); |
myTransform = CGAffineTransformMakeScale (width, height); // 9 |
CGContextConcatCTM (myContext, myTransform); // 10 |
CGContextSaveGState (myContext); // 11 |
CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1)); |
CGContextSetRGBFillColor (myContext, 1, 1, 1, 1); // 12 |
CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1)); |
CGContextDrawShading (myContext, shading); // 13 |
CGColorSpaceRelease (colorspace); // 14 |
CGShadingRelease (shading); |
CGFunctionRelease (myShadingFunction); |
CGContextRestoreGState (myContext); // 15 |
} |
Here’s what the code does:
Takes as parameters a graphics context and a rectangle to draw into.
Assigns a value to the center of the starting circle. The routine calculates values based on a user space that varies from 0 to 1. You’ll scale the space later for the window Quartz draws into. You can think of this coordinate location as x at 25% from the left and y at 50% from the bottom.
Assigns the radius of the starting circle. You can think of this as 10% of the width of user space.
Assigns a value to the center of the ending circle. You can think of this coordinate location as x at 70% from the left and y at 70% from the bottom.
Assigns the radius of the ending circle. You can think of this as 25% of the width of user space. The ending circle will be larger than the starting circle. The conical shape will be oriented from left to right, tipped upwards.
Creates a color space for device RGB because this routine draws to the display.
Creates a CGFunctionObject by calling the routine shown in Listing 8-7 and passing the color space you just created. However, recall that you’ll use the color calculation function shown in Listing 8-12.
Creates a CGShading object for a radial gradient. The last two parameters are false, to signal that Quartz should not fill the area beyond the starting and ending points of the gradient.
Sets up an affine transform that is scaled to the height and width of the window used for drawing. Note that the height is not necessarily equal to the width. In fact, the transformation will change whenever the user resizes the window.
Concatenates the transform you just set up with the graphics context passed to the routine.
Saves the graphics state to enable you to restore this state later.
Sets up a clipping area. This and the next two lines of code clip the context to a rectangle that is filled with white. The effect is that the gradient is drawn to a window with a white background.
Paints the gradient to the graphics context transforming the gradient as described previously.
Releases object. This and the next two lines of code release all the objects you created.
Restores the graphics state to the state that existed before you set up the filled background. The restored state is still transformed by the width and height of the window.
Last updated: 2007-12-11