Shadings


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

Figure 13.3. Anatomy of Axial and Radial Shadings


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

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

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

Creating the Color Function

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

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

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

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


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

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


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

void CGFunctionReleaseInfoCallback(void *info) 


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

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

Listing 13.1. Creating a CGFunction for RGB Shadings

// Tell the OS that our function expects one input value, // in the domain [0,6], and returns values in the range [0,1] const float kValidDomain[2] = { 0, 6 }; const short kChannelsPerColor = 4; // R, G, B, A const float kValidRange[kChannelsPerColor][2] = {                            { 0, 1 },                            { 0, 1 },                            { 0, 1 },                            { 0, 1 } }; CGFunctionCallbacks kRGBShadingCallbackRoutines = {     0,                     // Callback Structure Version     RainbowShading,        // Evaluation Callback     NULL                   // Release User Data Callback }; CGFunctionRef rainbowShading =     ::CGFunctionCreate(            NULL,            1,            kValidDomain,            kChannelsPerColor,            (float *) kValidRange,            &kRGBShadingCallbackRoutines); 

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

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

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

Listing 13.2. A Sample Evaluation Callback

void RainbowShading(void *info, const float *in, float *out) {    // colors of the knots in parameter space    static float const shadingColors[7][4] = {            { 1.0, 0.0, 0.0, 1.0}, // red            { 1.0, 0.5, 0.0, 1.0}, // orange            { 1.0, 1.0, 0.0, 1.0}, // yellow            { 0.0, 1.0, 0.0, 1.0}, // green            { 0.0, 0.0, 1.0, 1.0}, // blue            { 0.5, 0.0, 1.0, 1.0}, // indigo            { 0.5, 0.0, 0.5, 1.0}, // violet    };    // Retrieve the function's input (a single float in the    // domain [0,6])    float parameterValue = *in;    // Use the input to find the index of the color on the left side    // of the current sub-interval    int colorIndex = (int)parameterValue;    if(colorIndex == 6) {            colorIndex = 5;    }    // We subtract of the integer part and get a sub-interval scale    // between 0 and 1.0    float t = parameterValue - colorIndex;    // Use all those calculations to create a color value that is the    // result of this function. We copy each component into the function    // result array.    for(unsigned short ctr = 0; ctr < 4; ctr++) {            out[ctr] =                    (1.0 - t) * shadingColors[colorIndex][ctr] +                    (t) * shadingColors[colorIndex + 1][ctr];    } } 

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

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





Quartz 2D Graphics for Mac OS X Developers
Quartz 2D Graphics for Mac OS X Developers
ISBN: 0321336631
EAN: 2147483647
Year: 2006
Pages: 100

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net