Chapter 13. Shadings and Patterns

The shadings and patterns in Quartz 2D are powerful tools for creating popular graphic effects. The first, shadings, allows you to create a smooth blend of color based on a function that you specify. Figure 13.1 presents examples of several shadings.

Shading Examples

Figure 13.1. Shading Examples

Figure 13.1 uses some paths as clipping areas and drawn shadings inside of them. Out of necessity, all of the shadings in Figure 13.2 use grays. They could, just as easily, be in any color space that Quartz 2D supports.

An Example of a Pattern and Its Cell

Figure 13.2. An Example of a Pattern and Its Cell

A pattern is an efficient way to draw a single, repeated image. Your application draws a single, rectangular cell and Quartz 2D will tile that pattern on the drawing plane for you. You can transform the cell so your patterns can tile with just about any parallelogram. Figure 13.2 shows a rectangle filled with a simple pattern and the cell that created it.

Quartz 2D can generate the same effects that result from drawing patterns and shadings in other ways. An axial (a.k.a. linear) shading might be approximated with a series of multi-colored lines or rectangles. Careful use of transformations and a lot of drawing could easily replace a pattern. However, Quartz can draw shadings and patterns much more efficiently than either of these approximations.

For example, reproducing a pattern by drawing hundreds of copies of a graphic into a PDF context would lead to each copy adding to the size of the PDF. The PDF specification defines its own pattern primitive, which allows it to store a single copy of the pattern cell and some information about how to apply that cell to create the pattern. When drawing shading with lines or rectangles to a bitmap display, the drawing will likely show obvious bands of color instead of a smooth gradient. Quartz 2D can draw the shading at the pixel level and make the colors change smoothly.

This chapter explores both shadings and patterns and examines how to create the Core Graphics objects that represent these drawing elements, as well as discusses some of the performance issues associated with using them.

Shadings

Shadings are much easier to understand than they are to describe. The shadings in Figure 13.1 should give you some idea of what they look like. In general shadings are a smoothly blended field of color. Quartz 2D supports two shapes for the fields, axial and radial. An axial shading provides linear bands of color that run perpendicular to a line. Radial shadings are formed as smooth blends between two circles. Figure 13.3 shows the anatomy of linear and a radial shadings.

Anatomy of Axial and Radial Shadings

Figure 13.3. Anatomy of Axial and Radial Shadings

The shape of the axial shading on the left is defined by the start and end points of the line indicated. Note that the bands of color extend to the edges of the context perpendicular to the defining line. The radial shading on the right is defined by two circles. The colors of the shading are drawn as if the circle were blended from the start to the end. The circular shading shown is a bit unusual. Most of the time, the smaller circle would be inside the larger one, but Quartz is not limited to that condition alone.

In both shadings, Quartz 2D obtains the colors for the shading from the application. The library passes the function a parameter that indicates a position between the starting point and the ending point of the blend that it needs the color for. The function returns the color that Quartz should use in the blend at that position.

The process of creating shading is relatively straightforward. An instance of the CGFunction abstract data type represents the color function for the shading. This object becomes part of an instance of the CGShading opaque data type. That shading object also encodes information about the shape of the shading and the locations and sizes of its starting and ending shapes. Drawing the shading is as easy as calling CGContextDrawShading and passing the shading object as a parameter.

Creating the Color Function

The first step in creating shading is defining the color function. Unfortunately, if you read the description of the CGFunction opaque data type in the header file, you may find yourself lost in a sea of mathematical rigor. The actual idea that a CGFunctionRef embodies is actually pretty simple.

CGFunction is actually a generic mechanism for representing any function that takes an arbitrary number of floating point inputs and returns an arbitrary number of floating point outputs. When you create a CGFunction object, you indicate how many inputs and how many outputs your function will have. You also provide valid ranges for the input and output values.

The part of the CGFunctionRef that performs the color calculations is a callback routine in your application that has the following signature:

void CGFunctionEvaluateCallback(void *info, const float *in, float *out)

