Drawing Gradients with Quartz
For the most part, I find Quartz to be very straightforward. The naming is consistent and things are mostly structured the way you'd expect. One exception to this is the gradient API. It's not bad by your typical graphics library standard, but this is Mac OS X, dude. We're supposed to optimize for the most common case and make the other cases accessible.The most common case I'd expect is something like this:
Here's color 1
Here's color 2
Fill this rect using a linear or radial gradient
There's no Cocoa interface for this sort of thing, and the Quartz (CoreGraphics) version is a lot more awkward than I really think it needs to be. All you have to do is look at the Axial Shading page on ADC to see what I mean.
There's this:
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;
}
and then some of this:
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
}
... then a little bit of this:
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);
...
Yuck. Sorry to interrupt, but there's just so much code to blend two colors. You'd think we were writing a Carbon app.
Fortunately, there's a solution to all of this. Chad Weider created an Objective-C class called CTGradient which does exactly the type of thing we'd typically expect Cocoa to do. It's CC licensed so you can use it in your app without any worries. The API looks something like this:
+ (id)gradientWithBeginningColor:(NSColor *)begin
endingColor:(NSColor *)end;
- (CTGradient *)addColorStop:(NSColor *)color
atPosition:(float)position;
- (CTGradient *)removeColorStopAtIndex:(unsigned)index;
- (CTGradient *)removeColorStopAtPosition:(float)position;
- (NSColor *)colorStopAtIndex:(unsigned)index;
- (NSColor *)colorAtPosition:(float)position;
- (void)fillRect:(NSRect)rect angle:(float)angle;
- (void)radialFillRect:(NSRect)rect;
Ah. Now doesn't that feel better? It's not that the class does anything that is otherwise impossible, it's just a lot cleaner because all of the goofy callbacks and whatnot are moved into their own code space. In other words, you have more free time to work on the actual application.

Drawing Gradients with Quartz
Posted Mar 9, 2006 — 4 comments below
Posted Mar 9, 2006 — 4 comments below
Ben — Mar 10, 06 925
Shamyl Zakariya — Mar 10, 06 926
Blake Seely — Mar 21, 06 946
gormster — Nov 04, 07 4971