2007-09-16 01:02:06 +00:00
|
|
|
|
//
|
|
|
|
|
// CTGradient.m
|
|
|
|
|
//
|
|
|
|
|
// Created by Chad Weider on 2/14/07.
|
2008-05-10 14:02:19 +00:00
|
|
|
|
// Copyright (c) 2007 Chad Weider.
|
|
|
|
|
// Some rights reserved: <http://creativecommons.org/licenses/by/2.5/>
|
2007-09-16 01:02:06 +00:00
|
|
|
|
//
|
2008-05-10 14:02:19 +00:00
|
|
|
|
// Version: 1.6
|
2007-09-16 01:02:06 +00:00
|
|
|
|
|
|
|
|
|
#import "CTGradient.h"
|
|
|
|
|
|
|
|
|
|
@interface CTGradient (Private)
|
|
|
|
|
- (void)_commonInit;
|
|
|
|
|
- (void)setBlendingMode:(CTGradientBlendingMode)mode;
|
2008-05-10 14:02:19 +00:00
|
|
|
|
- (void)addElement:(CTGradientElement*)newElement;
|
2007-09-16 01:02:06 +00:00
|
|
|
|
|
|
|
|
|
- (CTGradientElement *)elementAtIndex:(unsigned)index;
|
|
|
|
|
|
|
|
|
|
- (CTGradientElement)removeElementAtIndex:(unsigned)index;
|
|
|
|
|
- (CTGradientElement)removeElementAtPosition:(float)position;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
//C Fuctions for color blending
|
|
|
|
|
static void linearEvaluation (void *info, const float *in, float *out);
|
|
|
|
|
static void chromaticEvaluation(void *info, const float *in, float *out);
|
|
|
|
|
static void inverseChromaticEvaluation(void *info, const float *in, float *out);
|
|
|
|
|
static void transformRGB_HSV(float *components);
|
|
|
|
|
static void transformHSV_RGB(float *components);
|
|
|
|
|
static void resolveHSV(float *color1, float *color2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@implementation CTGradient
|
|
|
|
|
/////////////////////////////////////Initialization Type Stuff
|
|
|
|
|
- (id)init
|
|
|
|
|
{
|
|
|
|
|
self = [super init];
|
|
|
|
|
|
|
|
|
|
if (self != nil)
|
|
|
|
|
{
|
|
|
|
|
[self _commonInit];
|
|
|
|
|
[self setBlendingMode:CTLinearBlendingMode];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)_commonInit
|
|
|
|
|
{
|
|
|
|
|
elementList = nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)dealloc
|
|
|
|
|
{
|
|
|
|
|
CGFunctionRelease(gradientFunction);
|
|
|
|
|
|
|
|
|
|
CTGradientElement *elementToRemove = elementList;
|
|
|
|
|
while(elementList != nil)
|
|
|
|
|
{
|
|
|
|
|
elementToRemove = elementList;
|
|
|
|
|
elementList = elementList->nextElement;
|
|
|
|
|
free(elementToRemove);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[super dealloc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id)copyWithZone:(NSZone *)zone
|
|
|
|
|
{
|
|
|
|
|
CTGradient *copy = [[[self class] allocWithZone:zone] init];
|
|
|
|
|
|
|
|
|
|
//now just copy my elementlist
|
|
|
|
|
CTGradientElement *currentElement = elementList;
|
|
|
|
|
while(currentElement != nil)
|
|
|
|
|
{
|
|
|
|
|
[copy addElement:currentElement];
|
|
|
|
|
currentElement = currentElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[copy setBlendingMode:blendingMode];
|
|
|
|
|
|
|
|
|
|
return copy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)coder
|
|
|
|
|
{
|
|
|
|
|
if([coder allowsKeyedCoding])
|
|
|
|
|
{
|
|
|
|
|
unsigned count = 0;
|
|
|
|
|
CTGradientElement *currentElement = elementList;
|
|
|
|
|
while(currentElement != nil)
|
|
|
|
|
{
|
|
|
|
|
[coder encodeValueOfObjCType:@encode(float) at:&(currentElement->red)];
|
|
|
|
|
[coder encodeValueOfObjCType:@encode(float) at:&(currentElement->green)];
|
|
|
|
|
[coder encodeValueOfObjCType:@encode(float) at:&(currentElement->blue)];
|
|
|
|
|
[coder encodeValueOfObjCType:@encode(float) at:&(currentElement->alpha)];
|
|
|
|
|
[coder encodeValueOfObjCType:@encode(float) at:&(currentElement->position)];
|
|
|
|
|
|
|
|
|
|
count++;
|
|
|
|
|
currentElement = currentElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
[coder encodeInt:count forKey:@"CTGradientElementCount"];
|
|
|
|
|
[coder encodeInt:blendingMode forKey:@"CTGradientBlendingMode"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
[NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (id)initWithCoder:(NSCoder *)coder
|
|
|
|
|
{
|
|
|
|
|
[self _commonInit];
|
|
|
|
|
|
|
|
|
|
[self setBlendingMode:[coder decodeIntForKey:@"CTGradientBlendingMode"]];
|
|
|
|
|
unsigned count = [coder decodeIntForKey:@"CTGradientElementCount"];
|
|
|
|
|
|
|
|
|
|
while(count != 0)
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement newElement;
|
|
|
|
|
|
|
|
|
|
[coder decodeValueOfObjCType:@encode(float) at:&(newElement.red)];
|
|
|
|
|
[coder decodeValueOfObjCType:@encode(float) at:&(newElement.green)];
|
|
|
|
|
[coder decodeValueOfObjCType:@encode(float) at:&(newElement.blue)];
|
|
|
|
|
[coder decodeValueOfObjCType:@encode(float) at:&(newElement.alpha)];
|
|
|
|
|
[coder decodeValueOfObjCType:@encode(float) at:&(newElement.position)];
|
|
|
|
|
|
|
|
|
|
count--;
|
|
|
|
|
[self addElement:&newElement];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Creation
|
|
|
|
|
+ (id)gradientWithBeginningColor:(NSColor *)begin endingColor:(NSColor *)end
|
|
|
|
|
{
|
|
|
|
|
id newInstance = [[[self class] alloc] init];
|
|
|
|
|
|
|
|
|
|
CTGradientElement color1;
|
|
|
|
|
CTGradientElement color2;
|
|
|
|
|
|
|
|
|
|
[[begin colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color1.red
|
|
|
|
|
green:&color1.green
|
|
|
|
|
blue:&color1.blue
|
|
|
|
|
alpha:&color1.alpha];
|
|
|
|
|
|
|
|
|
|
[[end colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color2.red
|
|
|
|
|
green:&color2.green
|
|
|
|
|
blue:&color2.blue
|
|
|
|
|
alpha:&color2.alpha];
|
|
|
|
|
color1.position = 0;
|
|
|
|
|
color2.position = 1;
|
|
|
|
|
|
|
|
|
|
[newInstance addElement:&color1];
|
|
|
|
|
[newInstance addElement:&color2];
|
|
|
|
|
|
|
|
|
|
return [newInstance autorelease];
|
|
|
|
|
}
|
2008-05-10 14:02:19 +00:00
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
2007-09-16 01:02:06 +00:00
|
|
|
|
#pragma mark Modification
|
|
|
|
|
- (CTGradient *)gradientWithAlphaComponent:(float)alpha
|
|
|
|
|
{
|
|
|
|
|
id newInstance = [[[self class] alloc] init];
|
|
|
|
|
|
|
|
|
|
CTGradientElement *curElement = elementList;
|
|
|
|
|
CTGradientElement tempElement;
|
|
|
|
|
|
|
|
|
|
while(curElement != nil)
|
|
|
|
|
{
|
|
|
|
|
tempElement = *curElement;
|
|
|
|
|
tempElement.alpha = alpha;
|
|
|
|
|
[newInstance addElement:&tempElement];
|
|
|
|
|
|
|
|
|
|
curElement = curElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [newInstance autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (CTGradient *)gradientWithBlendingMode:(CTGradientBlendingMode)mode
|
|
|
|
|
{
|
|
|
|
|
CTGradient *newGradient = [self copy];
|
|
|
|
|
|
|
|
|
|
[newGradient setBlendingMode:mode];
|
|
|
|
|
|
|
|
|
|
return [newGradient autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Adds a color stop with <color> at <position> in elementList
|
|
|
|
|
//(if two elements are at the same position then added imediatly after the one that was there already)
|
|
|
|
|
- (CTGradient *)addColorStop:(NSColor *)color atPosition:(float)position
|
|
|
|
|
{
|
|
|
|
|
CTGradient *newGradient = [self copy];
|
|
|
|
|
CTGradientElement newGradientElement;
|
|
|
|
|
|
|
|
|
|
//put the components of color into the newGradientElement - must make sure it is a RGB color (not Gray or CMYK)
|
|
|
|
|
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&newGradientElement.red
|
|
|
|
|
green:&newGradientElement.green
|
|
|
|
|
blue:&newGradientElement.blue
|
|
|
|
|
alpha:&newGradientElement.alpha];
|
|
|
|
|
newGradientElement.position = position;
|
|
|
|
|
|
|
|
|
|
//Pass it off to addElement to take care of adding it to the elementList
|
|
|
|
|
[newGradient addElement:&newGradientElement];
|
|
|
|
|
|
|
|
|
|
return [newGradient autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Removes the color stop at <position> from elementList
|
|
|
|
|
- (CTGradient *)removeColorStopAtPosition:(float)position
|
|
|
|
|
{
|
|
|
|
|
CTGradient *newGradient = [self copy];
|
|
|
|
|
CTGradientElement removedElement = [newGradient removeElementAtPosition:position];
|
|
|
|
|
|
|
|
|
|
if(isnan(removedElement.position))
|
|
|
|
|
[NSException raise:NSRangeException format:@"-[%@ removeColorStopAtPosition:]: no such colorStop at position (%f)", [self class], position];
|
|
|
|
|
|
|
|
|
|
return [newGradient autorelease];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (CTGradient *)removeColorStopAtIndex:(unsigned)index
|
|
|
|
|
{
|
|
|
|
|
CTGradient *newGradient = [self copy];
|
|
|
|
|
CTGradientElement removedElement = [newGradient removeElementAtIndex:index];
|
|
|
|
|
|
|
|
|
|
if(isnan(removedElement.position))
|
|
|
|
|
[NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
|
|
|
|
|
|
|
|
|
|
return [newGradient autorelease];
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Information
|
|
|
|
|
- (CTGradientBlendingMode)blendingMode
|
|
|
|
|
{
|
|
|
|
|
return blendingMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Returns color at <position> in gradient
|
|
|
|
|
- (NSColor *)colorStopAtIndex:(unsigned)index
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *element = [self elementAtIndex:index];
|
|
|
|
|
|
|
|
|
|
if(element != nil)
|
|
|
|
|
return [NSColor colorWithCalibratedRed:element->red
|
|
|
|
|
green:element->green
|
|
|
|
|
blue:element->blue
|
|
|
|
|
alpha:element->alpha];
|
|
|
|
|
|
|
|
|
|
[NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
|
|
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSColor *)colorAtPosition:(float)position
|
|
|
|
|
{
|
|
|
|
|
float components[4];
|
|
|
|
|
|
|
|
|
|
switch(blendingMode)
|
|
|
|
|
{
|
|
|
|
|
case CTLinearBlendingMode:
|
|
|
|
|
linearEvaluation(&elementList, &position, components); break;
|
|
|
|
|
case CTChromaticBlendingMode:
|
|
|
|
|
chromaticEvaluation(&elementList, &position, components); break;
|
|
|
|
|
case CTInverseChromaticBlendingMode:
|
|
|
|
|
inverseChromaticEvaluation(&elementList, &position, components); break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2008-05-10 14:02:19 +00:00
|
|
|
|
return [NSColor colorWithCalibratedRed:components[0]
|
|
|
|
|
green:components[1]
|
|
|
|
|
blue:components[2]
|
2007-09-16 01:02:06 +00:00
|
|
|
|
alpha:components[3]];
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Drawing
|
|
|
|
|
- (void)drawSwatchInRect:(NSRect)rect
|
|
|
|
|
{
|
|
|
|
|
[self fillRect:rect angle:45];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)fillRect:(NSRect)rect angle:(float)angle
|
|
|
|
|
{
|
|
|
|
|
//First Calculate where the beginning and ending points should be
|
|
|
|
|
CGPoint startPoint;
|
|
|
|
|
CGPoint endPoint;
|
|
|
|
|
|
|
|
|
|
if(angle == 0) //screw the calculations - we know the answer
|
|
|
|
|
{
|
|
|
|
|
startPoint = CGPointMake(NSMinX(rect), NSMinY(rect)); //right of rect
|
|
|
|
|
endPoint = CGPointMake(NSMaxX(rect), NSMinY(rect)); //left of rect
|
|
|
|
|
}
|
|
|
|
|
else if(angle == 90) //same as above
|
|
|
|
|
{
|
|
|
|
|
startPoint = CGPointMake(NSMinX(rect), NSMinY(rect)); //bottom of rect
|
|
|
|
|
endPoint = CGPointMake(NSMinX(rect), NSMaxY(rect)); //top of rect
|
|
|
|
|
}
|
|
|
|
|
else //ok, we'll do the calculations now
|
|
|
|
|
{
|
|
|
|
|
float x,y;
|
|
|
|
|
float sina, cosa, tana;
|
|
|
|
|
|
|
|
|
|
float length;
|
|
|
|
|
float deltax,
|
|
|
|
|
deltay;
|
|
|
|
|
|
|
|
|
|
float rangle = angle * pi/180; //convert the angle to radians
|
|
|
|
|
|
|
|
|
|
if(fabsf(tan(rangle))<=1) //for range [-45,45], [135,225]
|
|
|
|
|
{
|
|
|
|
|
x = NSWidth(rect);
|
|
|
|
|
y = NSHeight(rect);
|
|
|
|
|
|
|
|
|
|
sina = sin(rangle);
|
|
|
|
|
cosa = cos(rangle);
|
|
|
|
|
tana = tan(rangle);
|
|
|
|
|
|
|
|
|
|
length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
|
|
|
|
|
|
|
|
|
|
deltax = length*cosa/2;
|
|
|
|
|
deltay = length*sina/2;
|
|
|
|
|
}
|
|
|
|
|
else //for range [45,135], [225,315]
|
|
|
|
|
{
|
|
|
|
|
x = NSHeight(rect);
|
|
|
|
|
y = NSWidth(rect);
|
|
|
|
|
|
|
|
|
|
sina = sin(rangle - 90*pi/180);
|
|
|
|
|
cosa = cos(rangle - 90*pi/180);
|
|
|
|
|
tana = tan(rangle - 90*pi/180);
|
|
|
|
|
|
|
|
|
|
length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
|
|
|
|
|
|
|
|
|
|
deltax =-length*sina/2;
|
|
|
|
|
deltay = length*cosa/2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startPoint = CGPointMake(NSMidX(rect)-deltax, NSMidY(rect)-deltay);
|
|
|
|
|
endPoint = CGPointMake(NSMidX(rect)+deltax, NSMidY(rect)+deltay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Calls to CoreGraphics
|
|
|
|
|
CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
|
|
|
|
CGContextSaveGState(currentContext);
|
2008-05-10 14:02:19 +00:00
|
|
|
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
|
2007-09-16 01:02:06 +00:00
|
|
|
|
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
|
|
|
|
#else
|
|
|
|
|
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
|
|
|
|
|
#endif
|
|
|
|
|
CGShadingRef myCGShading = CGShadingCreateAxial(colorspace, startPoint, endPoint, gradientFunction, false, false);
|
|
|
|
|
|
|
|
|
|
CGContextClipToRect (currentContext, *(CGRect *)&rect); //This is where the action happens
|
|
|
|
|
CGContextDrawShading(currentContext, myCGShading);
|
|
|
|
|
|
|
|
|
|
CGShadingRelease(myCGShading);
|
|
|
|
|
CGColorSpaceRelease(colorspace );
|
|
|
|
|
CGContextRestoreGState(currentContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)radialFillRect:(NSRect)rect
|
|
|
|
|
{
|
2008-05-10 14:02:19 +00:00
|
|
|
|
CGPoint startPoint , endPoint;
|
2007-09-16 01:02:06 +00:00
|
|
|
|
float startRadius, endRadius;
|
|
|
|
|
float scalex, scaley, transx, transy;
|
|
|
|
|
|
|
|
|
|
startPoint = endPoint = CGPointMake(NSMidX(rect), NSMidY(rect));
|
|
|
|
|
|
|
|
|
|
startRadius = -1;
|
|
|
|
|
if(NSHeight(rect)>NSWidth(rect))
|
|
|
|
|
{
|
|
|
|
|
scalex = NSWidth(rect)/NSHeight(rect);
|
|
|
|
|
transx = (NSHeight(rect)-NSWidth(rect))/2;
|
|
|
|
|
scaley = 1;
|
|
|
|
|
transy = 1;
|
|
|
|
|
endRadius = NSHeight(rect)/2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
scalex = 1;
|
|
|
|
|
transx = 1;
|
|
|
|
|
scaley = NSHeight(rect)/NSWidth(rect);
|
|
|
|
|
transy = (NSWidth(rect)-NSHeight(rect))/2;
|
|
|
|
|
endRadius = NSWidth(rect)/2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Calls to CoreGraphics
|
|
|
|
|
CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
|
|
|
|
CGContextSaveGState(currentContext);
|
|
|
|
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
|
|
|
|
|
CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
|
|
|
|
#else
|
|
|
|
|
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
|
|
|
|
|
#endif
|
|
|
|
|
CGShadingRef myCGShading = CGShadingCreateRadial(colorspace, startPoint, startRadius, endPoint, endRadius, gradientFunction, true, true);
|
|
|
|
|
|
|
|
|
|
CGContextClipToRect (currentContext, *(CGRect *)&rect);
|
|
|
|
|
CGContextScaleCTM (currentContext, scalex, scaley);
|
|
|
|
|
CGContextTranslateCTM(currentContext, transx, transy);
|
|
|
|
|
CGContextDrawShading (currentContext, myCGShading); //This is where the action happens
|
|
|
|
|
|
|
|
|
|
CGShadingRelease(myCGShading);
|
|
|
|
|
CGColorSpaceRelease(colorspace);
|
|
|
|
|
CGContextRestoreGState(currentContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)fillBezierPath:(NSBezierPath *)path angle:(float)angle
|
|
|
|
|
{
|
|
|
|
|
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
|
|
|
|
|
[currentContext saveGraphicsState];
|
|
|
|
|
NSAffineTransform *transform = [[NSAffineTransform alloc] init];
|
|
|
|
|
|
|
|
|
|
[transform rotateByDegrees:-angle];
|
|
|
|
|
[path transformUsingAffineTransform:transform];
|
|
|
|
|
[transform invert];
|
|
|
|
|
[transform concat];
|
|
|
|
|
|
|
|
|
|
[path addClip];
|
|
|
|
|
[self fillRect:[path bounds] angle:0];
|
|
|
|
|
[path transformUsingAffineTransform:transform];
|
|
|
|
|
[transform release];
|
|
|
|
|
[currentContext restoreGraphicsState];
|
|
|
|
|
}
|
|
|
|
|
- (void)radialFillBezierPath:(NSBezierPath *)path
|
|
|
|
|
{
|
|
|
|
|
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
|
|
|
|
|
[currentContext saveGraphicsState];
|
|
|
|
|
[path addClip];
|
|
|
|
|
[self radialFillRect:[path bounds]];
|
|
|
|
|
[currentContext restoreGraphicsState];
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Private Methods
|
|
|
|
|
- (void)setBlendingMode:(CTGradientBlendingMode)mode;
|
|
|
|
|
{
|
|
|
|
|
blendingMode = mode;
|
|
|
|
|
|
|
|
|
|
//Choose what blending function to use
|
|
|
|
|
void *evaluationFunction;
|
|
|
|
|
switch(blendingMode)
|
|
|
|
|
{
|
|
|
|
|
case CTLinearBlendingMode:
|
|
|
|
|
evaluationFunction = &linearEvaluation; break;
|
|
|
|
|
case CTChromaticBlendingMode:
|
|
|
|
|
evaluationFunction = &chromaticEvaluation; break;
|
|
|
|
|
case CTInverseChromaticBlendingMode:
|
|
|
|
|
evaluationFunction = &inverseChromaticEvaluation; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//replace the current CoreGraphics Function with new one
|
|
|
|
|
if(gradientFunction != NULL)
|
|
|
|
|
CGFunctionRelease(gradientFunction);
|
|
|
|
|
|
|
|
|
|
CGFunctionCallbacks evaluationCallbackInfo = {0 , evaluationFunction, NULL}; //Version, evaluator function, cleanup function
|
|
|
|
|
|
|
|
|
|
static const float input_value_range [2] = { 0, 1 }; //range for the evaluator input
|
|
|
|
|
static const float output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 }; //ranges for the evaluator output (4 returned values)
|
|
|
|
|
|
|
|
|
|
gradientFunction = CGFunctionCreate(&elementList, //the two transition colors
|
|
|
|
|
1, input_value_range , //number of inputs (just fraction of progression)
|
|
|
|
|
4, output_value_ranges, //number of outputs (4 - RGBa)
|
|
|
|
|
&evaluationCallbackInfo); //info for using the evaluator function
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)addElement:(CTGradientElement *)newElement
|
2008-05-10 14:02:19 +00:00
|
|
|
|
{
|
2007-09-16 01:02:06 +00:00
|
|
|
|
if(elementList == nil || newElement->position < elementList->position) //inserting at beginning of list
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *tmpNext = elementList;
|
|
|
|
|
elementList = malloc(sizeof(CTGradientElement));
|
|
|
|
|
*elementList = *newElement;
|
|
|
|
|
elementList->nextElement = tmpNext;
|
|
|
|
|
}
|
|
|
|
|
else //inserting somewhere inside list
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *curElement = elementList;
|
|
|
|
|
|
|
|
|
|
while(curElement->nextElement != nil && !((curElement->position <= newElement->position) && (newElement->position < curElement->nextElement->position)))
|
|
|
|
|
{
|
|
|
|
|
curElement = curElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CTGradientElement *tmpNext = curElement->nextElement;
|
|
|
|
|
curElement->nextElement = malloc(sizeof(CTGradientElement));
|
|
|
|
|
*(curElement->nextElement) = *newElement;
|
|
|
|
|
curElement->nextElement->nextElement = tmpNext;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (CTGradientElement)removeElementAtIndex:(unsigned)index
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement removedElement;
|
|
|
|
|
|
|
|
|
|
if(elementList != nil)
|
|
|
|
|
{
|
|
|
|
|
if(index == 0)
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *tmpNext = elementList;
|
|
|
|
|
elementList = elementList->nextElement;
|
|
|
|
|
|
|
|
|
|
removedElement = *tmpNext;
|
|
|
|
|
free(tmpNext);
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned count = 1; //we want to start one ahead
|
|
|
|
|
CTGradientElement *currentElement = elementList;
|
|
|
|
|
while(currentElement->nextElement != nil)
|
|
|
|
|
{
|
|
|
|
|
if(count == index)
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *tmpNext = currentElement->nextElement;
|
|
|
|
|
currentElement->nextElement = currentElement->nextElement->nextElement;
|
|
|
|
|
|
|
|
|
|
removedElement = *tmpNext;
|
|
|
|
|
free(tmpNext);
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count++;
|
|
|
|
|
currentElement = currentElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//element is not found, return empty element
|
|
|
|
|
removedElement.red = 0.0;
|
|
|
|
|
removedElement.green = 0.0;
|
|
|
|
|
removedElement.blue = 0.0;
|
|
|
|
|
removedElement.alpha = 0.0;
|
|
|
|
|
removedElement.position = NAN;
|
|
|
|
|
removedElement.nextElement = nil;
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (CTGradientElement)removeElementAtPosition:(float)position
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement removedElement;
|
|
|
|
|
|
|
|
|
|
if(elementList != nil)
|
|
|
|
|
{
|
|
|
|
|
if(elementList->position == position)
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *tmpNext = elementList;
|
|
|
|
|
elementList = elementList->nextElement;
|
|
|
|
|
|
|
|
|
|
removedElement = *tmpNext;
|
|
|
|
|
free(tmpNext);
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *curElement = elementList;
|
|
|
|
|
while(curElement->nextElement != nil)
|
|
|
|
|
{
|
|
|
|
|
if(curElement->nextElement->position == position)
|
|
|
|
|
{
|
|
|
|
|
CTGradientElement *tmpNext = curElement->nextElement;
|
|
|
|
|
curElement->nextElement = curElement->nextElement->nextElement;
|
|
|
|
|
|
|
|
|
|
removedElement = *tmpNext;
|
|
|
|
|
free(tmpNext);
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//element is not found, return empty element
|
|
|
|
|
removedElement.red = 0.0;
|
|
|
|
|
removedElement.green = 0.0;
|
|
|
|
|
removedElement.blue = 0.0;
|
|
|
|
|
removedElement.alpha = 0.0;
|
|
|
|
|
removedElement.position = NAN;
|
|
|
|
|
removedElement.nextElement = nil;
|
|
|
|
|
|
|
|
|
|
return removedElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (CTGradientElement *)elementAtIndex:(unsigned)index;
|
|
|
|
|
{
|
|
|
|
|
unsigned count = 0;
|
|
|
|
|
CTGradientElement *currentElement = elementList;
|
|
|
|
|
|
|
|
|
|
while(currentElement != nil)
|
|
|
|
|
{
|
|
|
|
|
if(count == index)
|
|
|
|
|
return currentElement;
|
|
|
|
|
|
|
|
|
|
count++;
|
|
|
|
|
currentElement = currentElement->nextElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark Core Graphics
|
|
|
|
|
//////////////////////////////////////Blending Functions/////////////////////////////////////
|
|
|
|
|
void linearEvaluation (void *info, const float *in, float *out)
|
|
|
|
|
{
|
|
|
|
|
float position = *in;
|
|
|
|
|
|
|
|
|
|
if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
|
|
|
|
|
{
|
|
|
|
|
out[0] = out[1] = out[2] = out[3] = 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//This grabs the first two colors in the sequence
|
|
|
|
|
CTGradientElement *color1 = *(CTGradientElement **)info;
|
|
|
|
|
CTGradientElement *color2 = color1->nextElement;
|
|
|
|
|
|
|
|
|
|
//make sure first color and second color are on other sides of position
|
|
|
|
|
while(color2 != nil && color2->position < position)
|
|
|
|
|
{
|
|
|
|
|
color1 = color2;
|
|
|
|
|
color2 = color1->nextElement;
|
|
|
|
|
}
|
|
|
|
|
//if we don't have another color then make next color the same color
|
|
|
|
|
if(color2 == nil)
|
|
|
|
|
{
|
|
|
|
|
color2 = color1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//----------FailSafe settings----------
|
|
|
|
|
//color1->red = 1; color2->red = 0;
|
|
|
|
|
//color1->green = 1; color2->green = 0;
|
|
|
|
|
//color1->blue = 1; color2->blue = 0;
|
|
|
|
|
//color1->alpha = 1; color2->alpha = 1;
|
|
|
|
|
//color1->position = .5;
|
|
|
|
|
//color2->position = .5;
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
|
|
if(position <= color1->position) //Make all below color color1's position equal to color1
|
|
|
|
|
{
|
|
|
|
|
out[0] = color1->red;
|
|
|
|
|
out[1] = color1->green;
|
|
|
|
|
out[2] = color1->blue;
|
|
|
|
|
out[3] = color1->alpha;
|
|
|
|
|
}
|
|
|
|
|
else if (position >= color2->position) //Make all above color color2's position equal to color2
|
|
|
|
|
{
|
|
|
|
|
out[0] = color2->red;
|
|
|
|
|
out[1] = color2->green;
|
|
|
|
|
out[2] = color2->blue;
|
|
|
|
|
out[3] = color2->alpha;
|
|
|
|
|
}
|
|
|
|
|
else //Interpolate color at postions between color1 and color1
|
|
|
|
|
{
|
|
|
|
|
//adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
|
|
|
|
|
position = (position-color1->position)/(color2->position - color1->position);
|
|
|
|
|
|
|
|
|
|
out[0] = (color2->red - color1->red )*position + color1->red;
|
|
|
|
|
out[1] = (color2->green - color1->green)*position + color1->green;
|
|
|
|
|
out[2] = (color2->blue - color1->blue )*position + color1->blue;
|
|
|
|
|
out[3] = (color2->alpha - color1->alpha)*position + color1->alpha;
|
2008-05-10 14:02:19 +00:00
|
|
|
|
}
|
2007-09-16 01:02:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Chromatic Evaluation -
|
|
|
|
|
// This blends colors by their Hue, Saturation, and Value(Brightness) right now I just
|
|
|
|
|
// transform the RGB values stored in the CTGradientElements to HSB, in the future I may
|
|
|
|
|
// streamline it to avoid transforming in and out of HSB colorspace *for later*
|
|
|
|
|
//
|
|
|
|
|
// For the chromatic blend we shift the hue of color1 to meet the hue of color2. To do
|
|
|
|
|
// this we will add to the hue's angle (if we subtract we'll be doing the inverse
|
|
|
|
|
// chromatic...scroll down more for that). All we need to do is keep adding to the hue
|
|
|
|
|
// until we wrap around the colorwheel and get to color2.
|
|
|
|
|
void chromaticEvaluation(void *info, const float *in, float *out)
|
|
|
|
|
{
|
|
|
|
|
float position = *in;
|
|
|
|
|
|
|
|
|
|
if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
|
|
|
|
|
{
|
|
|
|
|
out[0] = out[1] = out[2] = out[3] = 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//This grabs the first two colors in the sequence
|
|
|
|
|
CTGradientElement *color1 = *(CTGradientElement **)info;
|
|
|
|
|
CTGradientElement *color2 = color1->nextElement;
|
|
|
|
|
|
|
|
|
|
float c1[4];
|
|
|
|
|
float c2[4];
|
|
|
|
|
|
|
|
|
|
//make sure first color and second color are on other sides of position
|
|
|
|
|
while(color2 != nil && color2->position < position)
|
|
|
|
|
{
|
|
|
|
|
color1 = color2;
|
|
|
|
|
color2 = color1->nextElement;
|
|
|
|
|
}
|
|
|
|
|
//if we don't have another color then make next color the same color
|
|
|
|
|
if(color2 == nil)
|
|
|
|
|
{
|
|
|
|
|
color2 = color1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c1[0] = color1->red;
|
|
|
|
|
c1[1] = color1->green;
|
|
|
|
|
c1[2] = color1->blue;
|
|
|
|
|
c1[3] = color1->alpha;
|
|
|
|
|
|
|
|
|
|
c2[0] = color2->red;
|
|
|
|
|
c2[1] = color2->green;
|
|
|
|
|
c2[2] = color2->blue;
|
|
|
|
|
c2[3] = color2->alpha;
|
|
|
|
|
|
|
|
|
|
transformRGB_HSV(c1);
|
|
|
|
|
transformRGB_HSV(c2);
|
|
|
|
|
resolveHSV(c1,c2);
|
|
|
|
|
|
|
|
|
|
if(c1[0] > c2[0]) //if color1's hue is higher than color2's hue then
|
|
|
|
|
c2[0] += 360; // we need to move c2 one revolution around the wheel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(position <= color1->position) //Make all below color color1's position equal to color1
|
|
|
|
|
{
|
|
|
|
|
out[0] = c1[0];
|
|
|
|
|
out[1] = c1[1];
|
|
|
|
|
out[2] = c1[2];
|
|
|
|
|
out[3] = c1[3];
|
|
|
|
|
}
|
|
|
|
|
else if (position >= color2->position) //Make all above color color2's position equal to color2
|
|
|
|
|
{
|
|
|
|
|
out[0] = c2[0];
|
|
|
|
|
out[1] = c2[1];
|
|
|
|
|
out[2] = c2[2];
|
|
|
|
|
out[3] = c2[3];
|
|
|
|
|
}
|
|
|
|
|
else //Interpolate color at postions between color1 and color1
|
|
|
|
|
{
|
|
|
|
|
//adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
|
|
|
|
|
position = (position-color1->position)/(color2->position - color1->position);
|
|
|
|
|
|
|
|
|
|
out[0] = (c2[0] - c1[0])*position + c1[0];
|
|
|
|
|
out[1] = (c2[1] - c1[1])*position + c1[1];
|
|
|
|
|
out[2] = (c2[2] - c1[2])*position + c1[2];
|
|
|
|
|
out[3] = (c2[3] - c1[3])*position + c1[3];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transformHSV_RGB(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Inverse Chromatic Evaluation -
|
|
|
|
|
// Inverse Chromatic is about the same story as Chromatic Blend, but here the Hue
|
|
|
|
|
// is strictly decreasing, that is we need to get from color1 to color2 by decreasing
|
|
|
|
|
// the 'angle' (i.e. 90<EFBFBD> -> 180<EFBFBD> would be done by subtracting 270<EFBFBD> and getting -180<EFBFBD>...
|
|
|
|
|
// which is equivalent to 180<EFBFBD> mod 360<EFBFBD>
|
|
|
|
|
void inverseChromaticEvaluation(void *info, const float *in, float *out)
|
|
|
|
|
{
|
|
|
|
|
float position = *in;
|
|
|
|
|
|
|
|
|
|
if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
|
|
|
|
|
{
|
|
|
|
|
out[0] = out[1] = out[2] = out[3] = 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//This grabs the first two colors in the sequence
|
|
|
|
|
CTGradientElement *color1 = *(CTGradientElement **)info;
|
|
|
|
|
CTGradientElement *color2 = color1->nextElement;
|
|
|
|
|
|
|
|
|
|
float c1[4];
|
|
|
|
|
float c2[4];
|
|
|
|
|
|
|
|
|
|
//make sure first color and second color are on other sides of position
|
|
|
|
|
while(color2 != nil && color2->position < position)
|
|
|
|
|
{
|
|
|
|
|
color1 = color2;
|
|
|
|
|
color2 = color1->nextElement;
|
|
|
|
|
}
|
|
|
|
|
//if we don't have another color then make next color the same color
|
|
|
|
|
if(color2 == nil)
|
|
|
|
|
{
|
|
|
|
|
color2 = color1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c1[0] = color1->red;
|
|
|
|
|
c1[1] = color1->green;
|
|
|
|
|
c1[2] = color1->blue;
|
|
|
|
|
c1[3] = color1->alpha;
|
|
|
|
|
|
|
|
|
|
c2[0] = color2->red;
|
|
|
|
|
c2[1] = color2->green;
|
|
|
|
|
c2[2] = color2->blue;
|
|
|
|
|
c2[3] = color2->alpha;
|
|
|
|
|
|
|
|
|
|
transformRGB_HSV(c1);
|
|
|
|
|
transformRGB_HSV(c2);
|
|
|
|
|
resolveHSV(c1,c2);
|
|
|
|
|
|
|
|
|
|
if(c1[0] < c2[0]) //if color1's hue is higher than color2's hue then
|
|
|
|
|
c1[0] += 360; // we need to move c2 one revolution back on the wheel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(position <= color1->position) //Make all below color color1's position equal to color1
|
|
|
|
|
{
|
|
|
|
|
out[0] = c1[0];
|
|
|
|
|
out[1] = c1[1];
|
|
|
|
|
out[2] = c1[2];
|
|
|
|
|
out[3] = c1[3];
|
|
|
|
|
}
|
|
|
|
|
else if (position >= color2->position) //Make all above color color2's position equal to color2
|
|
|
|
|
{
|
|
|
|
|
out[0] = c2[0];
|
|
|
|
|
out[1] = c2[1];
|
|
|
|
|
out[2] = c2[2];
|
|
|
|
|
out[3] = c2[3];
|
|
|
|
|
}
|
|
|
|
|
else //Interpolate color at postions between color1 and color1
|
|
|
|
|
{
|
|
|
|
|
//adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
|
|
|
|
|
position = (position-color1->position)/(color2->position - color1->position);
|
|
|
|
|
|
|
|
|
|
out[0] = (c2[0] - c1[0])*position + c1[0];
|
|
|
|
|
out[1] = (c2[1] - c1[1])*position + c1[1];
|
|
|
|
|
out[2] = (c2[2] - c1[2])*position + c1[2];
|
|
|
|
|
out[3] = (c2[3] - c1[3])*position + c1[3];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transformHSV_RGB(out);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2008-05-10 14:02:19 +00:00
|
|
|
|
|
2007-09-16 01:02:06 +00:00
|
|
|
|
void transformRGB_HSV(float *components) //H,S,B -> R,G,B
|
|
|
|
|
{
|
|
|
|
|
float H, S, V;
|
|
|
|
|
float R = components[0],
|
|
|
|
|
G = components[1],
|
|
|
|
|
B = components[2];
|
|
|
|
|
|
|
|
|
|
float MAX = R > G ? (R > B ? R : B) : (G > B ? G : B),
|
|
|
|
|
MIN = R < G ? (R < B ? R : B) : (G < B ? G : B);
|
|
|
|
|
|
|
|
|
|
if(MAX == MIN)
|
|
|
|
|
H = NAN;
|
|
|
|
|
else if(MAX == R)
|
|
|
|
|
if(G >= B)
|
|
|
|
|
H = 60*(G-B)/(MAX-MIN)+0;
|
|
|
|
|
else
|
|
|
|
|
H = 60*(G-B)/(MAX-MIN)+360;
|
|
|
|
|
else if(MAX == G)
|
|
|
|
|
H = 60*(B-R)/(MAX-MIN)+120;
|
|
|
|
|
else if(MAX == B)
|
|
|
|
|
H = 60*(R-G)/(MAX-MIN)+240;
|
|
|
|
|
|
|
|
|
|
S = MAX == 0 ? 0 : 1 - MIN/MAX;
|
|
|
|
|
V = MAX;
|
|
|
|
|
|
|
|
|
|
components[0] = H;
|
|
|
|
|
components[1] = S;
|
|
|
|
|
components[2] = V;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void transformHSV_RGB(float *components) //H,S,B -> R,G,B
|
|
|
|
|
{
|
|
|
|
|
float R, G, B;
|
|
|
|
|
float H = fmodf(components[0],359), //map to [0,360)
|
|
|
|
|
S = components[1],
|
|
|
|
|
V = components[2];
|
|
|
|
|
|
|
|
|
|
int Hi = (int)floorf(H/60.) % 6;
|
|
|
|
|
float f = H/60-Hi,
|
|
|
|
|
p = V*(1-S),
|
|
|
|
|
q = V*(1-f*S),
|
|
|
|
|
t = V*(1-(1-f)*S);
|
|
|
|
|
|
|
|
|
|
switch (Hi)
|
|
|
|
|
{
|
|
|
|
|
case 0: R=V;G=t;B=p; break;
|
|
|
|
|
case 1: R=q;G=V;B=p; break;
|
|
|
|
|
case 2: R=p;G=V;B=t; break;
|
|
|
|
|
case 3: R=p;G=q;B=V; break;
|
|
|
|
|
case 4: R=t;G=p;B=V; break;
|
|
|
|
|
case 5: R=V;G=p;B=q; break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
components[0] = R;
|
|
|
|
|
components[1] = G;
|
|
|
|
|
components[2] = B;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resolveHSV(float *color1, float *color2) //H value may be undefined (i.e. graycale color)
|
|
|
|
|
{ // we want to fill it with a sensible value
|
|
|
|
|
if(isnan(color1[0]) && isnan(color2[0]))
|
|
|
|
|
color1[0] = color2[0] = 0;
|
|
|
|
|
else if(isnan(color1[0]))
|
|
|
|
|
color1[0] = color2[0];
|
|
|
|
|
else if(isnan(color2[0]))
|
|
|
|
|
color2[0] = color1[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|