The first parameter to this routine is a block of arbitrary information that your application defines for its own purposes while evaluating the function. The second and third parameters represent C-style arrays of floating point values. These arrays are where your function receives its input values and stores the results of the function. In the case of the color function for a shading, the function you create takes a single input, the position between the start and end of the blend, and returns the components of a color. The number of color components the function must return depends on the color space the shading draws in. For example in an RGB shading, the function will return four values, three for the color channels and one for the alpha value.

Note

Creating the Color Function

Shadings can have partially transparent areas if the color function simply specifies an alpha value in the color returned by the color function. Unfortunately, many printers have trouble printing graphics containing alpha channels, including shadings. If you run into this problem in your own application, you can work around the problem by drawing the shading into an image and drawing that image in the place of the shading. Unfortunately, this representation of the shading will draw with a fixed resolution.

If the function accepts a block of user data, the CGFunction object can keep track of another callback to dispose of the data when the CGFunction is no longer needed. That callback has the signature

void CGFunctionReleaseInfoCallback(void *info)

The system will call not call this routine until it destroys the CGFunction object. This gives the application the opportunity to clean up any resources stored in that data.

All of these input values come together in a call to CGFunctionCreate. Listing 13.1 shows how you might create a CGFunction for supplying the color values of an RGB shading.

Example 13.1. Creating a CGFunction for RGB Shadings

// Tell the OS that our function expects one input value,
// in the domain [0,6], and returns values in the range [0,1]
const float kValidDomain[2] = { 0, 6 };
const short kChannelsPerColor = 4; // R, G, B, A
const float kValidRange[kChannelsPerColor][2] = {
                           { 0, 1 },
                           { 0, 1 },
                           { 0, 1 },
                           { 0, 1 } };

CGFunctionCallbacks kRGBShadingCallbackRoutines =
{
    0,                     // Callback Structure Version
    RainbowShading,        // Evaluation Callback
    NULL                   // Release User Data Callback
};
CGFunctionRef rainbowShading =
    ::CGFunctionCreate(
           NULL,
           1,
           kValidDomain,
           kChannelsPerColor,
           (float *) kValidRange,
           &kRGBShadingCallbackRoutines);

The constants at the top of Listing 13.1 define the logistics of the function you’re creating. The function will accept a single input, and kValidDomain defines the valid range for that input. For reasons that will become clear, you need to define the function so that the input extends from 0 at the start of the shading to 6.0 at its end. As mentioned previously, the function will return four output values representing an RGB color with Alpha. The kValidRange array provides the valid ranges for each output value. Because these represent the color components of a Quartz color, they fall in the range 0 to 1. The callbacks passed to a CGFunction are collected into a CGFunctionCallbacks structure. The first field of this structure is a version placeholder that allows Apple to change the definition of this structure in the future. In this case structure version 0 is being used. The next field is the function evaluation callback. RainbowShading is the name of a routine that computes the shading color. This shading function doesn’t pass application data to the evaluation routine, so the callback that releases the info data structure is not used.

The routine CGFunctionCreate builds the final object from all of these elements. The first parameter to CGFunctionCreate is the application-specific information pointer. Because it’s unused, NULL is passed. The argument is the number of input parameters the function expects and the valid range for those parameters. Following this, the routine call specifies the number of output values and their valid domains. The final parameter, of course, is the evaluation routine.

The CGFunctionRef just created uses the RainbowShading callback to evaluate the function. That routine is given in Listing 13.2.

Example 13.2. A Sample Evaluation Callback

