source: trunk/macosx/CTGradient/CTGradient.m @ 7059

Last change on this file since 7059 was 7059, checked in by livings124, 12 years ago

update release notes; trivial changes

File size: 25.1 KB
Line 
1//
2//  CTGradient.m
3//
4//  Created by Chad Weider on 2/14/07.
5//  Copyright (c) 2007 Chad Weider.
6//  Some rights reserved: <http://creativecommons.org/licenses/by/2.5/>
7//
8//  Version: 1.6
9
10#import "CTGradient.h"
11
12@interface CTGradient (Private)
13- (void)_commonInit;
14- (void)setBlendingMode:(CTGradientBlendingMode)mode;
15- (void)addElement:(CTGradientElement*)newElement;
16
17- (CTGradientElement *)elementAtIndex:(unsigned)index;
18
19- (CTGradientElement)removeElementAtIndex:(unsigned)index;
20- (CTGradientElement)removeElementAtPosition:(float)position;
21@end
22
23//C Fuctions for color blending
24static void linearEvaluation   (void *info, const float *in, float *out);
25static void chromaticEvaluation(void *info, const float *in, float *out);
26static void inverseChromaticEvaluation(void *info, const float *in, float *out);
27static void transformRGB_HSV(float *components);
28static void transformHSV_RGB(float *components);
29static void resolveHSV(float *color1, float *color2);
30
31
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  }
45
46- (void)_commonInit
47  {
48  elementList = nil;
49  }
50
51- (void)dealloc
52  {
53  CGFunctionRelease(gradientFunction);
54 
55  while(elementList != nil)
56        {
57        CTGradientElement *elementToRemove = elementList;
58        elementList = elementList->nextElement;
59        free(elementToRemove);
60        }
61 
62  [super dealloc];
63  }
64
65- (id)copyWithZone:(NSZone *)zone
66  {
67  CTGradient *copy = [[[self class] allocWithZone:zone] init];
68 
69  //now just copy my elementlist
70  CTGradientElement *currentElement = elementList;
71  while(currentElement != nil)
72        {
73        [copy addElement:currentElement];
74        currentElement = currentElement->nextElement;
75        }
76 
77  [copy setBlendingMode:blendingMode];
78 
79  return copy;
80  }
81
82- (void)encodeWithCoder:(NSCoder *)coder
83  {
84  if([coder allowsKeyedCoding])
85        {
86        unsigned count = 0;
87        CTGradientElement *currentElement = elementList;
88        while(currentElement != nil)
89                {
90                [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->red)];
91                [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->green)];
92                [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->blue)];
93                [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->alpha)];
94                [coder encodeValueOfObjCType:@encode(float) at:&(currentElement->position)];
95               
96                count++;
97                currentElement = currentElement->nextElement;
98                }
99        [coder encodeInt:count forKey:@"CTGradientElementCount"];
100        [coder encodeInt:blendingMode forKey:@"CTGradientBlendingMode"];
101        }
102  else
103        [NSException raise:NSInvalidArchiveOperationException format:@"Only supports NSKeyedArchiver coders"];
104  }
105
106- (id)initWithCoder:(NSCoder *)coder
107  {
108  [self _commonInit];
109 
110  [self setBlendingMode:[coder decodeIntForKey:@"CTGradientBlendingMode"]];
111  unsigned count = [coder decodeIntForKey:@"CTGradientElementCount"];
112 
113  while(count != 0)
114        {
115    CTGradientElement newElement;
116       
117        [coder decodeValueOfObjCType:@encode(float) at:&(newElement.red)];
118        [coder decodeValueOfObjCType:@encode(float) at:&(newElement.green)];
119        [coder decodeValueOfObjCType:@encode(float) at:&(newElement.blue)];
120        [coder decodeValueOfObjCType:@encode(float) at:&(newElement.alpha)];
121        [coder decodeValueOfObjCType:@encode(float) at:&(newElement.position)];
122       
123        count--;
124        [self addElement:&newElement];
125        }
126  return self;
127  }
128#pragma mark -
129
130
131
132#pragma mark Creation
133+ (id)gradientWithBeginningColor:(NSColor *)begin endingColor:(NSColor *)end
134  {
135  id newInstance = [[[self class] alloc] init];
136 
137  CTGradientElement color1;
138  CTGradientElement color2;
139 
140  [[begin colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color1.red
141                                                                                                                           green:&color1.green
142                                                                                                                                blue:&color1.blue
143                                                                                                                           alpha:&color1.alpha];
144 
145  [[end   colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&color2.red
146                                                                                                                           green:&color2.green
147                                                                                                                                blue:&color2.blue
148                                                                                                                           alpha:&color2.alpha]; 
149  color1.position = 0;
150  color2.position = 1;
151 
152  [newInstance addElement:&color1];
153  [newInstance addElement:&color2];
154 
155  return [newInstance autorelease];
156  }
157#pragma mark -
158
159
160#pragma mark Modification
161- (CTGradient *)gradientWithAlphaComponent:(float)alpha
162  {
163  id newInstance = [[[self class] alloc] init];
164 
165  CTGradientElement *curElement = elementList;
166  CTGradientElement tempElement;
167
168  while(curElement != nil)
169        {
170        tempElement = *curElement;
171        tempElement.alpha = alpha;
172        [newInstance addElement:&tempElement];
173       
174        curElement = curElement->nextElement;
175        }
176 
177  return [newInstance autorelease];
178  }
179
180- (CTGradient *)gradientWithBlendingMode:(CTGradientBlendingMode)mode
181  {
182  CTGradient *newGradient = [self copy]; 
183 
184  [newGradient setBlendingMode:mode];
185 
186  return [newGradient autorelease];
187  }
188
189
190//Adds a color stop with <color> at <position> in elementList
191//(if two elements are at the same position then added imediatly after the one that was there already)
192- (CTGradient *)addColorStop:(NSColor *)color atPosition:(float)position
193  {
194  CTGradient *newGradient = [self copy];
195  CTGradientElement newGradientElement;
196 
197  //put the components of color into the newGradientElement - must make sure it is a RGB color (not Gray or CMYK)
198  [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&newGradientElement.red
199                                                                                                                           green:&newGradientElement.green
200                                                                                                                                blue:&newGradientElement.blue
201                                                                                                                           alpha:&newGradientElement.alpha];
202  newGradientElement.position = position;
203 
204  //Pass it off to addElement to take care of adding it to the elementList
205  [newGradient addElement:&newGradientElement];
206 
207  return [newGradient autorelease];
208  }
209
210
211//Removes the color stop at <position> from elementList
212- (CTGradient *)removeColorStopAtPosition:(float)position
213  {
214  CTGradient *newGradient = [self copy];
215  CTGradientElement removedElement = [newGradient removeElementAtPosition:position];
216 
217  if(isnan(removedElement.position))
218        [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtPosition:]: no such colorStop at position (%f)", [self class], position];
219 
220  return [newGradient autorelease];
221  }
222
223- (CTGradient *)removeColorStopAtIndex:(unsigned)index
224  {
225  CTGradient *newGradient = [self copy];
226  CTGradientElement removedElement = [newGradient removeElementAtIndex:index];
227 
228  if(isnan(removedElement.position))
229        [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
230 
231  return [newGradient autorelease];
232  }
233#pragma mark -
234
235
236
237#pragma mark Information
238- (CTGradientBlendingMode)blendingMode
239  {
240  return blendingMode;
241  }
242
243//Returns color at <position> in gradient
244- (NSColor *)colorStopAtIndex:(unsigned)index
245  {
246  CTGradientElement *element = [self elementAtIndex:index];
247 
248  if(element != nil)
249        return [NSColor colorWithCalibratedRed:element->red
250                                                                         green:element->green
251                                                                          blue:element->blue
252                                                                         alpha:element->alpha];
253 
254  [NSException raise:NSRangeException format:@"-[%@ removeColorStopAtIndex:]: index (%i) beyond bounds", [self class], index];
255 
256  return nil;
257  }
258
259- (NSColor *)colorAtPosition:(float)position
260  {
261  float components[4];
262 
263  switch(blendingMode)
264        {
265        case CTLinearBlendingMode:
266                 linearEvaluation(&elementList, &position, components);                         break;
267        case CTChromaticBlendingMode:
268                 chromaticEvaluation(&elementList, &position, components);                      break;
269        case CTInverseChromaticBlendingMode:
270                 inverseChromaticEvaluation(&elementList, &position, components);       break;
271        }
272 
273 
274  return [NSColor colorWithCalibratedRed:components[0]
275                                                                   green:components[1]
276                                                                    blue:components[2]
277                                                                   alpha:components[3]];
278  }
279#pragma mark -
280
281
282
283#pragma mark Drawing
284- (void)drawSwatchInRect:(NSRect)rect
285  {
286  [self fillRect:rect angle:45];
287  }
288
289- (void)fillRect:(NSRect)rect angle:(float)angle
290  {
291  //First Calculate where the beginning and ending points should be
292  CGPoint startPoint;
293  CGPoint endPoint;
294 
295  if(angle == 0)                //screw the calculations - we know the answer
296        {
297        startPoint = CGPointMake(NSMinX(rect), NSMinY(rect));   //right of rect
298        endPoint   = CGPointMake(NSMaxX(rect), NSMinY(rect));   //left  of rect
299        }
300  else if(angle == 90)  //same as above
301        {
302        startPoint = CGPointMake(NSMinX(rect), NSMinY(rect));   //bottom of rect
303        endPoint   = CGPointMake(NSMinX(rect), NSMaxY(rect));   //top    of rect
304        }
305  else                                          //ok, we'll do the calculations now
306        {
307        float x,y;
308        float sina, cosa, tana;
309       
310        float length;
311        float deltax,
312                  deltay;
313       
314        float rangle = angle * pi/180;  //convert the angle to radians
315       
316        if(fabsf(tan(rangle))<=1)       //for range [-45,45], [135,225]
317                {
318                x = NSWidth(rect);
319                y = NSHeight(rect);
320               
321                sina = sin(rangle);
322                cosa = cos(rangle);
323                tana = tan(rangle);
324               
325                length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
326               
327                deltax = length*cosa/2;
328                deltay = length*sina/2;
329                }
330        else                                            //for range [45,135], [225,315]
331                {
332                x = NSHeight(rect);
333                y = NSWidth(rect);
334               
335                sina = sin(rangle - 90*pi/180);
336                cosa = cos(rangle - 90*pi/180);
337                tana = tan(rangle - 90*pi/180);
338               
339                length = x/fabsf(cosa)+(y-x*fabsf(tana))*fabsf(sina);
340               
341                deltax =-length*sina/2;
342                deltay = length*cosa/2;
343                }
344 
345        startPoint = CGPointMake(NSMidX(rect)-deltax, NSMidY(rect)-deltay);
346        endPoint   = CGPointMake(NSMidX(rect)+deltax, NSMidY(rect)+deltay);
347        }
348 
349  //Calls to CoreGraphics
350  CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
351  CGContextSaveGState(currentContext);
352          #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
353                CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
354          #else
355                CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
356          #endif
357          CGShadingRef myCGShading = CGShadingCreateAxial(colorspace, startPoint, endPoint, gradientFunction, false, false);
358         
359          CGContextClipToRect (currentContext, *(CGRect *)&rect);       //This is where the action happens
360          CGContextDrawShading(currentContext, myCGShading);
361         
362          CGShadingRelease(myCGShading);
363          CGColorSpaceRelease(colorspace );
364  CGContextRestoreGState(currentContext);
365  }
366
367- (void)radialFillRect:(NSRect)rect
368  {
369  CGPoint startPoint , endPoint;
370  float startRadius, endRadius;
371  float scalex, scaley, transx, transy;
372 
373  startPoint = endPoint = CGPointMake(NSMidX(rect), NSMidY(rect));
374 
375  startRadius = -1;
376  if(NSHeight(rect)>NSWidth(rect))
377        {
378        scalex = NSWidth(rect)/NSHeight(rect);
379        transx = (NSHeight(rect)-NSWidth(rect))/2;
380        scaley = 1;
381        transy = 1;
382        endRadius = NSHeight(rect)/2;
383        }
384  else
385        {
386        scalex = 1;
387        transx = 1;
388        scaley = NSHeight(rect)/NSWidth(rect);
389        transy = (NSWidth(rect)-NSHeight(rect))/2;
390        endRadius = NSWidth(rect)/2;
391        }
392 
393  //Calls to CoreGraphics
394  CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
395  CGContextSaveGState(currentContext);
396          #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
397                CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
398          #else
399                CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
400          #endif
401          CGShadingRef myCGShading = CGShadingCreateRadial(colorspace, startPoint, startRadius, endPoint, endRadius, gradientFunction, true, true);
402
403          CGContextClipToRect  (currentContext, *(CGRect *)&rect);
404          CGContextScaleCTM    (currentContext, scalex, scaley);
405          CGContextTranslateCTM(currentContext, transx, transy);
406          CGContextDrawShading (currentContext, myCGShading);           //This is where the action happens
407         
408          CGShadingRelease(myCGShading);
409          CGColorSpaceRelease(colorspace);
410  CGContextRestoreGState(currentContext);
411  }
412
413- (void)fillBezierPath:(NSBezierPath *)path angle:(float)angle
414  {
415  NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
416  [currentContext saveGraphicsState];
417        NSAffineTransform *transform = [[NSAffineTransform alloc] init];
418       
419        [transform rotateByDegrees:-angle];
420        [path transformUsingAffineTransform:transform];
421        [transform invert];
422        [transform concat];
423       
424        [path addClip];
425        [self fillRect:[path bounds] angle:0];
426        [path transformUsingAffineTransform:transform];
427        [transform release];
428  [currentContext restoreGraphicsState];
429  }
430- (void)radialFillBezierPath:(NSBezierPath *)path
431  {
432  NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
433  [currentContext saveGraphicsState];
434        [path addClip];
435        [self radialFillRect:[path bounds]];
436  [currentContext restoreGraphicsState];
437  }
438#pragma mark -
439
440
441
442#pragma mark Private Methods
443- (void)setBlendingMode:(CTGradientBlendingMode)mode;
444  {
445  blendingMode = mode;
446 
447  //Choose what blending function to use
448  void *evaluationFunction;
449  switch(blendingMode)
450        {
451        case CTLinearBlendingMode:
452                 evaluationFunction = &linearEvaluation;                        break;
453        case CTChromaticBlendingMode:
454                 evaluationFunction = &chromaticEvaluation;                     break;
455        case CTInverseChromaticBlendingMode:
456                 evaluationFunction = &inverseChromaticEvaluation;      break;
457        }
458 
459  //replace the current CoreGraphics Function with new one
460  if(gradientFunction != NULL)
461          CGFunctionRelease(gradientFunction);
462   
463  CGFunctionCallbacks evaluationCallbackInfo = {0 , evaluationFunction, NULL};  //Version, evaluator function, cleanup function
464 
465  static const float input_value_range   [2] = { 0, 1 };                                                //range  for the evaluator input
466  static const float output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };              //ranges for the evaluator output (4 returned values)
467 
468  gradientFunction = CGFunctionCreate(&elementList,                                     //the two transition colors
469                                                                          1, input_value_range  ,               //number of inputs (just fraction of progression)
470                                                                          4, output_value_ranges,               //number of outputs (4 - RGBa)
471                                                                          &evaluationCallbackInfo);             //info for using the evaluator function
472  }
473
474- (void)addElement:(CTGradientElement *)newElement
475{
476  if(elementList == nil || newElement->position < elementList->position)        //inserting at beginning of list
477        {
478        CTGradientElement *tmpNext = elementList;
479        elementList = malloc(sizeof(CTGradientElement));
480        *elementList = *newElement;
481        elementList->nextElement = tmpNext;
482        }
483  else                                                                                                                                          //inserting somewhere inside list
484        {
485        CTGradientElement *curElement = elementList;
486       
487        while(curElement->nextElement != nil && !((curElement->position <= newElement->position) && (newElement->position < curElement->nextElement->position)))
488                {
489                curElement = curElement->nextElement;
490                }
491       
492        CTGradientElement *tmpNext = curElement->nextElement;
493        curElement->nextElement = malloc(sizeof(CTGradientElement));
494        *(curElement->nextElement) = *newElement;
495        curElement->nextElement->nextElement = tmpNext;
496        }
497  }
498
499- (CTGradientElement)removeElementAtIndex:(unsigned)index
500  {
501  CTGradientElement removedElement;
502 
503  if(elementList != nil)
504        {
505        if(index == 0)
506                {
507                CTGradientElement *tmpNext = elementList;
508                elementList = elementList->nextElement;
509               
510                removedElement = *tmpNext;
511                free(tmpNext);
512               
513                return removedElement;
514                }
515       
516        unsigned count = 1;             //we want to start one ahead
517        CTGradientElement *currentElement = elementList;
518        while(currentElement->nextElement != nil)
519                {
520                if(count == index)
521                        {
522                        CTGradientElement *tmpNext  = currentElement->nextElement;
523                        currentElement->nextElement = currentElement->nextElement->nextElement;
524                       
525                        removedElement = *tmpNext;
526                        free(tmpNext);
527
528                        return removedElement;
529                        }
530
531                count++;
532                currentElement = currentElement->nextElement;
533                }
534        }
535 
536  //element is not found, return empty element
537  removedElement.red   = 0.0;
538  removedElement.green = 0.0;
539  removedElement.blue  = 0.0;
540  removedElement.alpha = 0.0;
541  removedElement.position = NAN;
542  removedElement.nextElement = nil;
543 
544  return removedElement;
545  }
546
547- (CTGradientElement)removeElementAtPosition:(float)position
548  {
549  CTGradientElement removedElement;
550 
551  if(elementList != nil)
552        {
553        if(elementList->position == position)
554                {
555                CTGradientElement *tmpNext = elementList;
556                elementList = elementList->nextElement;
557               
558                removedElement = *tmpNext;
559                free(tmpNext);
560               
561                return removedElement;
562                }
563        else
564                {
565                CTGradientElement *curElement = elementList;
566                while(curElement->nextElement != nil)
567                        {
568                        if(curElement->nextElement->position == position)
569                                {
570                                CTGradientElement *tmpNext = curElement->nextElement;
571                                curElement->nextElement = curElement->nextElement->nextElement;
572                               
573                                removedElement = *tmpNext;
574                                free(tmpNext);
575
576                                return removedElement;
577                                }
578                        }
579                }
580        }
581 
582  //element is not found, return empty element
583  removedElement.red   = 0.0;
584  removedElement.green = 0.0;
585  removedElement.blue  = 0.0;
586  removedElement.alpha = 0.0;
587  removedElement.position = NAN;
588  removedElement.nextElement = nil;
589 
590  return removedElement;
591  }
592
593
594- (CTGradientElement *)elementAtIndex:(unsigned)index;                 
595  {
596  unsigned count = 0;
597  CTGradientElement *currentElement = elementList;
598 
599  while(currentElement != nil)
600        {
601        if(count == index)
602                return currentElement;
603       
604        count++;
605        currentElement = currentElement->nextElement;
606        }
607 
608  return nil;
609  }
610#pragma mark -
611
612
613
614#pragma mark Core Graphics
615//////////////////////////////////////Blending Functions/////////////////////////////////////
616void linearEvaluation (void *info, const float *in, float *out)
617  {
618  float position = *in;
619 
620  if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
621        {
622        out[0] = out[1] = out[2] = out[3] = 1;
623        return;
624        }
625 
626  //This grabs the first two colors in the sequence
627  CTGradientElement *color1 = *(CTGradientElement **)info;
628  CTGradientElement *color2 = color1->nextElement;
629 
630  //make sure first color and second color are on other sides of position
631  while(color2 != nil && color2->position < position)
632        {
633        color1 = color2;
634        color2 = color1->nextElement;
635        }
636  //if we don't have another color then make next color the same color
637  if(color2 == nil)
638    {
639        color2 = color1;
640    }
641 
642  //----------FailSafe settings----------
643  //color1->red   = 1; color2->red   = 0;
644  //color1->green = 1; color2->green = 0;
645  //color1->blue  = 1; color2->blue  = 0;
646  //color1->alpha = 1; color2->alpha = 1;
647  //color1->position = .5;
648  //color2->position = .5;
649  //-------------------------------------
650 
651  if(position <= color1->position)                      //Make all below color color1's position equal to color1
652        {
653        out[0] = color1->red;
654        out[1] = color1->green;
655        out[2] = color1->blue;
656        out[3] = color1->alpha;
657        }
658  else if (position >= color2->position)        //Make all above color color2's position equal to color2
659        {
660        out[0] = color2->red;
661        out[1] = color2->green;
662        out[2] = color2->blue;
663        out[3] = color2->alpha;
664        }
665  else                                                                          //Interpolate color at postions between color1 and color1
666        {
667        //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
668        position = (position-color1->position)/(color2->position - color1->position);
669       
670        out[0] = (color2->red   - color1->red  )*position + color1->red;
671        out[1] = (color2->green - color1->green)*position + color1->green;
672        out[2] = (color2->blue  - color1->blue )*position + color1->blue;
673        out[3] = (color2->alpha - color1->alpha)*position + color1->alpha;
674        }
675  }
676
677
678
679
680//Chromatic Evaluation -
681//      This blends colors by their Hue, Saturation, and Value(Brightness) right now I just
682//      transform the RGB values stored in the CTGradientElements to HSB, in the future I may
683//      streamline it to avoid transforming in and out of HSB colorspace *for later*
684//
685//      For the chromatic blend we shift the hue of color1 to meet the hue of color2. To do
686//      this we will add to the hue's angle (if we subtract we'll be doing the inverse
687//      chromatic...scroll down more for that). All we need to do is keep adding to the hue
688//  until we wrap around the colorwheel and get to color2.
689void chromaticEvaluation(void *info, const float *in, float *out)
690  {
691  float position = *in;
692 
693  if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
694        {
695        out[0] = out[1] = out[2] = out[3] = 1;
696        return;
697        }
698 
699  //This grabs the first two colors in the sequence
700  CTGradientElement *color1 = *(CTGradientElement **)info;
701  CTGradientElement *color2 = color1->nextElement;
702 
703  float c1[4];
704  float c2[4];
705   
706  //make sure first color and second color are on other sides of position
707  while(color2 != nil && color2->position < position)
708        {
709        color1 = color2;
710        color2 = color1->nextElement;
711        }
712  //if we don't have another color then make next color the same color
713  if(color2 == nil)
714    {
715        color2 = color1;
716    }
717 
718 
719  c1[0] = color1->red;
720  c1[1] = color1->green;
721  c1[2] = color1->blue;
722  c1[3] = color1->alpha;
723 
724  c2[0] = color2->red;
725  c2[1] = color2->green;
726  c2[2] = color2->blue;
727  c2[3] = color2->alpha;
728 
729  transformRGB_HSV(c1);
730  transformRGB_HSV(c2);
731  resolveHSV(c1,c2);
732 
733  if(c1[0] > c2[0]) //if color1's hue is higher than color2's hue then
734         c2[0] += 360;  //      we need to move c2 one revolution around the wheel
735 
736 
737  if(position <= color1->position)                      //Make all below color color1's position equal to color1
738        {
739        out[0] = c1[0];
740        out[1] = c1[1];
741        out[2] = c1[2];
742        out[3] = c1[3];
743        }
744  else if (position >= color2->position)        //Make all above color color2's position equal to color2
745        {
746        out[0] = c2[0];
747        out[1] = c2[1];
748        out[2] = c2[2];
749        out[3] = c2[3];
750        }
751  else                                                                          //Interpolate color at postions between color1 and color1
752        {
753        //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
754        position = (position-color1->position)/(color2->position - color1->position);
755       
756        out[0] = (c2[0] - c1[0])*position + c1[0];
757        out[1] = (c2[1] - c1[1])*position + c1[1];
758        out[2] = (c2[2] - c1[2])*position + c1[2];
759        out[3] = (c2[3] - c1[3])*position + c1[3];
760        }
761   
762  transformHSV_RGB(out);
763  }
764
765
766
767//Inverse Chromatic Evaluation -
768//      Inverse Chromatic is about the same story as Chromatic Blend, but here the Hue
769//      is strictly decreasing, that is we need to get from color1 to color2 by decreasing
770//      the 'angle' (i.e. 90Œ -> 180Œ would be done by subtracting 270Œ and getting -180Œ...
771//      which is equivalent to 180Œ mod 360Œ
772void inverseChromaticEvaluation(void *info, const float *in, float *out)
773  {
774    float position = *in;
775 
776  if(*(CTGradientElement **)info == nil)        //if elementList is empty return clear color
777        {
778        out[0] = out[1] = out[2] = out[3] = 1;
779        return;
780        }
781 
782  //This grabs the first two colors in the sequence
783  CTGradientElement *color1 = *(CTGradientElement **)info;
784  CTGradientElement *color2 = color1->nextElement;
785 
786  float c1[4];
787  float c2[4];
788     
789  //make sure first color and second color are on other sides of position
790  while(color2 != nil && color2->position < position)
791        {
792        color1 = color2;
793        color2 = color1->nextElement;
794        }
795  //if we don't have another color then make next color the same color
796  if(color2 == nil)
797    {
798        color2 = color1;
799    }
800
801  c1[0] = color1->red;
802  c1[1] = color1->green;
803  c1[2] = color1->blue;
804  c1[3] = color1->alpha;
805 
806  c2[0] = color2->red;
807  c2[1] = color2->green;
808  c2[2] = color2->blue;
809  c2[3] = color2->alpha;
810
811  transformRGB_HSV(c1);
812  transformRGB_HSV(c2);
813  resolveHSV(c1,c2);
814
815  if(c1[0] < c2[0]) //if color1's hue is higher than color2's hue then
816         c1[0] += 360;  //      we need to move c2 one revolution back on the wheel
817
818 
819  if(position <= color1->position)                      //Make all below color color1's position equal to color1
820        {
821        out[0] = c1[0];
822        out[1] = c1[1];
823        out[2] = c1[2];
824        out[3] = c1[3];
825        }
826  else if (position >= color2->position)        //Make all above color color2's position equal to color2
827        {
828        out[0] = c2[0];
829        out[1] = c2[1];
830        out[2] = c2[2];
831        out[3] = c2[3];
832        }
833  else                                                                          //Interpolate color at postions between color1 and color1
834        {
835        //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
836        position = (position-color1->position)/(color2->position - color1->position);
837       
838        out[0] = (c2[0] - c1[0])*position + c1[0];
839        out[1] = (c2[1] - c1[1])*position + c1[1];
840        out[2] = (c2[2] - c1[2])*position + c1[2];
841        out[3] = (c2[3] - c1[3])*position + c1[3];
842        }
843   
844  transformHSV_RGB(out);
845  }
846
847
848
849void transformRGB_HSV(float *components) //H,S,B -> R,G,B
850        {
851        float H, S, V;
852        float R = components[0],
853                  G = components[1],
854                  B = components[2];
855       
856        float MAX = R > G ? (R > B ? R : B) : (G > B ? G : B),
857              MIN = R < G ? (R < B ? R : B) : (G < B ? G : B);
858       
859        if(MAX == MIN)
860                H = NAN;
861        else if(MAX == R)
862                if(G >= B)
863                        H = 60*(G-B)/(MAX-MIN)+0;
864                else
865                        H = 60*(G-B)/(MAX-MIN)+360;
866        else if(MAX == G)
867                H = 60*(B-R)/(MAX-MIN)+120;
868        else if(MAX == B)
869                H = 60*(R-G)/(MAX-MIN)+240;
870       
871        S = MAX == 0 ? 0 : 1 - MIN/MAX;
872        V = MAX;
873       
874        components[0] = H;
875        components[1] = S;
876        components[2] = V;
877        }
878
879void transformHSV_RGB(float *components) //H,S,B -> R,G,B
880        {
881        float R, G, B;
882        float H = fmodf(components[0],359),     //map to [0,360)
883                  S = components[1],
884                  V = components[2];
885       
886        int   Hi = (int)floorf(H/60.) % 6;
887        float f  = H/60-Hi,
888                  p  = V*(1-S),
889                  q  = V*(1-f*S),
890                  t  = V*(1-(1-f)*S);
891       
892        switch (Hi)
893                {
894                case 0: R=V;G=t;B=p;    break;
895                case 1: R=q;G=V;B=p;    break;
896                case 2: R=p;G=V;B=t;    break;
897                case 3: R=p;G=q;B=V;    break;
898                case 4: R=t;G=p;B=V;    break;
899                case 5: R=V;G=p;B=q;    break;
900                }
901       
902        components[0] = R;
903        components[1] = G;
904        components[2] = B;
905        }
906
907void resolveHSV(float *color1, float *color2)   //H value may be undefined (i.e. graycale color)
908        {                                                                                       //      we want to fill it with a sensible value
909        if(isnan(color1[0]) && isnan(color2[0]))
910                color1[0] = color2[0] = 0;
911        else if(isnan(color1[0]))
912                color1[0] = color2[0];
913        else if(isnan(color2[0]))
914                color2[0] = color1[0];
915        }
916
917@end
Note: See TracBrowser for help on using the repository browser.