Gloss-Caustic Shading
Matt Gallagher recently wrote a very useful article about drawing gloss gradients using Core Graphics. In his article, Matt describes how to reproduce the oft-seen glossy gradient effect. Thanks Matt! It’s a nice article. “Cocoa with Love” lovingly provides the working source code. This little article aims to complement Matt’s work.
I’ve also re-factored the software and packaged the result within an Objective-C class called RRGlossCausticShader. This packaging automatically adds support for key-value coding and observing. Bindings then let you easily wrap the class within a little application able to adjust the many parameters interactively.
Show me the money
Before going any further, you might first want to see the results. I know I would! Download the project to play with the shader. Compile and run the application using Xcode. The main window appears as follows. Inspector panels also appear from where you can interactively adjust the many shading parameters.

Gloss above, caustic below. Those are the two halves of the shading. Gloss appears in the top half, caustic in the bottom half. Within each half, an exponential function blends a given non-caustic base colour with whiteness above and with matching caustic colour below. The main window displays these basic elements of the shading process: the shading itself (left), non-caustic base colour (top right) and coefficient of the exponent function (bottom right).
Re-factoring
My version of Matt’s work includes some re-factoring. Rather than just the one functional interface, the version that you’ll find within the project involves three modular utility classes.
- RRGlossCausticShader encapsulates shading
- RRCausticColorMatcher finds caustic matches for given colours
- RRExponentialFunction wraps the exponential maths
- RRLuminanceFromRGBComponents returns luminance from RGB triples
This re-factoring has some benefits. First, it promotes re-use. Other requirements may call for RGB-to-luminance conversion, or a generic exponential function for example; or you might want to sub-class the caustic colour matcher and add some customisation. Separating out the individual sub-requirements adds some extra scope for growth. I think it also makes the source code somewhat easier to follow and therefore comprehend. But that might just be my own personal opinion. Your mileage may vary, as they say!
Anyway, the purpose of this article is to outline some of the differences in implementation and the underlying thinking. Comments welcome, of course.
Twinkle, twinkle
The gloss effect on the upper half of the gradient derives from the base colour’s luminosity. So obtaining luminance from a given Red, Green and Blue is part of the glossing requirement.
As you can see below, I replace Matt’s conversion with the one described at OpenGL. The difference is small, almost non-existent. Only, the comment that “Haeberli notes that [the YIQ colour conversion values used by Matt] are incorrect in a linear RGB colour space” convinced me to make the small change. Just call me fussy.
CGFloat
Notice a few other things. I’m using CGFloat type for floats because this corresponds exactly with Apple’s usage for colour components. Can we always assume that CGFloat equals float? No. In fact, CGFloat is sometimes double! This happens when 64-bit compiling for instance. Argument const-correctness is another feature.
Caustic nature
The lower half of the gradient blends the base colour into shades of yellow (yellow is the default caustic hue). The lower end of the gradient starts to look more-and-more tinted towards the yellow. However, the precise colour depends upon the non-caustic base colour. For blue colours, for example, the default caustic switches to magenta. Blue looks better fading toward magenta, rather than yellow. In all cases though, the amount of caustic to non-caustic blending varies according to the cosine of the distance between the caustic and non-caustic hues.
So rather than having a function handle the caustic colour matching and implementing the adjustable parameters as manifest constants, the new version implements a “colour matching” class. Interface listed below. The class implements adjustable parameters as read-write class properties with all the necessary setters and getters. This approach buys some advantages. It automatically adds support for key-value coding and observing. In practice, it means that user-interface controls can bind to these properties using Cocoa Bindings.
Exponentially
Shading employs an exponential function. As the gradient progresses from 0 to 0.5, gloss white blending increases exponentially in the 0,1 interval. Progressing through the bottom half of the gradient from 0.5 to 1, caustic blending increases exponentially. The implementation re-factors the exponential function. Interface listing below. It becomes a C-style object class and thereby minimises its dependencies: just plain C types and standard library math.h and hence very suitable for repeated invocations from the bowels of a shading function.
// Encapsulates an optimising generic Exponential Function where
// y=1-(exp(x*-c)-exp(-c))/(1-exp(-c))
// and where 0<c is a general coefficient describing the exponential
// curvature. The function's input domain lies within the 0..1 interval, its
// output range likewise. The implementation optimises by pre-computing those
// constant terms depending only on the coefficient whenever the coefficient
// changes. Repeated evaluation takes less computing time thereafter.
struct RRExponentialFunction
;
typedef struct RRExponentialFunction RRExponentialFunction;
void
float
Shading
And finally, the “gloss-caustic shader” class brings all the disparate pieces together. The design aims for reusability. Idea is that you instantiate a shader, set up the parameters such as non-caustic colour or any other necessary adjustments to defaults, then re-use it over-and-over whenever you need to draw the shading.
The sample project explains how to use the shader. I hope it’s clear enough. The sources have an MIT license. I haven’t tried it out on iPhone or pre-Leopard Mac, so can’t make any comments about portability except to say that porting should be fairly straightforward. But no doubt you’ve heard that before!
20 days later
Hello,
Very nice sample, I am trying to use something like this to replicate the look of the SMS conversation bubbles (like iChat) on the iphone, I know there will be things to change to improve the portability (due to the lack of NSColor on the iphone but this is not what I want to talk about.
There are a couple things missing:
First the gradient should not be: glossy gradient - caustic gradient. There should really be an area in the middle where the non caustic color is applied without a gradient. For this purpose I have modified the shading function to be:
Now my remaining problems are: 1) at the top there is an area which is actually darker than the base color before the gloss gradient should be added. I am going to see if I can reproduce that. Second the corners have some kind of gradient too which does not appear to be either linear nor radial to my poor untrained eyes and I have no idea how I’ll replicate this.
Best regards
Benoit
20 days later
Dear Benoit,
Most interesting! I like your idea. iPhone’s and iChat’s glossy speech bubble is a nice effect. It would be handy to have some way to reproduce it. Or, at least, something similar. I’m interested to see how you get on. Please keep me posted. In fact, you’ve got me thinking about it too!
Kind regards,
Roy
P.S.
If you want to check out the version-controlled sources and clone your own, they live here at github.
25 days later
too bad I hadn’t seen the reply yet. I have just downloaded the git hub. As I was in a hurry to get those bubble ready and as in my app for now they are static, I stopped working on this and ended up doing them with illustrator (and I had to learn the basics of that tool… it may have been faster to code in the end). Since I sent my new version to apple I am now back on the problem and will tell you the progess. I think my time with illustrator gave me a better understanding of what will be needed. so it was not lost. I’ll keep you posted
12 months later
First, thanks for this great example.Second, is there a way to draw the gloss into an NSBezierPath ? .. maybe add a drawShadingIntoPath: function to the shader. Thanks !
12 months later
Nevermind, I found out how to do it..
about 1 year later
Great post, thank you very much! I just ported your code to the iPhone and added an iPhone sample app. See my blog post if you are interested.
over 1 year later
Hello Pioneer, could this be modified to use this piece of code to draw the rounded glossy icons you see in Settings?
Thanks,
Kenneth
over 1 year later
Dear Kenneth, Indeed you can. You only need to set up clipping for the Core Graphics context. See the Quartz 2D Programming Guide for the details. At the online iPhone library, you can find the document here.
But in summary, before sending
-drawShadingFromPoint:toPoint:inContext:to the shader you need to clip the context. You can also do this usingCGLayermasking as per the iPhone sample code, or via theCGContextgraphics API or usingNSBezierPathon the Mac, e.g.- (void)drawRect:(NSRect)rect { // Shade from the top-left corner of the bounds to the bottom-left. NSRect bounds = [self bounds]; [[NSBezierPath bezierPathWithRoundedRect:bounds xRadius:10.0 yRadius:10.0] setClip]; [shader drawShadingFromPoint:CGPointMake(NSMinX(bounds), NSMaxY(bounds)) toPoint:CGPointMake(NSMinX(bounds), NSMinY(bounds)) inContext:[[NSGraphicsContext currentContext] graphicsPort]]; }almost 3 years later
Thank you so much. You are a rock star. (And so is Matt G.)