void RainbowShading(void *info, const float *in, float *out)
{
   // colors of the knots in parameter space
   static float const shadingColors[7][4] = {
           { 1.0, 0.0, 0.0, 1.0}, // red
           { 1.0, 0.5, 0.0, 1.0}, // orange
           { 1.0, 1.0, 0.0, 1.0}, // yellow
           { 0.0, 1.0, 0.0, 1.0}, // green
           { 0.0, 0.0, 1.0, 1.0}, // blue
           { 0.5, 0.0, 1.0, 1.0}, // indigo
           { 0.5, 0.0, 0.5, 1.0}, // violet
   };

   // Retrieve the function's input (a single float in the
   // domain [0,6])
   float parameterValue = *in;

   // Use the input to find the index of the color on the left side
   // of the current sub-interval
   int colorIndex = (int)parameterValue;
   if(colorIndex == 6) {
           colorIndex = 5;
   }

   // We subtract of the integer part and get a sub-interval scale
   // between 0 and 1.0
   float t = parameterValue - colorIndex;

   // Use all those calculations to create a color value that is the
   // result of this function. We copy each component into the function
   // result array.
   for(unsigned short ctr = 0; ctr < 4; ctr++) {
           out[ctr] =
                   (1.0 - t) * shadingColors[colorIndex][ctr] +
                   (t) * shadingColors[colorIndex + 1][ctr];
   }
}

This routine defines a color sequence that has all the colors of the rainbow. The routine begins with an array of color values that will make the shading. The input parameter will be between 0 and 6 because that’s the way the function was set up when it was created. This value is used to select the interval inside of the color array and the position within that interval that the code is looking for. The loop at the end of the array calculates an interpolated value for each component of the color to be returned. This particular shading example uses a fixed array of colors, if the array of source colors was passed in as part of the info parameter, but the code could create many function objects that use the same callback to generate shadings with many different colors.

Note

listingsA Sample Evaluation CallbackA Sample Evaluation Callback

When Quartz sends your shading object to a PDF, it tries to convert your shading function into a representation that can be stored in the file directly. Unfortunately, it is possible for your application to create functions that are too complex for this mechanism. If you are drawing a shading into a PDF and find that the colors in the PDF do not match your color function, you may have a function that is too complex. If you run into this problem, one potential solution is to render the shading into an image or CGLayer and then draw that into the PDF.

Creating Shadings

With the CGFunction in hand, the next step in drawing a shading is to create the shading object itself. Quartz 2D includes separate routines for creating axial and radial shadings. The primary difference in the parameters between the two routines is the geometrical information that defines where the shading will draw in the context.

There is another option that affects the way Quartz 2D draws shadings that has not yet been discussed. As part of creating a shading, you indicate whether or not you want the colors of the shading to extend beyond its end points. You extend the blend on the starting and ending sides independently. Figure 13.4 shows the effect of extending colors for both shading shapes.

Extending Axial and Radial Shadings

Figure 13.4. Extending Axial and Radial Shadings

The shadings in Figure 13.4 are drawn on top of a context that is filled with a checkerboard pattern. The shadings on the left do not have the shadings extended. The shadings stop at the bounds of their defining geometry. The shadings on the right include extensions. The colors at either end of the shading extend as far as they can filling the context and obscuring the checkered background.

Axial Shadings

As seen earlier, Quartz defines the geometry of an axial shading with a line segment that runs perpendicular to the colors in the shading. Correspondingly, when you create the shading, you supply the endpoints of that line. In addition, you define the color space that the shading will use and the function that returns colors in that space. A complete example of creating a shading is shown in Listing 13.3.

Example 13.3. Creating an Axial Shading

CGRect shadingStage = mediaBox;
CGPoint startPoint = CGPointMake(
    shadingStage.origin.x + (shadingStage.size.width / 4),
    shadingStage.origin.y + (shadingStage.size.height / 4));
CGPoint endPoint = CGPointMake(
    CGRectGetMaxX(shadingStage) - (shadingStage.size.width / 4),
    CGRectGetMaxY(shadingStage) - (shadingStage.size.height / 4));

CGColorSpaceRef colorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);

CGShadingRef shadingObject =
    CGShadingCreateAxial(colorSpace, startPoint,
                           endPoint, rampShading,
                           false, false);

CGColorSpaceRelease(colorSpace);
colorSpace = NULL;

