Re: CGShading
Re: CGShading
- Subject: Re: CGShading
- From: Scott Thompson <email@hidden>
- Date: Sat, 31 Jul 2004 20:35:29 -0500
On Jul 31, 2004, at 5:40 PM, Peter Laurens wrote:
>
Hi,
>
>
I am attempting to draw a simple gradient shade, and I have been
>
slowly going through the Quartz documentation to try and learn how to
>
achieve this.
>
>
I was pleased to see a sample project that shows off some code how to
>
do this. Unfortunately it's a very complex set of code with all sorts
>
of bits scattered all over the place in strange different functions,
>
and so it's difficult for me to follow properly... impossible in fact.
>
I am having a hard time stripping out the core stuff I need for a
>
gradient to be drawn, and the extra fluff the developer has added
>
around it.
>
>
Does anyone have a real simple CGShading demo project for me to take a
>
look at?
>
>
I'd be incredibly grateful for any help at all,
>
>
Thanks,
>
>
- Peter Laurens
Fundamentally what you do when you create a Shader is provide a
callback... your callback accepts a floating point value in the range
0.0 to 1.0 and return a color, in the color space of your choice, that
will be used at a corresponding point in the shading.
Before you can provide that callback, however, you have to create a
CGFunctionRef. The documentation of a CGFunctionRef in the headers is
pretty complex and hard to read. This is because CGFunctionRefs
actually provide a generalized mechanism for describing multi-input,
multi-valued functions. In the future the CGFunctionRef mechanism is
general enough that it could be used for something other than providing
callbacks for shaders in the future.
Basically a CGFunctionRef is a description of some kind of callback
that accepts a given number of floating point inputs and returns a
given number of floating point values as output. This description is
then paired with an actual routine pointer and the result is a
CGFunctionRef. The parameters you pass to CGFunctionCreate identifies
exactly how many floating point inputs and how many floating point
outputs your function will take as well as the range of valid values
for each of those inputs and outputs.
Perhaps an example is the best way to make this clearer. A shading
callback that returns an RGBA color will take one input value (in the
range 0..1) and return four output values (also in the range 0 .. 1).
To describe that kind of function using a CGFunctionRef you might use
(C++) code like this:
// Tell the OS that our function expects one input value (in the domain
[0 .. 1.0])
// and returns four values in the range [0..1]
const short kChannelsPerColor = 4; // RGBA
const float kValidDomain[2] = { 0, 1 };
const float kValidRange[kChannelsPerColor][2] = {
{ 0, 1 },
{ 0, 1 },
{ 0, 1 },
{ 0, 1 } };
... some other stuff here ...
// Create a CGFunctionRef that describes a function taking 1 input and
kChannelsPerColor outputs.
CGFunctionRef callbackObject = ::CGFunctionCreate(NULL, 1,
kValidDomain, kChannelsPerColor, (float *) kValidRange,
&kShadingCallbackRoutines);
In addition to the inputs and ouputs, your callback can also take a
block of arbitrary data. The first parameter to CGFunctionCreate is
simply some arbitrary data that your callback may need. The last
parameter is a structure in which you provide a pointer to your
callback routine and a pointer to a routine that can free the arbitrary
data.
Now that you have a description of your callback, you need the callback
itself. Here's a callback that provides a rainbow of colors.
/* ---------------------------------------------------
CalculateShadingColors */
/*
Our callback which provides the colors that will be used in the
shading.
*/
void CalculateShadingColors(void *info, const float *in, float *out)
{
// colors of the knots in parameter space
static float const colorsInShading[7][kChannelsPerColor] = {
{ 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 .. 1]
float parameterValue = *in;
// Convert the input to a value in the range [0 .. 6]. We typecast
this
// as an int since it will be an array index in the colorsInShading
array
int colorIndex = (int)(parameterValue * 6.0);
// Fancy math. We take the interval between knot values and create a
// value that scales from [0 .. 1] between knots
float subIntervalParam = (parameterValue - (colorIndex / 6.0)) * 6.0;
#if DUMP_MATH_RESULTS
CFStringRef debugString = CFStringCreateWithFormat(NULL, NULL,
CFSTR("%f %d %f"), parameterValue, colorIndex, subIntervalParam);
CFShow(debugString);
CFRelease(debugString);
#endif
// 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 < kChannelsPerColor; ctr++) {
out[ctr] = (1.0 - subIntervalParam) *
colorsInShading[colorIndex][ctr] +
(subIntervalParam) * colorsInShading[colorIndex + 1][ctr];
}
}
The first parameter of our callback is the arbitrary data (unused in
this case). The second parameter is a pointer to the inputs to the
callback. When we created our CGFunctionRef we told the OS that our
callback expects this array to be one value long. The third parameter
is space to place the outputs of our function. Our callback returns
four values representing the red, green, blue, and alpha values of a
color. We expect, therefore that the output array will be four values
long.
What is left is to create a CGShadingRef object that uses our
CGFunctionRef (with it's embedded callback routine pointer) to draw the
rainbow:
// Now create a shading object based on that callback
CGShadingRef shadingObject = ::CGShadingCreateAxial(colorSpace,
CGPointMake(0.0, 0.0),
CGPointMake(200.0, 200.0),
callbackObject,
true,
true);
We create a linear gradient that travels from the point (0, 0) to the
point (200, 200). The CG shading documentation can describe the
characteristics of an Axial shader but playing with one for about 10
minutes will probably give you a better idea of how they work.
Now we need to use our shading object in some drawing code. This came
from a Carbon Event Handler in a Carbon application but it should be
applicable in a drawRect: method as well:
HIRect fillRect = { 10, 10, 200.0, 200.0 };
// Tell CG where we want to apply our shading by setting the clip
::CGContextSaveGState(cgContext); // save GState so we can undo
clip
::CGContextAddRect(cgContext, fillRect);
::CGContextClip(cgContext);
::CGContextDrawShading(cgContext, shadingObject);
::CGContextRestoreGState(cgContext); // undo clip
::CGContextSetLineWidth(cgContext, 5.0);
::CGContextSetRGBStrokeColor(cgContext, 0.0, 0.0, 0.0, 1.0);
::CGContextAddRect(cgContext, fillRect);
::CGContextStrokePath(cgContext);
To draw a shader you create a clipping path and ask the system to draw
your shader in that clipping path. This code goes a bit further and
puts a 5 point stroke around the shaded rectangle.
I hope that helps... if it's still unclear, let me know and I'll try to
confuse you even more :-)
--
Macintosh Software Engineering Consulting Services
Visit my resume at <
http://homepage.mac.com/easco/RSTResume.html>
[demime 0.98b removed an attachment of type application/pkcs7-signature which had a name of smime.p7s]
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.
References: | |
| >CGShading (From: Peter Laurens <email@hidden>) |