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

Last change on this file since 5785 was 5785, checked in by livings124, 14 years ago

update CTGradient to 1.8

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