source: trunk/Cocoa/AntiRSI/CTBadge/CTGradient.m@ 447

Last change on this file since 447 was 370, checked in by Nicholas Riley, 17 years ago

Apparently in Xcode, 'Commit Entire Project' doesn't. See the log message from [369].

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