This code creates the unextended axial shading in Figure 13.4. The code begins by selecting a start and end points for the shading based on the bounding box of the illustration. It then creates the generic gray color space used by this particular shading. The routine that creates the shading object itself is CGShadingCreateAxial. The color space and endpoints are accompanied by the CGFunctionRef, named rampShading, and two boolean parameters. The booleans ask Quartz not to extend the colors of the shading.

Radial Shadings

Creating a radial works the same way as creating an axial shading. The only difference is that the defining geometry is two circles instead of a single line. Each of the circles is defined by a center point and a radius. Listing 13.4 is the code that created the shadings for the unextended radial portion of Figure 13.4.

Example 13.4. Creating a Radial Shading

CGColorSpaceRef colorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
CGShadingRef shadingObject =
    CGShadingCreateRadial(
           colorSpace,
           CGPointMake(50, 50), 10,
           CGPointMake(50, 50), 50,
           rampShading, false, false);

This listing is much shorter than the axial sample, primarily because the geometry is easier to create. The two circles that define the shading share a center point, and only their radii change. The starting circle uses a radius of 10, while the ending circle uses a radius of 50. Beyond that, the parameters for CGShadingCreateRadial are identical to those used in Listing 13.3.

Drawing Shadings

Drawing a shading is a very simple operation after you’ve created it. To draw a shading you simply call the CGContext routine CGContextDrawShading. This routine takes two parameters, a context, and the shading you want to draw into it.

If you look at the shadings in Figure 13.4, you will notice that they fill the context, particularly those where you’ve indicated that the colors should extend beyond the edges of the shading’s geometry. Unless you are using shading as a background, you will probably set up a clipping path first and then draw the shading inside of that.

Patterns

In many ways, creating patterns is similar to creating shadings. Quartz 2D patterns are constructed from a single seed image called a pattern cell. The pattern is an efficient mechanism for repeating that cell on a regularly spaced grid to color an area of the graphics context. The application controls the contents of the cell and the spacing of the cells in both the horizontal and vertical directions. Patterns are drawn in their own coordinate system, and you can also control the mapping between that coordinate system and user space. Your pattern cells can contain any Quartz 2D graphics including images.

Quartz 2D allows you to create two different kinds of patterns, a colored pattern and a stencil pattern. The computer can draw a colored pattern with no additional information other than the colors specified by the cell’s image. With a stencil pattern, the pattern cell defines a shape but contains no color information. In this sense it is very much like a resolution independent image mask. When drawing a stencil pattern, your application must supply a color for the pattern to be drawn with.

One challenging thing to grasp about patterns is the fact that Quartz 2D treats a pattern as a custom color in a very specialized color space when drawing. In that sense, a pattern is a lot like the polka-dotted paint that cartoon characters sometimes use. Regardless of where they apply that paint, the polka dot pattern is always reproduced faithfully. Because Quartz 2D treats patterns as special kinds of colors, Quartz 2D can apply them when stroking and filling graphics.

This chapter explores all these characteristics of patterns. You will learn how to use Quartz 2D to create patterns and use them in your drawings.

Cells, Spacing, and Transformations

A cell is simply a rectangular drawing space that contains Quartz 2D graphics. For colored patterns, the cell’s graphic will define both color and shape information. For a stencil pattern, the cell’s graphic includes shapes but no colors. Figure 13.5 contains a very simple example of a cell that contains some basic line art.

A Simple Cell

Figure 13.5. A Simple Cell

In this figure, and in some of the figures to follow, a dotted line has been added that shows where the boundary of the cell is. In this case the bounding rectangle of the cell is 48 points square. For this example, and cell, a color cell is used.

If you asked Quartz 2D to draw this cell in a pattern with a cell spacing of 48 points in the x and y directions, the results might look something like this

Pattern Drawn with 48 Point Spacing

Figure 13.6. Pattern Drawn with 48 Point Spacing

The left side of this illustration shows the cells with their boundaries marked. The rectangle on the right shows how Quartz 2D might draw this pattern when it is used to fill a rectangle.

