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).
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.
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.
1 2 3 4 5
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
float? No. In fact,
CGFloat is sometimes
double! This happens when 64-bit compiling for instance. Argument
const-correctness is another feature.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
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!