|
CocoaDrawing
How would I go about creating a gradient fill between a foreground and background color in a custom NSView?
I don't think there is any API for this in Cocoa, so you'll need to use CoreGraphics directly, here is an example of using the gradient fill support in an NSView subclass:
void Interpolate (void* info, float const* inData, float* outData)
{
outData[0] = inData[0];
outData[1] = sin(M_PI * inData[0]);
outData[2] = 1.0;
outData[3] = 1.0;
}
@implementation MyView
- (id)initWithFrame:(NSRect)frameRect
{
[super initWithFrame:frameRect];
return self;
}
- (void)drawRect:(NSRect)rect
{
NSEraseRect(rect);
struct CGFunctionCallbacks callbacks = { 0, Interpolate, NULL };
CGFunctionRef function = CGFunctionCreate(
NULL, // void* info,
1, // size_t domainDimension,
NULL, // float const* domain,
4, // size_t rangeDimension,
NULL, // float const* range,
&callbacks // CGFunctionCallbacks const* callbacks
);
CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
NSRect bounds = [self bounds];
float srcX = NSMinX(bounds), srcY = NSMinY(bounds);
float dstX = NSMaxX(bounds), dstY = NSMaxY(bounds);
CGShadingRef shading = CGShadingCreateAxial(
cspace, // CGColorSpaceRef colorspace,
CGPointMake(srcX, srcY), // CGPoint start,
CGPointMake(dstX, dstY), // CGPoint end,
function, // CGFunctionRef function,
false, // bool extendStart,
false // bool extendEnd
);
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextDrawShading(
context,
shading
);
CGShadingRelease(shading);
CGColorSpaceRelease(cspace);
CGFunctionRelease(function);
}
@end
How would I use this with two specific colors though?
The Interpolate-function receive a float in the range of [0..1] and should store the corresponding RGBA colour in the out array. So to go from red to yellow, use something like:
void Interpolate (void* info, float const* inData, float *outData)
{
static float red[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
static float yellow[4] = { 1.0f, 1.0f, 0.0f, 1.0f };
float a = inData[0];
for(int i = 0; i < 4; i++)
outData[i] = (1.0f-a)*red[i] + a*yellow[i];
}
Please read the documentation for further info.
See HowToUseOAGradientTableView?
Here is a category on NSBezierPath to do a linear gradient fill of an area using two specified colors.
//### globals
CGColorSpaceRef colorspace = nil;
float start_red,
start_green,
start_blue,
start_alpha;
float end_red,
end_green,
end_blue,
end_alpha;
float d_red,
d_green,
d_blue,
d_alpha;
@implementation NSBezierPath(MRGradientFill)
static void
evaluate(void *info, const float *in, float *out)
{
// red
*out++ = start_red + *in * d_red;
// green
*out++ = start_green + *in * d_green;
// blue
*out++ = start_blue + *in * d_blue;
//alpha
*out++ = start_alpha + *in * d_alpha;
}
-(void)linearGradientFill:(NSRect)thisRect
startColor:(NSColor *)startColor
endColor:(NSColor *)endColor
{
CGShadingRef shading;
static CGPoint startPoint = { 0, 0 };
static CGPoint endPoint = { 0, 0 };
int k;
CGFunctionRef function;
CGFunctionRef (*getFunction)(CGColorSpaceRef);
CGShadingRef (*getShading)(CGColorSpaceRef, CGFunctionRef);
// get my context
CGContextRef currentContext =
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
NSColor *s = [startColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
NSColor *e = [endColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
// set up colors for gradient
start_red = [s redComponent];
start_green = [s greenComponent];
start_blue = [s blueComponent];
start_alpha = [s alphaComponent];
end_red = [e redComponent];
end_green = [e greenComponent];
end_blue = [e blueComponent];
end_alpha = [e alphaComponent];
d_red = absDiff(end_red, start_red);
d_green = absDiff(end_green, start_green);
d_blue = absDiff(end_blue, start_blue);
d_alpha = absDiff(end_alpha ,start_alpha);
// draw gradient
colorspace = CGColorSpaceCreateDeviceRGB();
size_t components;
static const float domain[2] = { 0.0, 1.0 };
static const float range[10] = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 };
static const CGFunctionCallbacks callbacks = { 0, &evaluate, NULL };
components = 1 + CGColorSpaceGetNumberOfComponents(colorspace);
function = CGFunctionCreate((void *)components, 1, domain, components,
range, &callbacks);
// function = getFunction(colorspace);
startPoint.x=0;
startPoint.y=thisRect.origin.y;
endPoint.x=0;
endPoint.y=NSMaxY(thisRect);
shading = CGShadingCreateAxial(colorspace,
startPoint, endPoint,
function,
NO, NO);
CGContextDrawShading(currentContext, shading);
};
@end
You can use it like this:
- (void)drawRect:(NSRect)rect
{
where();
// get inset rect
NSRect thisRect = NSInsetRect(rect, 15.5, 15.5);
// get bezier path and draw rounded rect
NSBezierPath *rr = [NSBezierPath bezierPath];
[rr appendRoundedRect:thisRect cornerRadius:8.0];
[rr addClip];
[rr linearGradientFill:thisRect
startColor:[NSColor whiteColor]
endColor:[NSColor selectedControlColor]];
[[NSColor blackColor] set];
[rr stroke];
}
The code above doesn't free resources lit it should, so be careful about copy-and-paste. You might want to add something like this somewhere:
CGFunctionRelease(function);
CGShadingRelease(shading);
CGColorSpaceRelease(colorspace);
-Michael Rothwell (michael@rothwell.us)
I'm getting a ZeroLink 'unknown symbol' error relating to where() (and if I comment that out, absDiff()) ... I assume I need to be including a library or several somewhere ... Which ones? Jul 09 '05
Update - I feel like a moron. ;-) absDiff() isn't some standard function in some library somewhere; it's a relatively simple function like so:
int absDiff(int a, int b)
{
return (a < b) ? b-a : a-b;
}
(sigh) As to where() - I have no idea what it is. Removing it (and adding the absDiff() function) made it run, but it's still not really drawing anything. It compiled just fine, though. :-/ So ... at least on Tiger, this example doesn't seem to work.
'Another Update:' There's definitely something wrong. Here's what I've done so far. 1- I've removed the "where();" statement (I have no idea what this represents but that may be the problem). 2- Added the absDiff function above (also the only other difference). The problem is, I get a solid white rectangle surrounded by the stroked path. After some experimentation, I find that changing startColor (but not endColor) has mixed results. Some colors (such as redColor and greenColor) create the original color fading upwards into a different shade of the color. I'm thinking that the alpha and / or the other color components on endColor might be zeroed out (but I'm having trouble visualizing what this would mean graphically).
So I guess the question is, what's wrong with endColor?
|