One of the drawing options you can change when you create a pattern is the spacing of the cells. You can change the horizontal and vertical spacing of the pattern’s cell independently. If you ask Quartz to draw the pattern with a horizontal spacing of 72 points and a vertical spacing of 60 points, the resulting pattern might look something like this

Pattern Drawn with a Different Spacing

Figure 13.7. Pattern Drawn with a Different Spacing

The computer takes the additional spacing and uses it to insert blank space between the pattern cells.

In this example, the spacing is the same size as the cell’s height and width, but you could just as easily use values that are smaller. If the graphics of adjacent cells overlap, Mac OS X will still draw the pattern to the best of its ability. However, the order in which Quartz draws the pattern is undefined. The effect you get from drawing an overlapping pattern to the screen may not match the effect of drawing that pattern to a PDF or the printer.

In addition to changing the spacing of a pattern, you can also apply a transformation to it. The tiled graphic of a pattern is drawn in its own coordinate space, the pattern space. A Quartz 2D application can supply an affine transform that defines how the computer maps the tiled graphic from pattern space to user space. You can use this to achieve patterns where the tiling consists of just about any parallelogram. If you draw the tiling of Figure 13.6 into a rectangle, applying a transformation between pattern space and user space at the same time, the results might look something like Figure 13.8.

The Pattern Drawn with a Transformation

Figure 13.8. The Pattern Drawn with a Transformation

When you draw a pattern with a transformation, the mathematics involved can introduce small errors into the spacing of a pattern, particularly when working with bitmap contexts. Quartz can compensate for these spacing errors by slightly warping the cells in-place within the pattern. Your application has some control over whether or not the computer makes this trade-off. Quartz 2D defines three constants that affect the spacing versus shape trade-off.

  • kCGPatternTilingNoDistortion -. This option asks Quartz to keep the shape of the pattern cells consistent as possible. This option comes with a cost of the pattern spacing. Some cells may be off by as much as a pixel in either direction.

  • kCGPatternTilingConstantSpacingMinimalDistortion -. With this option, the computer keeps the spacing of cells constant but might have to distort some of the cells (again, by as much as a pixel in either direction).

  • kCGPatternTilingConstantSpacing -. This option is very similar to the previous one. This option asks Quartz 2D to keep the spacing constant, but it does not perform the rigorous calculations needed to keep the distortion of the cells to a minimum. As a result, this option will allow the computer to draw your pattern more quickly, but individual cells may be distorted more than if you had used kCGPatternTilingConstantSpacingMinimalDistortion.

Colored and Stencil Patterns

In introducing patterns the difference between colored and stencil patterns were briefly discribed. A color pattern is one where the color of the cell is defined as part of that cell. In a stencil pattern, your cell defines the shapes that you want to draw, but all of the shapes in the cell are drawn in the same color. You supply the color when you draw the pattern, not when the cell is created.

The cell defined in Figure 13.5 is a color pattern cell. The cell itself contains two colors, a gray and black. The figures that follow show the effects of using this cell in a colored pattern.

Figure 13.9 shows a stencil cell and several rectangles that are filled with a stencil pattern using that cell. In each rectangle is drawn the stencil pattern with a different color. Each rectangle was drawn using the same pattern, with just a different color supplied each time pattern was drawn.

A Stencil Pattern Drawn with Several Colors

Figure 13.9. A Stencil Pattern Drawn with Several Colors

Creating Patterns

Quartz 2D keeps track of all the information needed to draw a pattern in an instance of the CGPattern opaque data type. To draw a pattern, you crate an instance of this opaque data type by calling CGPatternCreate and supplying all the relevant options.

The Pattern Cell Callback

A callback routine provides the graphics for a pattern cell. The callback has the routine signature

void DrawPatternCellCallback(void *info, CGContextRef cgContext);

