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
                              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,
  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,
                               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

- (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.
Design Element
Drawing Gradients with Quartz
Posted Mar 9, 2006 — 4 comments below


Ben — Mar 10, 06 925

oh fantastic, i was actually looking do this in my current project. This will help just fantastically , thank you.

Shamyl Zakariya — Mar 10, 06 926

This is beautiful. I'd spent many hours trying to learn the gradient API for a custom tabbed-pane widget I was writing. It's not that the API is so bad, but that the documentation tells you next to nothing. I ended up using PNGs...

Blake Seely — Mar 21, 06 946

The book "Programming with Quartz" also has a section about shadings that is very good. It's a small section in a very large book, but it's got tons of other good info about how to use quartz.

gormster — Nov 04, 07 4971

Leopard has an NSGradient class, but you'd be hard pressed to find mention of it in the docs. There is documentation aplenty for it, but you have to know the class name.


Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.

Copyright © Scott Stevenson 2004-2015