No need for these arrays (which I added while hunting and forgot to remove)
[adiumx.git] / Frameworks / ShortcutRecorder / Source / CTGradient.m
blob07259a68b39b9e74551128b3ec08c2ee5cff07ad
1 //
2 //  CTGradient.m
3 //
4 //  Created by Chad Weider on 12/3/05.
5 //  Copyright (c) 2006 Cotingent.
6 //  Some rights reserved: <http://creativecommons.org/licenses/by/2.5/>
7 //
8 //  Version: 1.5
10 #import "CTGradient.h"
12 @interface CTGradient (Private)
13 - (void)_commonInit;
14 - (void)setBlendingMode:(CTGradientBlendingMode)mode;
15 - (void)addElement:(CTGradientElement*)newElement;
17 - (CTGradientElement *)elementAtIndex:(unsigned)index;
19 - (CTGradientElement)removeElementAtIndex:(unsigned)index;
20 - (CTGradientElement)removeElementAtPosition:(float)position;
21 @end
23 //C Fuctions for color blending
24 void linearEvaluation   (void *info, const float *in, float *out);
25 void chromaticEvaluation(void *info, const float *in, float *out);
26 void inverseChromaticEvaluation(void *info, const float *in, float *out);
27 void transformRGB_HSV(float *components);
28 void transformHSV_RGB(float *components);
29 void resolveHSV(float *color1, float *color2);
32 @implementation CTGradient
33 /////////////////////////////////////Initialization Type Stuff
34 - (id)init
35   {
36   self = [super init];
37   
38   if (self != nil)
39         {
40         [self _commonInit];
41         [self setBlendingMode:CTLinearBlendingMode];
42         }
43   return self;
44   }
46 - (void)_commonInit
47   {
48   elementList = nil;
49   }
51 - (void)dealloc
52   {
53   CGFunctionRelease(gradientFunction);
54   
55   CTGradientElement *elementToRemove = elementList;
56   while(elementList != nil)
57         {
58         elementToRemove = elementList;
59         elementList = elementList->nextElement;
60         free(elementToRemove);
61         }
62   
63   [super dealloc];
64   }
66 - (id)copyWithZone:(NSZone *)zone
67   {
68   CTGradient *copy = [[[self class] allocWithZone:zone] init];
69   
70   //now just copy my elementlist
71   CTGradientElement *currentElement = elementList;
72   while(currentElement != nil)
73         {
74         [copy addElement:currentElement];
75         currentElement = currentElement->nextElement;
76         }
77   
78   [copy setBlendingMode:blendingMode];
79   
80   return copy;
81   }
83 - (void)encodeWithCoder:(NSCoder *)coder
84   {
85   if([coder allowsKeyedCoding])
86         {
87         unsigned count = 0;
88         CTGradientElement *currentElement = elementList;
89         while(currentElement != nil)
90                 {
91                 [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->red)];
92                 [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->green)];
93                 [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->blue)];
94                 [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->alpha)];
95                 [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->position)];
96                 
97                 count++;
98                 currentElement = currentElement->nextElement;
99                 }
100         [coder encodeInt:count forKey:@"CTGradientElementCount"];
101         [coder encodeInt:blendingMode forKey:@"CTGradientBlendingMode"];
102         }
103   else
104         [NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
105   }
107 - (id)initWithCoder:(NSCoder *)coder
108   {
109   [self _commonInit];
110   
111   [self setBlendingMode:[coder decodeIntForKey:@"CTGradientBlendingMode"]];
112   unsigned count = [coder decodeIntForKey:@"CTGradientElementCount"];
113   
114   while(count != 0)
115         {
116     CTGradientElement newElement;
117         
118         [coder decodeValueOfObjCType:@encode(float) at:&(newElement.red)];
119         [coder decodeValueOfObjCType:@encode(float) at:&(newElement.green)];
120         [coder decodeValueOfObjCType:@encode(float) at:&(newElement.blue)];
121         [coder decodeValueOfObjCType:@encode(float) at:&(newElement.alpha)];
122         [coder decodeValueOfObjCType:@encode(float) at:&(newElement.position)];
123         
124         count--;
125         [self addElement:&newElement];
126         }
127   return self;
128   }
131 #pragma mark -
135 #pragma mark Creation
136 + (id)gradientWithBeginningColor:(NSColor *)begin endingColor:(NSColor *)end
137   {
138   id newInstance = [[[self class] alloc] init];
139   
140   CTGradientElement color1;
141   CTGradientElement color2;
142   
143   [[begin colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color1.red
144                                                                                                                            green:&color1.green
145                                                                                                                                 blue:&color1.blue
146                                                                                                                            alpha:&color1.alpha];
147   
148   [[end   colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color2.red
149                                                                                                                            green:&color2.green
150                                                                                                                                 blue:&color2.blue
151                                                                                                                            alpha:&color2.alpha];  
152   color1.position = 0;
153   color2.position = 1;
154   
155   [newInstance addElement:&color1];
156   [newInstance addElement:&color2];
157   
158   return [newInstance autorelease];
159   }
161 + (id)aquaSelectedGradient
162   {
163   id newInstance = [[[self class] alloc] init];
164   
165   CTGradientElement color1;
166   color1.red   = 0.58;
167   color1.green = 0.86;
168   color1.blue  = 0.98;
169   color1.alpha = 1.00;
170   color1.position = 0;
171   
172   CTGradientElement color2;
173   color2.red   = 0.42;
174   color2.green = 0.68;
175   color2.blue  = 0.90;
176   color2.alpha = 1.00;
177   color2.position = 11.5/23;
178   
179   CTGradientElement color3;
180   color3.red   = 0.64;
181   color3.green = 0.80;
182   color3.blue  = 0.94;
183   color3.alpha = 1.00;
184   color3.position = 11.5/23;
185   
186   CTGradientElement color4;
187   color4.red   = 0.56;
188   color4.green = 0.70;
189   color4.blue  = 0.90;
190   color4.alpha = 1.00;
191   color4.position = 1;
192   
193   [newInstance addElement:&color1];
194   [newInstance addElement:&color2];
195   [newInstance addElement:&color3];
196   [newInstance addElement:&color4];
197   
198   return [newInstance autorelease];
199   }
201 + (id)aquaNormalGradient
202   {
203   id newInstance = [[[self class] alloc] init];
204   
205   CTGradientElement color1;
206   color1.red = color1.green = color1.blue  = 0.95;
207   color1.alpha = 1.00;
208   color1.position = 0;
209   
210   CTGradientElement color2;
211   color2.red = color2.green = color2.blue  = 0.83;
212   color2.alpha = 1.00;
213   color2.position = 11.5/23;
214   
215   CTGradientElement color3;
216   color3.red = color3.green = color3.blue  = 0.95;
217   color3.alpha = 1.00;
218   color3.position = 11.5/23;
219   
220   CTGradientElement color4;
221   color4.red = color4.green = color4.blue  = 0.92;
222   color4.alpha = 1.00;
223   color4.position = 1;
224   
225   [newInstance addElement:&color1];
226   [newInstance addElement:&color2];
227   [newInstance addElement:&color3];
228   [newInstance addElement:&color4];
229   
230   return [newInstance autorelease];
231   }
233 + (id)aquaPressedGradient
234   {
235   id newInstance = [[[self class] alloc] init];
236   
237   CTGradientElement color1;
238   color1.red = color1.green = color1.blue  = 0.80;
239   color1.alpha = 1.00;
240   color1.position = 0;
241   
242   CTGradientElement color2;
243   color2.red = color2.green = color2.blue  = 0.64;
244   color2.alpha = 1.00;
245   color2.position = 11.5/23;
246   
247   CTGradientElement color3;
248   color3.red = color3.green = color3.blue  = 0.80;
249   color3.alpha = 1.00;
250   color3.position = 11.5/23;
251   
252   CTGradientElement color4;
253   color4.red = color4.green = color4.blue  = 0.77;
254   color4.alpha = 1.00;
255   color4.position = 1;
256   
257   [newInstance addElement:&color1];
258   [newInstance addElement:&color2];
259   [newInstance addElement:&color3];
260   [newInstance addElement:&color4];
261   
262   return [newInstance autorelease];
263   }
265 + (id)unifiedSelectedGradient
266   {
267   id newInstance = [[[self class] alloc] init];
268   
269   CTGradientElement color1;
270   color1.red = color1.green = color1.blue  = 0.85;
271   color1.alpha = 1.00;
272   color1.position = 0;
273   
274   CTGradientElement color2;
275   color2.red = color2.green = color2.blue  = 0.95;
276   color2.alpha = 1.00;
277   color2.position = 1;
278   
279   [newInstance addElement:&color1];
280   [newInstance addElement:&color2];
281   
282   return [newInstance autorelease];
283   }
285 + (id)unifiedNormalGradient
286   {
287   id newInstance = [[[self class] alloc] init];
288   
289   CTGradientElement color1;
290   color1.red = color1.green = color1.blue  = 0.75;
291   color1.alpha = 1.00;
292   color1.position = 0;
293   
294   CTGradientElement color2;
295   color2.red = color2.green = color2.blue  = 0.90;
296   color2.alpha = 1.00;
297   color2.position = 1;
298   
299   [newInstance addElement:&color1];
300   [newInstance addElement:&color2];
301   
302   return [newInstance autorelease];
303   }
305 + (id)unifiedPressedGradient
306   {
307   id newInstance = [[[self class] alloc] init];
308   
309   CTGradientElement color1;
310   color1.red = color1.green = color1.blue  = 0.60;
311   color1.alpha = 1.00;
312   color1.position = 0;
313   
314   CTGradientElement color2;
315   color2.red = color2.green = color2.blue  = 0.75;
316   color2.alpha = 1.00;
317   color2.position = 1;
318   
319   [newInstance addElement:&color1];
320   [newInstance addElement:&color2];
321   
322   return [newInstance autorelease];
323   }
325 + (id)unifiedDarkGradient
326   {
327   id newInstance = [[[self class] alloc] init];
328   
329   CTGradientElement color1;
330   color1.red = color1.green = color1.blue  = 0.68;
331   color1.alpha = 1.00;
332   color1.position = 0;
333   
334   CTGradientElement color2;
335   color2.red = color2.green = color2.blue  = 0.83;
336   color2.alpha = 1.00;
337   color2.position = 1;
338   
339   [newInstance addElement:&color1];
340   [newInstance addElement:&color2];
341   
342   return [newInstance autorelease];
343   }
345 + (id)sourceListSelectedGradient
346   {
347   id newInstance = [[[self class] alloc] init];
348   
349   CTGradientElement color1;
350   color1.red   = 0.06;
351   color1.green = 0.37;
352   color1.blue  = 0.85;
353   color1.alpha = 1.00;
354   color1.position = 0;
355   
356   CTGradientElement color2;
357   color2.red   = 0.30;
358   color2.green = 0.60;
359   color2.blue  = 0.92;
360   color2.alpha = 1.00;
361   color2.position = 1;
362   
363   [newInstance addElement:&color1];
364   [newInstance addElement:&color2];
365   
366   return [newInstance autorelease];
367   }
369 + (id)sourceListUnselectedGradient
370   {
371   id newInstance = [[[self class] alloc] init];
372   
373   CTGradientElement color1;
374   color1.red   = 0.43;
375   color1.green = 0.43;
376   color1.blue  = 0.43;
377   color1.alpha = 1.00;
378   color1.position = 0;
379   
380   CTGradientElement color2;
381   color2.red   = 0.60;
382   color2.green = 0.60;
383   color2.blue  = 0.60;
384   color2.alpha = 1.00;
385   color2.position = 1;
386   
387   [newInstance addElement:&color1];
388   [newInstance addElement:&color2];
389   
390   return [newInstance autorelease];
391   }
393 + (id)rainbowGradient
394   {
395   id newInstance = [[[self class] alloc] init];
396   
397   CTGradientElement color1;
398   color1.red   = 1.00;
399   color1.green = 0.00;
400   color1.blue  = 0.00;
401   color1.alpha = 1.00;
402   color1.position = 0.0;
403   
404   CTGradientElement color2;
405   color2.red   = 0.54;
406   color2.green = 0.00;
407   color2.blue  = 1.00;
408   color2.alpha = 1.00;
409   color2.position = 1.0;
410     
411   [newInstance addElement:&color1];
412   [newInstance addElement:&color2];
413   
414   [newInstance setBlendingMode:CTChromaticBlendingMode];
415   
416   return [newInstance autorelease];
417   }
419 + (id)hydrogenSpectrumGradient
420   {
421   id newInstance = [[[self class] alloc] init];
422   
423   struct {float hue; float position; float width;} colorBands[4];
424   
425   colorBands[0].hue = 22;
426   colorBands[0].position = .145;
427   colorBands[0].width = .01;
428   
429   colorBands[1].hue = 200;
430   colorBands[1].position = .71;
431   colorBands[1].width = .008;
432   
433   colorBands[2].hue = 253;
434   colorBands[2].position = .885;
435   colorBands[2].width = .005;
436   
437   colorBands[3].hue = 275;
438   colorBands[3].position = .965;
439   colorBands[3].width = .003;
440   
441   int i;
442   /////////////////////////////
443   for(i = 0; i < 4; i++)
444         {       
445         float color[4];
446         color[0] = colorBands[i].hue - 180*colorBands[i].width;
447         color[1] = 1;
448         color[2] = 0.001;
449         color[3] = 1;
450         transformHSV_RGB(color);
451         CTGradientElement fadeIn;
452         fadeIn.red   = color[0];
453         fadeIn.green = color[1];
454         fadeIn.blue  = color[2];
455         fadeIn.alpha = color[3];
456         fadeIn.position = colorBands[i].position - colorBands[i].width;
457         
458         
459         color[0] = colorBands[i].hue;
460         color[1] = 1;
461         color[2] = 1;
462         color[3] = 1;
463         transformHSV_RGB(color);
464         CTGradientElement band;
465         band.red   = color[0];
466         band.green = color[1];
467         band.blue  = color[2];
468         band.alpha = color[3];
469         band.position = colorBands[i].position;
470         
471         color[0] = colorBands[i].hue + 180*colorBands[i].width;
472         color[1] = 1;
473         color[2] = 0.001;
474         color[3] = 1;
475         transformHSV_RGB(color);
476         CTGradientElement fadeOut;
477         fadeOut.red   = color[0];
478         fadeOut.green = color[1];
479         fadeOut.blue  = color[2];
480         fadeOut.alpha = color[3];
481         fadeOut.position = colorBands[i].position + colorBands[i].width;
482         
483         
484         [newInstance addElement:&fadeIn];
485         [newInstance addElement:&band];
486         [newInstance addElement:&fadeOut];
487         }
488   
489   [newInstance setBlendingMode:CTChromaticBlendingMode];
490   
491   return [newInstance autorelease];
492   }
494 #pragma mark -
498 #pragma mark Modification
499 - (CTGradient *)gradientWithAlphaComponent:(float)alpha
500   {
501   id newInstance = [[[self class] alloc] init];
502   
503   CTGradientElement *curElement = elementList;
504   CTGradientElement tempElement;
506   while(curElement != nil)
507         {
508         tempElement = *curElement;
509         tempElement.alpha = alpha;
510         [newInstance addElement:&tempElement];
511         
512         curElement = curElement->nextElement;
513         }
514   
515   return [newInstance autorelease];
516   }
518 - (CTGradient *)gradientWithBlendingMode:(CTGradientBlendingMode)mode
519   {
520   CTGradient *newGradient = [self copy];  
521   
522   [newGradient setBlendingMode:mode];
523   
524   return [newGradient autorelease];
525   }
528 //Adds a color stop with <color> at <position> in elementList
529 //(if two elements are at the same position then added imediatly after the one that was there already)
530 - (CTGradient *)addColorStop:(NSColor *)color atPosition:(float)position
531   {
532   CTGradient *newGradient = [self copy];
533   CTGradientElement newGradientElement;
534   
535   //put the components of color into the newGradientElement - must make sure it is a RGB color (not Gray or CMYK) 
536   [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&newGradientElement.red
537                                                                                                                            green:&newGradientElement.green
538                                                                                                                                 blue:&newGradientElement.blue
539                                                                                                                            alpha:&newGradientElement.alpha];
540   newGradientElement.position = position;
541   
542   //Pass it off to addElement to take care of adding it to the elementList
543   [newGradient addElement:&newGradientElement];
544   
545   return [newGradient autorelease];
546   }
549 //Removes the color stop at <position> from elementList
550 - (CTGradient *)removeColorStopAtPosition:(float)position
551   {
552   CTGradient *newGradient = [self copy];
553   CTGradientElement removedElement = [newGradient removeElementAtPosition:position];
554   
555   if(isnan(removedElement.position))
556         [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtPosition:]: no such colorStop at position (%f)", [self class], position];
557   
558   return [newGradient autorelease];
559   }
561 - (CTGradient *)removeColorStopAtIndex:(unsigned)index
562   {
563   CTGradient *newGradient = [self copy];
564   CTGradientElement removedElement = [newGradient removeElementAtIndex:index];
565   
566   if(isnan(removedElement.position))
567         [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
568   
569   return [newGradient autorelease];
570   }
571 #pragma mark -
575 #pragma mark Information
576 - (CTGradientBlendingMode)blendingMode
577   {
578   return blendingMode;
579   }
581 //Returns color at <position> in gradient
582 - (NSColor *)colorStopAtIndex:(unsigned)index
583   {
584   CTGradientElement *element = [self elementAtIndex:index];
585   
586   if(element != nil)
587         return [NSColor colorWithCalibratedRed:element->red 
588                                                                          green:element->green
589                                                                           blue:element->blue
590                                                                          alpha:element->alpha];
591   
592   [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
593   
594   return nil;
595   }
597 - (NSColor *)colorAtPosition:(float)position
598   {
599   float components[4];
600   
601   switch(blendingMode)
602         {
603         case CTLinearBlendingMode:
604                  linearEvaluation(&elementList, &position, components);                 break;
605         case CTChromaticBlendingMode:
606                  chromaticEvaluation(&elementList, &position, components);                      break;
607         case CTInverseChromaticBlendingMode:
608                  inverseChromaticEvaluation(&elementList, &position, components);       break;
609         }
610   
611   
612   return [NSColor colorWithCalibratedRed:components[0]
613                                                                    green:components[1]
614                                                                     blue:components[2]
615                                                                    alpha:components[3]];
616   }
617 #pragma mark -
621 #pragma mark Drawing
622 - (void)drawSwatchInRect:(NSRect)rect
623   {
624   [self fillRect:rect angle:45];
625   }
627 - (void)fillRect:(NSRect)rect angle:(float)angle
628   {
629   //First Calculate where the beginning and ending points should be
630   CGPoint startPoint;
631   CGPoint endPoint;
632   
633   if(angle == 0)                //screw the calculations - we know the answer
634         {
635         startPoint = CGPointMake(NSMinX(rect), NSMinY(rect));   //right of rect
636         endPoint   = CGPointMake(NSMaxX(rect), NSMinY(rect));   //left  of rect
637         }
638   else if(angle == 90)  //same as above
639         {
640         startPoint = CGPointMake(NSMinX(rect), NSMinY(rect));   //bottom of rect
641         endPoint   = CGPointMake(NSMinX(rect), NSMaxY(rect));   //top    of rect
642         }
643   else                                          //ok, we'll do the calculations now 
644         {
645         float x,y;
646         float sina, cosa, tana;
647         
648         float length;
649         float deltax,
650                   deltay;
651         
652         float rangle = angle * pi/180;  //convert the angle to radians
653         
654         if(fabsf(tan(rangle))<=1)       //for range [-45,45], [135,225]
655                 {
656                 x = NSWidth(rect);
657                 y = NSHeight(rect);
658                 
659                 sina = sin(rangle);
660                 cosa = cos(rangle);
661                 tana = tan(rangle);
662                 
663                 length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
664                 
665                 deltax = length*cosa/2;
666                 deltay = length*sina/2;
667                 }
668         else                                            //for range [45,135], [225,315]
669                 {
670                 x = NSHeight(rect);
671                 y = NSWidth(rect);
672                 
673                 sina = sin(rangle - 90*pi/180);
674                 cosa = cos(rangle - 90*pi/180);
675                 tana = tan(rangle - 90*pi/180);
676                 
677                 length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
678                 
679                 deltax =-length*sina/2;
680                 deltay = length*cosa/2;
681                 }
682   
683         startPoint = CGPointMake(NSMidX(rect)-deltax, NSMidY(rect)-deltay);
684         endPoint   = CGPointMake(NSMidX(rect)+deltax, NSMidY(rect)+deltay);
685         }
686   
687   //Calls to CoreGraphics
688   CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
689   CGContextSaveGState(currentContext);
690           #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
691                 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
692           #else
693                 CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
694           #endif
695           
696           CGShadingRef myCGShading = CGShadingCreateAxial(colorspace, startPoint, endPoint, gradientFunction, false, false);
697           
698           CGContextClipToRect(currentContext , *(CGRect *)&rect);       //This is where the action happens
699           CGContextDrawShading(currentContext, myCGShading);
700           
701           CGShadingRelease   (myCGShading);
702           CGColorSpaceRelease(colorspace );
703   CGContextRestoreGState(currentContext);
704   }
706 - (void)radialFillRect:(NSRect)rect
707   {
708   CGPoint startPoint , endPoint;
709   float startRadius, endRadius;
710   
711   startPoint = endPoint = CGPointMake(NSMidX(rect), NSMidY(rect));
712   
713   startRadius = 1;
714   
715   if(NSHeight(rect)>NSWidth(rect))
716         endRadius = NSHeight(rect)/2;
717   else
718         endRadius = NSWidth(rect)/2;
720   //Calls to CoreGraphics
721   CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
722   CGContextSaveGState(currentContext);
723           #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
724                 CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
725           #else
726                 CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
727           #endif
729           CGShadingRef myCGShading = CGShadingCreateRadial(colorspace, startPoint, startRadius, endPoint, endRadius, gradientFunction, true, true);
730           
731           CGContextClipToRect (currentContext , *(CGRect *)&rect);
732           CGContextDrawShading(currentContext , myCGShading);           //This is where the action happens
733           
734           CGShadingRelease    (myCGShading);
735           CGColorSpaceRelease (colorspace);
736   CGContextRestoreGState(currentContext);
737   }
739 #pragma mark -
743 #pragma mark Private Methods
744 - (void)setBlendingMode:(CTGradientBlendingMode)mode;
745   {
746   blendingMode = mode;
747   
748   //Choose what blending function to use
749   void *evaluationFunction = NULL;
750   switch(blendingMode)
751         {
752         case CTLinearBlendingMode:
753                  evaluationFunction = &linearEvaluation;                        break;
754         case CTChromaticBlendingMode:
755                  evaluationFunction = &chromaticEvaluation;                     break;
756         case CTInverseChromaticBlendingMode:
757                  evaluationFunction = &inverseChromaticEvaluation;      break;
758         }
759   
760   //replace the current CoreGraphics Function with new one
761   if(gradientFunction != NULL)
762           CGFunctionRelease(gradientFunction);
763     
764   CGFunctionCallbacks evaluationCallbackInfo = {0 , evaluationFunction, NULL};  //Version, evaluator function, cleanup function
765   
766   static const float input_value_range   [2] = { 0, 1 };                                                //range  for the evaluator input
767   static const float output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };              //ranges for the evaluator output (4 returned values)
768   
769   gradientFunction = CGFunctionCreate(&elementList,                                     //the two transition colors
770                                                                           1, input_value_range  ,               //number of inputs (just fraction of progression)
771                                                                           4, output_value_ranges,               //number of outputs (4 - RGBa)
772                                                                           &evaluationCallbackInfo);             //info for using the evaluator function
773   }
775 - (void)addElement:(CTGradientElement *)newElement
777   if(elementList == nil || newElement->position < elementList->position)        //inserting at beginning of list
778         {
779         CTGradientElement *tmpNext = elementList;
780         elementList = malloc(sizeof(CTGradientElement));
781         *elementList = *newElement;
782         elementList->nextElement = tmpNext;
783         }
784   else                                                                                                                                          //inserting somewhere inside list
785         {
786         CTGradientElement *curElement = elementList;
787         
788         while(curElement->nextElement != nil && !((curElement->position <= newElement->position) && (newElement->position < curElement->nextElement->position)))
789                 {
790                 curElement = curElement->nextElement;
791                 }
792         
793         CTGradientElement *tmpNext = curElement->nextElement;
794         curElement->nextElement = malloc(sizeof(CTGradientElement));
795         *(curElement->nextElement) = *newElement;
796         curElement->nextElement->nextElement = tmpNext;
797         }
798   }
800 - (CTGradientElement)removeElementAtIndex:(unsigned)index
801   {
802   CTGradientElement removedElement;
803   
804   if(elementList != nil)
805         {
806         if(index == 0)
807                 {
808                 CTGradientElement *tmpNext = elementList;
809                 elementList = elementList->nextElement;
810                 
811                 removedElement = *tmpNext;
812                 free(tmpNext);
813                 
814                 return removedElement;
815                 }
816         
817         unsigned count = 1;             //we want to start one ahead
818         CTGradientElement *currentElement = elementList;
819         while(currentElement->nextElement != nil)
820                 {
821                 if(count == index)
822                         {
823                         CTGradientElement *tmpNext  = currentElement->nextElement;
824                         currentElement->nextElement = currentElement->nextElement->nextElement;
825                         
826                         removedElement = *tmpNext;
827                         free(tmpNext);
829                         return removedElement;
830                         }
832                 count++;
833                 currentElement = currentElement->nextElement;
834                 }
835         }
836   
837   //element is not found, return empty element
838   removedElement.red   = 0.0;
839   removedElement.green = 0.0;
840   removedElement.blue  = 0.0;
841   removedElement.alpha = 0.0;
842   removedElement.position = NAN;
843   removedElement.nextElement = nil;
844   
845   return removedElement;
846   }
848 - (CTGradientElement)removeElementAtPosition:(float)position
849   {
850   CTGradientElement removedElement;
851   
852   if(elementList != nil)
853         {
854         if(elementList->position == position)
855                 {
856                 CTGradientElement *tmpNext = elementList;
857                 elementList = elementList->nextElement;
858                 
859                 removedElement = *tmpNext;
860                 free(tmpNext);
861                 
862                 return removedElement;
863                 }
864         else
865                 {
866                 CTGradientElement *curElement = elementList;
867                 while(curElement->nextElement != nil)
868                         {
869                         if(curElement->nextElement->position == position)
870                                 {
871                                 CTGradientElement *tmpNext = curElement->nextElement;
872                                 curElement->nextElement = curElement->nextElement->nextElement;
873                                 
874                                 removedElement = *tmpNext;
875                                 free(tmpNext);
877                                 return removedElement;
878                                 }
879                         }
880                 }
881         }
882   
883   //element is not found, return empty element
884   removedElement.red   = 0.0;
885   removedElement.green = 0.0;
886   removedElement.blue  = 0.0;
887   removedElement.alpha = 0.0;
888   removedElement.position = NAN;
889   removedElement.nextElement = nil;
890   
891   return removedElement;
892   }
895 - (CTGradientElement *)elementAtIndex:(unsigned)index;                  
896   {
897   unsigned count = 0;
898   CTGradientElement *currentElement = elementList;
899   
900   while(currentElement != nil)
901         {
902         if(count == index)
903                 return currentElement;
904         
905         count++;
906         currentElement = currentElement->nextElement;
907         }
908   
909   return nil;
910   }
911 #pragma mark -
915 #pragma mark Core Graphics
916 //////////////////////////////////////Blending Functions/////////////////////////////////////
917 void linearEvaluation (void *info, const float *in, float *out)
918   {
919   float position = *in;
920   
921   if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
922         {
923         out[0] = out[1] = out[2] = out[3] = 1;
924         return;
925         }
926   
927   //This grabs the first two colors in the sequence
928   CTGradientElement *color1 = *(CTGradientElement **)info;
929   CTGradientElement *color2 = color1->nextElement;
930   
931   //make sure first color and second color are on other sides of position
932   while(color2 != nil && color2->position < position)
933         {
934         color1 = color2;
935         color2 = color1->nextElement;
936         }
937   //if we don't have another color then make next color the same color
938   if(color2 == nil)
939     {
940         color2 = color1;
941     }
942   
943   //----------FailSafe settings----------
944   //color1->red   = 1; color2->red   = 0;
945   //color1->green = 1; color2->green = 0;
946   //color1->blue  = 1; color2->blue  = 0;
947   //color1->alpha = 1; color2->alpha = 1;
948   //color1->position = .5;
949   //color2->position = .5;
950   //-------------------------------------
951   
952   if(position <= color1->position)                      //Make all below color color1's position equal to color1
953         {
954         out[0] = color1->red; 
955         out[1] = color1->green;
956         out[2] = color1->blue;
957         out[3] = color1->alpha;
958         }
959   else if (position >= color2->position)        //Make all above color color2's position equal to color2
960         {
961         out[0] = color2->red; 
962         out[1] = color2->green;
963         out[2] = color2->blue;
964         out[3] = color2->alpha;
965         }
966   else                                                                          //Interpolate color at postions between color1 and color1
967         {
968         //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position 
969         position = (position-color1->position)/(color2->position - color1->position);
970         
971         out[0] = (color2->red   - color1->red  )*position + color1->red; 
972         out[1] = (color2->green - color1->green)*position + color1->green;
973         out[2] = (color2->blue  - color1->blue )*position + color1->blue;
974         out[3] = (color2->alpha - color1->alpha)*position + color1->alpha;
975         }
976   }
981 //Chromatic Evaluation - 
982 //      This blends colors by their Hue, Saturation, and Value(Brightness) right now I just 
983 //      transform the RGB values stored in the CTGradientElements to HSB, in the future I may
984 //      streamline it to avoid transforming in and out of HSB colorspace *for later*
986 //      For the chromatic blend we shift the hue of color1 to meet the hue of color2. To do
987 //      this we will add to the hue's angle (if we subtract we'll be doing the inverse
988 //      chromatic...scroll down more for that). All we need to do is keep adding to the hue
989 //  until we wrap around the colorwheel and get to color2.
990 void chromaticEvaluation(void *info, const float *in, float *out)
991   {
992   float position = *in;
993   
994   if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
995         {
996         out[0] = out[1] = out[2] = out[3] = 1;
997         return;
998         }
999   
1000   //This grabs the first two colors in the sequence
1001   CTGradientElement *color1 = *(CTGradientElement **)info;
1002   CTGradientElement *color2 = color1->nextElement;
1003   
1004   float c1[4];
1005   float c2[4];
1006     
1007   //make sure first color and second color are on other sides of position
1008   while(color2 != nil && color2->position < position)
1009         {
1010         color1 = color2;
1011         color2 = color1->nextElement;
1012         }
1013   //if we don't have another color then make next color the same color
1014   if(color2 == nil)
1015     {
1016         color2 = color1;
1017     }
1018   
1019   
1020   c1[0] = color1->red; 
1021   c1[1] = color1->green;
1022   c1[2] = color1->blue;
1023   c1[3] = color1->alpha;
1024   
1025   c2[0] = color2->red; 
1026   c2[1] = color2->green;
1027   c2[2] = color2->blue;
1028   c2[3] = color2->alpha;
1029   
1030   transformRGB_HSV(c1);
1031   transformRGB_HSV(c2);
1032   resolveHSV(c1,c2);
1033   
1034   if(c1[0] > c2[0]) //if color1's hue is higher than color2's hue then 
1035          c2[0] += 360;  //      we need to move c2 one revolution around the wheel
1036   
1037   
1038   if(position <= color1->position)                      //Make all below color color1's position equal to color1
1039         {
1040         out[0] = c1[0]; 
1041         out[1] = c1[1];
1042         out[2] = c1[2];
1043         out[3] = c1[3];
1044         }
1045   else if (position >= color2->position)        //Make all above color color2's position equal to color2
1046         {
1047         out[0] = c2[0]; 
1048         out[1] = c2[1];
1049         out[2] = c2[2];
1050         out[3] = c2[3];
1051         }
1052   else                                                                          //Interpolate color at postions between color1 and color1
1053         {
1054         //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position 
1055         position = (position-color1->position)/(color2->position - color1->position);
1056         
1057         out[0] = (c2[0] - c1[0])*position + c1[0]; 
1058         out[1] = (c2[1] - c1[1])*position + c1[1];
1059         out[2] = (c2[2] - c1[2])*position + c1[2];
1060         out[3] = (c2[3] - c1[3])*position + c1[3];
1061         }
1062     
1063   transformHSV_RGB(out);
1064   
1065   //if(position > -1 && out[0] == out[1] && out[1] == out[2]  && out[0]==0)
1066   //printf("%.4f: %.4f,%.4f,%.4f\n",position,out[0],out[1],out[2]);
1067   //printf("%.4f: %.4f,%.4f,%.4f\n",position,color1->red,color1->green,color1->blue);
1068   }
1072 //Inverse Chromatic Evaluation - 
1073 //      Inverse Chromatic is about the same story as Chromatic Blend, but here the Hue
1074 //      is strictly decreasing, that is we need to get from color1 to color2 by decreasing
1075 //      the 'angle' (i.e. 90º -> 180º would be done by subtracting 270º and getting -180º...
1076 //      which is equivalent to 180º mod 360º
1077 void inverseChromaticEvaluation(void *info, const float *in, float *out)
1078   {
1079     float position = *in;
1080   
1081   if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
1082         {
1083         out[0] = out[1] = out[2] = out[3] = 1;
1084         return;
1085         }
1086   
1087   //This grabs the first two colors in the sequence
1088   CTGradientElement *color1 = *(CTGradientElement **)info;
1089   CTGradientElement *color2 = color1->nextElement;
1090   
1091   float c1[4];
1092   float c2[4];
1093       
1094   //make sure first color and second color are on other sides of position
1095   while(color2 != nil && color2->position < position)
1096         {
1097         color1 = color2;
1098         color2 = color1->nextElement;
1099         }
1100   //if we don't have another color then make next color the same color
1101   if(color2 == nil)
1102     {
1103         color2 = color1;
1104     }
1106   c1[0] = color1->red; 
1107   c1[1] = color1->green;
1108   c1[2] = color1->blue;
1109   c1[3] = color1->alpha;
1110   
1111   c2[0] = color2->red; 
1112   c2[1] = color2->green;
1113   c2[2] = color2->blue;
1114   c2[3] = color2->alpha;
1116   transformRGB_HSV(c1);
1117   transformRGB_HSV(c2);
1118   resolveHSV(c1,c2);
1120   if(c1[0] < c2[0]) //if color1's hue is higher than color2's hue then 
1121          c1[0] += 360;  //      we need to move c2 one revolution back on the wheel
1123   
1124   if(position <= color1->position)                      //Make all below color color1's position equal to color1
1125         {
1126         out[0] = c1[0]; 
1127         out[1] = c1[1];
1128         out[2] = c1[2];
1129         out[3] = c1[3];
1130         }
1131   else if (position >= color2->position)        //Make all above color color2's position equal to color2
1132         {
1133         out[0] = c2[0]; 
1134         out[1] = c2[1];
1135         out[2] = c2[2];
1136         out[3] = c2[3];
1137         }
1138   else                                                                          //Interpolate color at postions between color1 and color1
1139         {
1140         //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position 
1141         position = (position-color1->position)/(color2->position - color1->position);
1142         
1143         out[0] = (c2[0] - c1[0])*position + c1[0]; 
1144         out[1] = (c2[1] - c1[1])*position + c1[1];
1145         out[2] = (c2[2] - c1[2])*position + c1[2];
1146         out[3] = (c2[3] - c1[3])*position + c1[3];
1147         }
1148     
1149   transformHSV_RGB(out);
1150   }
1161 void transformRGB_HSV(float *components) //H,S,B -> R,G,B
1162         {
1163         float H = 0.0, S = 0.0, V = 0.0;
1164         float R = components[0],
1165                   G = components[1],
1166                   B = components[2];
1167         
1168         float MAX = R > G ? (R > B ? R : B) : (G > B ? G : B),
1169               MIN = R < G ? (R < B ? R : B) : (G < B ? G : B);
1170         
1171         if(MAX == MIN)
1172                 H = NAN;
1173         else if(MAX == R)
1174                 if(G >= B)
1175                         H = 60*(G-B)/(MAX-MIN)+0;
1176                 else
1177                         H = 60*(G-B)/(MAX-MIN)+360;
1178         else if(MAX == G)
1179                 H = 60*(B-R)/(MAX-MIN)+120;
1180         else if(MAX == B)
1181                 H = 60*(R-G)/(MAX-MIN)+240;
1182         
1183         S = MAX == 0 ? 0 : 1 - MIN/MAX;
1184         V = MAX;
1185         
1186         components[0] = H;
1187         components[1] = S;
1188         components[2] = V;
1189         }
1191 void transformHSV_RGB(float *components) //H,S,B -> R,G,B
1192         {
1193         float R = 0.0, G = 0.0, B = 0.0;
1194         float H = fmodf(components[0],359),     //map to [0,360)
1195                   S = components[1],
1196                   V = components[2];
1197         
1198         int   Hi = (int)floorf(H/60.) % 6;
1199         float f  = H/60-Hi,
1200                   p  = V*(1-S),
1201                   q  = V*(1-f*S),
1202                   t  = V*(1-(1-f)*S);
1203         
1204         switch (Hi)
1205                 {
1206                 case 0: R=V;G=t;B=p;    break;
1207                 case 1: R=q;G=V;B=p;    break;
1208                 case 2: R=p;G=V;B=t;    break;
1209                 case 3: R=p;G=q;B=V;    break;
1210                 case 4: R=t;G=p;B=V;    break;
1211                 case 5: R=V;G=p;B=q;    break;
1212                 }
1213         
1214         components[0] = R;
1215         components[1] = G;
1216         components[2] = B;
1217         }
1219 void resolveHSV(float *color1, float *color2)   //H value may be undefined (i.e. graycale color)
1220         {                                                                                       //      we want to fill it with a sensible value
1221         if(isnan(color1[0]) && isnan(color2[0]))
1222                 color1[0] = color2[0] = 0;
1223         else if(isnan(color1[0]))
1224                 color1[0] = color2[0];
1225         else if(isnan(color2[0]))
1226                 color2[0] = color1[0];
1227         }
1229 @end