Like the callback for a CGFunction, the pattern cell callback accepts a pointer to a block of data that the application can use for its own purposes. The second parameter is a CGContext. The callback draws the graphics of the cell into that context it receives in the second parameter. As is the custom in Quartz 2D, when the system passes in the context, the origin is at the lower left corner of the cell. As usual, however, transformations can relocate the origin to make it easier to draw into the cell.

The pattern also can store a pointer to the routine that disposes of the application specific info when the pattern gets destroyed. The signature of that function is

void ReleasePatternInfoCallback(void *info);

When you create a CGPattern, you provide these callbacks to Quartz 2D by packing the routine pointers into an instance of the CGPatternCallbacks structure. This structure also has a field that indicates the version of the structure you are using.

Putting It Together

Listing 13.5 contains the code used to create the pattern drawn in Figure 13.2 at the beginning of the chapter.

Example 13.5. A CGPatternCreate Sample

const float kPatternWidth = 100;
const float kPatternHeight = 100;
const bool  kPatternIsColored = true;

const CGRect patternBounds =
        CGRectMake(0, 0, kPatternWidth, kPatternHeight);
const CGPatternCallbacks kPatternCallbacks =
{
        0,
        DrawCirclesAndTriangle,
        NULL
};

CGAffineTransform patternTransform = CGAffineTransformIdentity;
patternTransform = CGAffineTransformMakeRotation( -pi / 4.0);
patternTransform = CGAffineTransformScale(patternTransform,
        0.33333, 0.5);
patternTransform = CGAffineTransformRotate(patternTransform,
        pi / 4.0);

CGPatternRef pattern = CGPatternCreate(
        NULL,
        patternBounds,
        patternTransform,
        kPatternWidth, // used as horizontal spacing
        kPatternHeight,// used as vertical spacing
        kCGPatternTilingNoDistortion,
        kPatternIsColored,
        &kPatternCallbacks);

There are many constants in the code above which can lead to the impression that it is more complex than it is. The focus of the listing is the call to CGPatternCreate at the end. The first parameter is the application specific info data that Quartz will pass to your pattern callback. That info is not used in this sample, so NULL is passed in. The second parameter is a rectangle that defines the bounds of the pattern cell. The third parameter is the CGAffineTransform that maps the pattern’s graphics from pattern space to user space. In this case a simple transformation that skews the graphic slightly is passed.

The pattern width and pattern height are used as the spacing parameters. This corresponds to the options used to draw Figure 13.6. You pass the spacing constant that asks Quartz to ensure your shapes are not distorted, at the expense of potential spacing inaccuracies.This pattern procedure is going to draw a colored cell, so you create a colored pattern instead of a stencil pattern.

The last parameter, of course, is the structure that describes the pattern callbacks. The routine that draws the cell in this case is DrawCirclesAndTriangle.

Drawing with Patterns

Quartz 2D treats patterns as special types of color. Once you have created a pattern, setting up a context to draw it is very similar to setting up the context to draw a color. Patterns “live” in special pattern color spaces. The first step in drawing a pattern is to create a pattern color space. You can use that pattern color space as the stroke or fill pattern in the current context. From that point, using the pattern is as easy as stroking or filling the paths you want to draw.

Pattern Color Spaces

To create a pattern color space, you call the routine CGColorSpaceCreatePattern. This routine accepts a single parameter. For colored patterns, you should pass NULL as the base color space. If you are using a stencil pattern, you should pass a traditional color space like Generic RGB or the color space of the main display. When you draw the stencil pattern you will specify the color with which Quartz should draw the stencil by specifying the components of a color from this color space.

After creating the pattern color space, your code can use CGContextSetFillCol orSpace and CGContextSetStrokeColorSpace to indicate that you want to use a pattern for fills or strokes, respectively.

Setting the Current Pattern

After you have the color space settings of the context set up, you can set the current fill pattern for the context by calling CGContextSetFillPattern and the current stroke pattern with CGContextSetStrokePattern. Each of these routines accept the context you wish to change, a CGPatternRef, and an array of floating point values.

