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

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

version.plist: Not needed.

Info.plist: Updated for 1.4njr2.

AUTHORS: Updated Onne Gorter's email address and added mine.

AntiRSI.[hm]: Added dock badge session timer display and auto-reset options. Updated URLs to point to my Web site; should probably just switch to Sparkle.

CTBadge: Added customized version.

English.lproj/InfoPlist.strings: Updated for 1.4njr2; credit myself and Chad; remove Onne Gorter's URL.

English.lproj/MainMenu.nib: Preference changes; add cmd-R for manual reset.

File size: 32.5 KB
RevLine 
[332]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 blendingMode = mode;
782
783 //Choose what blending function to use
784 void *evaluationFunction;
785 switch(blendingMode)
786 {
787 case CTLinearBlendingMode:
788 evaluationFunction = &linearEvaluation; break;
789 case CTChromaticBlendingMode:
790 evaluationFunction = &chromaticEvaluation; break;
791 case CTInverseChromaticBlendingMode:
792 evaluationFunction = &inverseChromaticEvaluation; break;
793 }
794
795 //replace the current CoreGraphics Function with new one
796 if(gradientFunction != NULL)
797 CGFunctionRelease(gradientFunction);
798
799 CGFunctionCallbacks evaluationCallbackInfo = {0 , evaluationFunction, NULL}; //Version, evaluator function, cleanup function
800
801 static const float input_value_range [2] = { 0, 1 }; //range for the evaluator input
802 static const float output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 }; //ranges for the evaluator output (4 returned values)
803
804 gradientFunction = CGFunctionCreate(&elementList, //the two transition colors
805 1, input_value_range , //number of inputs (just fraction of progression)
806 4, output_value_ranges, //number of outputs (4 - RGBa)
807 &evaluationCallbackInfo); //info for using the evaluator function
808 }
809
810- (void)addElement:(CTGradientElement *)newElement
811{
812 if(elementList == nil || newElement->position < elementList->position) //inserting at beginning of list
813 {
814 CTGradientElement *tmpNext = elementList;
815 elementList = malloc(sizeof(CTGradientElement));
816 *elementList = *newElement;
817 elementList->nextElement = tmpNext;
818 }
819 else //inserting somewhere inside list
820 {
821 CTGradientElement *curElement = elementList;
822
823 while(curElement->nextElement != nil && !((curElement->position <= newElement->position) && (newElement->position < curElement->nextElement->position)))
824 {
825 curElement = curElement->nextElement;
826 }
827
828 CTGradientElement *tmpNext = curElement->nextElement;
829 curElement->nextElement = malloc(sizeof(CTGradientElement));
830 *(curElement->nextElement) = *newElement;
831 curElement->nextElement->nextElement = tmpNext;
832 }
833 }
834
835- (CTGradientElement)removeElementAtIndex:(unsigned)index
836 {
837 CTGradientElement removedElement;
838
839 if(elementList != nil)
840 {
841 if(index == 0)
842 {
843 CTGradientElement *tmpNext = elementList;
844 elementList = elementList->nextElement;
845
846 removedElement = *tmpNext;
847 free(tmpNext);
848
849 return removedElement;
850 }
851
852 unsigned count = 1; //we want to start one ahead
853 CTGradientElement *currentElement = elementList;
854 while(currentElement->nextElement != nil)
855 {
856 if(count == index)
857 {
858 CTGradientElement *tmpNext = currentElement->nextElement;
859 currentElement->nextElement = currentElement->nextElement->nextElement;
860
861 removedElement = *tmpNext;
862 free(tmpNext);
863
864 return removedElement;
865 }
866
867 count++;
868 currentElement = currentElement->nextElement;
869 }
870 }
871
872 //element is not found, return empty element
873 removedElement.red = 0.0;
874 removedElement.green = 0.0;
875 removedElement.blue = 0.0;
876 removedElement.alpha = 0.0;
877 removedElement.position = NAN;
878 removedElement.nextElement = nil;
879
880 return removedElement;
881 }
882
883- (CTGradientElement)removeElementAtPosition:(float)position
884 {
885 CTGradientElement removedElement;
886
887 if(elementList != nil)
888 {
889 if(elementList->position == position)
890 {
891 CTGradientElement *tmpNext = elementList;
892 elementList = elementList->nextElement;
893
894 removedElement = *tmpNext;
895 free(tmpNext);
896
897 return removedElement;
898 }
899 else
900 {
901 CTGradientElement *curElement = elementList;
902 while(curElement->nextElement != nil)
903 {
904 if(curElement->nextElement->position == position)
905 {
906 CTGradientElement *tmpNext = curElement->nextElement;
907 curElement->nextElement = curElement->nextElement->nextElement;
908
909 removedElement = *tmpNext;
910 free(tmpNext);
911
912 return removedElement;
913 }
914 }
915 }
916 }
917
918 //element is not found, return empty element
919 removedElement.red = 0.0;
920 removedElement.green = 0.0;
921 removedElement.blue = 0.0;
922 removedElement.alpha = 0.0;
923 removedElement.position = NAN;
924 removedElement.nextElement = nil;
925
926 return removedElement;
927 }
928
929
930- (CTGradientElement *)elementAtIndex:(unsigned)index;
931 {
932 unsigned count = 0;
933 CTGradientElement *currentElement = elementList;
934
935 while(currentElement != nil)
936 {
937 if(count == index)
938 return currentElement;
939
940 count++;
941 currentElement = currentElement->nextElement;
942 }
943
944 return nil;
945 }
946#pragma mark -
947
948
949
950#pragma mark Core Graphics
951//////////////////////////////////////Blending Functions/////////////////////////////////////
952void linearEvaluation (void *info, const float *in, float *out)
953 {
954 float position = *in;
955
956 if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
957 {
958 out[0] = out[1] = out[2] = out[3] = 1;
959 return;
960 }
961
962 //This grabs the first two colors in the sequence
963 CTGradientElement *color1 = *(CTGradientElement **)info;
964 CTGradientElement *color2 = color1->nextElement;
965
966 //make sure first color and second color are on other sides of position
967 while(color2 != nil && color2->position < position)
968 {
969 color1 = color2;
970 color2 = color1->nextElement;
971 }
972 //if we don't have another color then make next color the same color
973 if(color2 == nil)
974 {
975 color2 = color1;
976 }
977
978 //----------FailSafe settings----------
979 //color1->red = 1; color2->red = 0;
980 //color1->green = 1; color2->green = 0;
981 //color1->blue = 1; color2->blue = 0;
982 //color1->alpha = 1; color2->alpha = 1;
983 //color1->position = .5;
984 //color2->position = .5;
985 //-------------------------------------
986
987 if(position <= color1->position) //Make all below color color1's position equal to color1
988 {
989 out[0] = color1->red;
990 out[1] = color1->green;
991 out[2] = color1->blue;
992 out[3] = color1->alpha;
993 }
994 else if (position >= color2->position) //Make all above color color2's position equal to color2
995 {
996 out[0] = color2->red;
997 out[1] = color2->green;
998 out[2] = color2->blue;
999 out[3] = color2->alpha;
1000 }
1001 else //Interpolate color at postions between color1 and color1
1002 {
1003 //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
1004 position = (position-color1->position)/(color2->position - color1->position);
1005
1006 out[0] = (color2->red - color1->red )*position + color1->red;
1007 out[1] = (color2->green - color1->green)*position + color1->green;
1008 out[2] = (color2->blue - color1->blue )*position + color1->blue;
1009 out[3] = (color2->alpha - color1->alpha)*position + color1->alpha;
1010 }
1011 }
1012
1013
1014
1015
1016//Chromatic Evaluation -
1017// This blends colors by their Hue, Saturation, and Value(Brightness) right now I just
1018// transform the RGB values stored in the CTGradientElements to HSB, in the future I may
1019// streamline it to avoid transforming in and out of HSB colorspace *for later*
1020//
1021// For the chromatic blend we shift the hue of color1 to meet the hue of color2. To do
1022// this we will add to the hue's angle (if we subtract we'll be doing the inverse
1023// chromatic...scroll down more for that). All we need to do is keep adding to the hue
1024// until we wrap around the colorwheel and get to color2.
1025void chromaticEvaluation(void *info, const float *in, float *out)
1026 {
1027 float position = *in;
1028
1029 if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
1030 {
1031 out[0] = out[1] = out[2] = out[3] = 1;
1032 return;
1033 }
1034
1035 //This grabs the first two colors in the sequence
1036 CTGradientElement *color1 = *(CTGradientElement **)info;
1037 CTGradientElement *color2 = color1->nextElement;
1038
1039 float c1[4];
1040 float c2[4];
1041
1042 //make sure first color and second color are on other sides of position
1043 while(color2 != nil && color2->position < position)
1044 {
1045 color1 = color2;
1046 color2 = color1->nextElement;
1047 }
1048 //if we don't have another color then make next color the same color
1049 if(color2 == nil)
1050 {
1051 color2 = color1;
1052 }
1053
1054
1055 c1[0] = color1->red;
1056 c1[1] = color1->green;
1057 c1[2] = color1->blue;
1058 c1[3] = color1->alpha;
1059
1060 c2[0] = color2->red;
1061 c2[1] = color2->green;
1062 c2[2] = color2->blue;
1063 c2[3] = color2->alpha;
1064
1065 transformRGB_HSV(c1);
1066 transformRGB_HSV(c2);
1067 resolveHSV(c1,c2);
1068
1069 if(c1[0] > c2[0]) //if color1's hue is higher than color2's hue then
1070 c2[0] += 360; // we need to move c2 one revolution around the wheel
1071
1072
1073 if(position <= color1->position) //Make all below color color1's position equal to color1
1074 {
1075 out[0] = c1[0];
1076 out[1] = c1[1];
1077 out[2] = c1[2];
1078 out[3] = c1[3];
1079 }
1080 else if (position >= color2->position) //Make all above color color2's position equal to color2
1081 {
1082 out[0] = c2[0];
1083 out[1] = c2[1];
1084 out[2] = c2[2];
1085 out[3] = c2[3];
1086 }
1087 else //Interpolate color at postions between color1 and color1
1088 {
1089 //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
1090 position = (position-color1->position)/(color2->position - color1->position);
1091
1092 out[0] = (c2[0] - c1[0])*position + c1[0];
1093 out[1] = (c2[1] - c1[1])*position + c1[1];
1094 out[2] = (c2[2] - c1[2])*position + c1[2];
1095 out[3] = (c2[3] - c1[3])*position + c1[3];
1096 }
1097
1098 transformHSV_RGB(out);
1099 }
1100
1101
1102
1103//Inverse Chromatic Evaluation -
1104// Inverse Chromatic is about the same story as Chromatic Blend, but here the Hue
1105// is strictly decreasing, that is we need to get from color1 to color2 by decreasing
1106// the 'angle' (i.e. 90¼ -> 180¼ would be done by subtracting 270¼ and getting -180¼...
1107// which is equivalent to 180¼ mod 360¼
1108void inverseChromaticEvaluation(void *info, const float *in, float *out)
1109 {
1110 float position = *in;
1111
1112 if(*(CTGradientElement **)info == nil) //if elementList is empty return clear color
1113 {
1114 out[0] = out[1] = out[2] = out[3] = 1;
1115 return;
1116 }
1117
1118 //This grabs the first two colors in the sequence
1119 CTGradientElement *color1 = *(CTGradientElement **)info;
1120 CTGradientElement *color2 = color1->nextElement;
1121
1122 float c1[4];
1123 float c2[4];
1124
1125 //make sure first color and second color are on other sides of position
1126 while(color2 != nil && color2->position < position)
1127 {
1128 color1 = color2;
1129 color2 = color1->nextElement;
1130 }
1131 //if we don't have another color then make next color the same color
1132 if(color2 == nil)
1133 {
1134 color2 = color1;
1135 }
1136
1137 c1[0] = color1->red;
1138 c1[1] = color1->green;
1139 c1[2] = color1->blue;
1140 c1[3] = color1->alpha;
1141
1142 c2[0] = color2->red;
1143 c2[1] = color2->green;
1144 c2[2] = color2->blue;
1145 c2[3] = color2->alpha;
1146
1147 transformRGB_HSV(c1);
1148 transformRGB_HSV(c2);
1149 resolveHSV(c1,c2);
1150
1151 if(c1[0] < c2[0]) //if color1's hue is higher than color2's hue then
1152 c1[0] += 360; // we need to move c2 one revolution back on the wheel
1153
1154
1155 if(position <= color1->position) //Make all below color color1's position equal to color1
1156 {
1157 out[0] = c1[0];
1158 out[1] = c1[1];
1159 out[2] = c1[2];
1160 out[3] = c1[3];
1161 }
1162 else if (position >= color2->position) //Make all above color color2's position equal to color2
1163 {
1164 out[0] = c2[0];
1165 out[1] = c2[1];
1166 out[2] = c2[2];
1167 out[3] = c2[3];
1168 }
1169 else //Interpolate color at postions between color1 and color1
1170 {
1171 //adjust position so that it goes from 0 to 1 in the range from color 1 & 2's position
1172 position = (position-color1->position)/(color2->position - color1->position);
1173
1174 out[0] = (c2[0] - c1[0])*position + c1[0];
1175 out[1] = (c2[1] - c1[1])*position + c1[1];
1176 out[2] = (c2[2] - c1[2])*position + c1[2];
1177 out[3] = (c2[3] - c1[3])*position + c1[3];
1178 }
1179
1180 transformHSV_RGB(out);
1181 }
1182
1183
1184
1185void transformRGB_HSV(float *components) //H,S,B -> R,G,B
1186 {
1187 float H, S, V;
1188 float R = components[0],
1189 G = components[1],
1190 B = components[2];
1191
1192 float MAX = R > G ? (R > B ? R : B) : (G > B ? G : B),
1193 MIN = R < G ? (R < B ? R : B) : (G < B ? G : B);
1194
1195 if(MAX == MIN)
1196 H = NAN;
1197 else if(MAX == R)
1198 if(G >= B)
1199 H = 60*(G-B)/(MAX-MIN)+0;
1200 else
1201 H = 60*(G-B)/(MAX-MIN)+360;
1202 else if(MAX == G)
1203 H = 60*(B-R)/(MAX-MIN)+120;
1204 else if(MAX == B)
1205 H = 60*(R-G)/(MAX-MIN)+240;
1206
1207 S = MAX == 0 ? 0 : 1 - MIN/MAX;
1208 V = MAX;
1209
1210 components[0] = H;
1211 components[1] = S;
1212 components[2] = V;
1213 }
1214
1215void transformHSV_RGB(float *components) //H,S,B -> R,G,B
1216 {
1217 float R, G, B;
1218 float H = fmodf(components[0],359), //map to [0,360)
1219 S = components[1],
1220 V = components[2];
1221
1222 int Hi = (int)floorf(H/60.) % 6;
1223 float f = H/60-Hi,
1224 p = V*(1-S),
1225 q = V*(1-f*S),
1226 t = V*(1-(1-f)*S);
1227
1228 switch (Hi)
1229 {
1230 case 0: R=V;G=t;B=p; break;
1231 case 1: R=q;G=V;B=p; break;
1232 case 2: R=p;G=V;B=t; break;
1233 case 3: R=p;G=q;B=V; break;
1234 case 4: R=t;G=p;B=V; break;
1235 case 5: R=V;G=p;B=q; break;
1236 }
1237
1238 components[0] = R;
1239 components[1] = G;
1240 components[2] = B;
1241 }
1242
1243void resolveHSV(float *color1, float *color2) //H value may be undefined (i.e. graycale color)
1244 { // we want to fill it with a sensible value
1245 if(isnan(color1[0]) && isnan(color2[0]))
1246 color1[0] = color2[0] = 0;
1247 else if(isnan(color1[0]))
1248 color1[0] = color2[0];
1249 else if(isnan(color2[0]))
1250 color2[0] = color1[0];
1251 }
1252
1253@end
Note: See TracBrowser for help on using the repository browser.