//
//  CTGradient.m
//
//  Created by Chad Weider on 2/14/07.
//  Copyright (c) 2007 Chad Weider.
//  Some rights reserved: <http://creativecommons.org/licenses/by/2.5/>
//
//  Version: 1.6

#import "CTGradient.h"

@interface CTGradient (Private)
- (void)_commonInit;
- (void)setBlendingMode:(CTGradientBlendingMode)mode;
- (void)addElement:(CTGradientElement*)newElement;

- (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];
}


+ (id)dividerGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.84;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.90;
	color2.alpha = 1.00;
	color2.position = 0.5;
	
	CTGradientElement color3;
	color3.red = color3.green = color3.blue  = 0.84;
	color3.alpha = 1.00;
	color3.position = 1.0;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	[newInstance addElement:&color3];
	
	return [newInstance autorelease];
}


+ (id)statusBarGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.872;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.77;
	color2.alpha = 1.00;
	color2.position = 0.618;
	
	CTGradientElement color3;
	color3.red = color3.green = color3.blue  = 0.74;
	color3.alpha = 1.00;
	color3.position = 1.0;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	[newInstance addElement:&color3];
	
	return [newInstance autorelease];
}


+ (id)aquaSelectedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red   = 0.58;
	color1.green = 0.86;
	color1.blue  = 0.98;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red   = 0.42;
	color2.green = 0.68;
	color2.blue  = 0.90;
	color2.alpha = 1.00;
	color2.position = 11.5/23;
	
	CTGradientElement color3;
	color3.red   = 0.64;
	color3.green = 0.80;
	color3.blue  = 0.94;
	color3.alpha = 1.00;
	color3.position = 11.5/23;
	
	CTGradientElement color4;
	color4.red   = 0.56;
	color4.green = 0.70;
	color4.blue  = 0.90;
	color4.alpha = 1.00;
	color4.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	[newInstance addElement:&color3];
	[newInstance addElement:&color4];
	
	return [newInstance autorelease];
}

+ (id)aquaNormalGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.95;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.83;
	color2.alpha = 1.00;
	color2.position = 11.5/23;
	
	CTGradientElement color3;
	color3.red = color3.green = color3.blue  = 0.95;
	color3.alpha = 1.00;
	color3.position = 11.5/23;
	
	CTGradientElement color4;
	color4.red = color4.green = color4.blue  = 0.92;
	color4.alpha = 1.00;
	color4.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	[newInstance addElement:&color3];
	[newInstance addElement:&color4];
	
	return [newInstance autorelease];
}

+ (id)aquaPressedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.80;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.64;
	color2.alpha = 1.00;
	color2.position = 11.5/23;
	
	CTGradientElement color3;
	color3.red = color3.green = color3.blue  = 0.80;
	color3.alpha = 1.00;
	color3.position = 11.5/23;
	
	CTGradientElement color4;
	color4.red = color4.green = color4.blue  = 0.77;
	color4.alpha = 1.00;
	color4.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	[newInstance addElement:&color3];
	[newInstance addElement:&color4];
	
	return [newInstance autorelease];
}

+ (id)unifiedSelectedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.85;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.95;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)unifiedNormalGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.75;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.90;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)unifiedPressedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.60;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.75;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)unifiedDarkGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red = color1.green = color1.blue  = 0.68;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red = color2.green = color2.blue  = 0.83;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)sourceListSelectedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red   = 0.06;
	color1.green = 0.37;
	color1.blue  = 0.85;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red   = 0.30;
	color2.green = 0.60;
	color2.blue  = 0.92;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)sourceListUnselectedGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red   = 0.43;
	color1.green = 0.43;
	color1.blue  = 0.43;
	color1.alpha = 1.00;
	color1.position = 0;
	
	CTGradientElement color2;
	color2.red   = 0.60;
	color2.green = 0.60;
	color2.blue  = 0.60;
	color2.alpha = 1.00;
	color2.position = 1;
	
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	return [newInstance autorelease];
}

+ (id)rainbowGradient
{
	id newInstance = [[[self class] alloc] init];
	
	CTGradientElement color1;
	color1.red   = 1.00;
	color1.green = 0.00;
	color1.blue  = 0.00;
	color1.alpha = 1.00;
	color1.position = 0.0;
	
	CTGradientElement color2;
	color2.red   = 0.54;
	color2.green = 0.00;
	color2.blue  = 1.00;
	color2.alpha = 1.00;
	color2.position = 1.0;
    
	[newInstance addElement:&color1];
	[newInstance addElement:&color2];
	
	[newInstance setBlendingMode:CTChromaticBlendingMode];
	
	return [newInstance autorelease];
}

+ (id)hydrogenSpectrumGradient
{
	id newInstance = [[[self class] alloc] init];
	
	struct {float hue; float position; float width;} colorBands[4];
	
	colorBands[0].hue = 22;
	colorBands[0].position = .145;
	colorBands[0].width = .01;
	
	colorBands[1].hue = 200;
	colorBands[1].position = .71;
	colorBands[1].width = .008;
	
	colorBands[2].hue = 253;
	colorBands[2].position = .885;
	colorBands[2].width = .005;
	
	colorBands[3].hue = 275;
	colorBands[3].position = .965;
	colorBands[3].width = .003;
	
	int i;
	/////////////////////////////
	for(i = 0; i < 4; i++)
	{	
		float color[4];
		color[0] = colorBands[i].hue - 180*colorBands[i].width;
		color[1] = 1;
		color[2] = 0.001;
		color[3] = 1;
		transformHSV_RGB(color);
		CTGradientElement fadeIn;
		fadeIn.red   = color[0];
		fadeIn.green = color[1];
		fadeIn.blue  = color[2];
		fadeIn.alpha = color[3];
		fadeIn.position = colorBands[i].position - colorBands[i].width;
		
		
		color[0] = colorBands[i].hue;
		color[1] = 1;
		color[2] = 1;
		color[3] = 1;
		transformHSV_RGB(color);
		CTGradientElement band;
		band.red   = color[0];
		band.green = color[1];
		band.blue  = color[2];
		band.alpha = color[3];
		band.position = colorBands[i].position;
		
		color[0] = colorBands[i].hue + 180*colorBands[i].width;
		color[1] = 1;
		color[2] = 0.001;
		color[3] = 1;
		transformHSV_RGB(color);
		CTGradientElement fadeOut;
		fadeOut.red   = color[0];
		fadeOut.green = color[1];
		fadeOut.blue  = color[2];
		fadeOut.alpha = color[3];
		fadeOut.position = colorBands[i].position + colorBands[i].width;
		
		
		[newInstance addElement:&fadeIn];
		[newInstance addElement:&band];
		[newInstance addElement:&fadeOut];
	}
	
	[newInstance setBlendingMode:CTChromaticBlendingMode];
	
	return [newInstance autorelease];
}

#pragma mark -



#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;
	}
	
	
	return [NSColor colorWithCalibratedRed:components[0]
									 green:components[1]
									  blue:components[2]
									 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);
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
	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
{
	CGPoint startPoint , endPoint;
	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
{
	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;
  	}
}




//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å¼ -> 180å¼ would be done by subtracting 270å¼ and getting -180å¼...
//	which is equivalent to 180å¼ mod 360å¼
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);
}



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