The values you supply in the floating point array depend on what type of pattern you are working with. If you are working with a colored pattern, then you should pass in a single floating point value that indicates the alpha value you want to use for your colored pattern. For example, if you pass in 0.5 as the alpha value, the computer will draw your pattern with 50% translucency.

If you are working with a stencil pattern, the floating point array is where you specify the color Quartz should apply to the stencil. You will need to specify the components of a color in the color space you provided as a base to the pattern color space you created. For example, if you indicated that your pattern’s base space was Generic CMYK, your floating point array would have five values in it—one for each of the four color components and one for the alpha value.

Drawing Patterned Graphics

After calling CGContextSetFillPattern or CGContextSetStrokePattern, any strokes or fills that your code calls for will draw with the current pattern. Listing 13.6 ties together the entire process of drawing with a colored pattern. This is part of the code used to draw Figure 13.2.

Example 13.6. Drawing a Colored Pattern

float opaque = 1.0;
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(inContext, patternSpace);
CGContextSetFillPattern(inContext, pattern, &opaque);
CGContextAddRect(inContext, bounds);
CGContextFillPath(inContext);
CGColorSpaceRelease(patternSpace);
CGPatternRelease(pattern);

You are drawing a colored pattern so you pass NULL to CGColorSpaceCreatePattern. You then tell the computer that we are interested in using the color space you’ve created as the fill color space. The pattern passed to CGContextSetFillPattern is the same pattern created in Listing 13.5. To use the pattern, you simply add a rectangular path to the current context and then call CGContextFillPath. Quartz will fill the path with your pattern. Upon returning, you clean up the pattern space and the pattern.

Listing 13.7 is a block of code that draws a stencil cell in seven different colors. This listing was taken from the Patterns code sample from the code sample CD.

Example 13.7. Drawing a Stencil Pattern in a Variety of Colors

// To draw the pattern we need a pattern color space
CGColorSpaceRef baseColorSpace = CGColorSpaceCreateWithName(
        kCGColorSpaceGenericRGB);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(
        baseColorSpace);
CGColorSpaceRelease(baseColorSpace);
baseColorSpace = NULL;

CGContextSetFillColorSpace(inContext, patternSpace);
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;

static float const rainbowColors[7][4] = {
        { 1.0, 0.0, 0.0, 1.0 }, // red
        { 1.0, 0.5, 0.0, 1.0 }, // orange
        { 1.0, 1.0, 0.0, 1.0 }, // yellow
        { 0.0, 1.0, 0.0, 1.0 }, // green
        { 0.0, 0.0, 1.0, 1.0 }, // blue
        { 0.5, 0.0, 1.0, 1.0 }, // indigo
        { 0.5, 0.0, 0.5, 1.0 }, // violet
};
// Add a path to fill with our pattern and fill it.
float rectHeight = bounds.size.height / 7;
for(short ctr = 0; ctr < 7; ctr++) {
        CGRect patternBounds = CGRectMake(
                       bounds.origin.x,
                       bounds.origin.y + ctr * rectHeight,
                       bounds.size.width,
                       rectHeight);

        CGContextSetFillPattern(inContext,
               pattern, rainbowColors[ctr]);
        CGContextAddRect(inContext, patternBounds);
        CGContextFillPath(inContext);
}

CGPatternRelease(pattern);
pattern = NULL;

This code listing begins much like the previous one, with the construction of a pattern color space. In this case, because you are using a stencil pattern, you need to provide a base color space (Generic RGB has been chosen here). As before, after creating the pattern color space, you set up the context to use that the fill color space for the port.

The large array in the middle of the code example simply defines seven RGBA colors (the same ones used in the shading example). The loop at the bottom is responsible for drawing the pattern in a series of seven rectangles. To draw each rectangle, you set the current fill pattern and call CGContextSeFillPattern to pass one of the seven colors as the color the computer should apply through the stencil. The pattern is drawn, then, using CGContextFillPath.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.174.44