Merge pull request #10 from gunyarakun/fix-invalid-return
[cocotron.git] / AppKit / NSGradient.m
blobc78b6a084ca8e02e84b6906ced1d24d33bff8d41
1 /* Copyright (c) 2008 Sijmen Mulder
3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
9 #import "NSGradient.h"
10 #import <AppKit/NSBezierPath.h>
11 #import <AppKit/NSColor.h>
12 #import <AppKit/NSColorSpace.h>
13 #import <AppKit/NSGraphicsContext.h>
14 #import <AppKit/NSRaise.h>
16 @implementation NSGradient
18 static void evaluate(void *info,float const *input,float *output) {
19    NSGradient *self=(NSGradient *)info;
20    CGFloat   **components=self->_components;
21    CGFloat    *locations=self->_locations;
22    
23     // Find where we are within the gradient location range - this will establish
24     // which color pair we are blending between
25     NSInteger colorIndex = 0;
26     for(colorIndex = 0; colorIndex < self->_numberOfColors; colorIndex++) {
27        if(locations[colorIndex]>=input[0]) {
28            // We've found the right side of the color range
29            break;
30        }
31     }
32     
33     NSInteger   startColorIndex = 0, endColorIndex = 0;
34     // it could be right at the limit
35     if (colorIndex >= self->_numberOfColors) {
36         startColorIndex = endColorIndex = self->_numberOfColors - 1;
37     } else {
38         endColorIndex = colorIndex;
39         // Start index must then be the preceding index
40         startColorIndex = colorIndex - 1;
41         if (startColorIndex < 0) {
42             // Make sure we don't go out of range to the left
43             startColorIndex = 0;
44         }
45     }
46     
47     // Now here's the tricky part, we need to find out the ratio between the two colors.
48     // This means figuring out the distance between the two and then figuring out how far along we
49     // are between them
50     float start = locations[startColorIndex];
51     float length = locations[endColorIndex] - locations[startColorIndex];
52     float offset = input[0] - start;
54     float ratio = 0;
55     // Make sure we don't divide by 0!
56     if (length > 0) {
57         ratio = offset/length;
58     }
59     
60     // now blend all the components using the ratio
61     NSInteger componentIndex;
62    for(componentIndex = 0; componentIndex < self->_numberOfComponents; componentIndex++){
63     output[componentIndex] = (components[startColorIndex][componentIndex] +
64                  (ratio * (components[endColorIndex][componentIndex] - components[startColorIndex][componentIndex])));
65    }
69 -initWithStartingColor:(NSColor *)startingColor endingColor:(NSColor *)endingColor {
70    NSArray *colors=[NSArray arrayWithObjects:startingColor,endingColor,nil];
71    CGFloat  locations[2]={0.0,1.0};
72    
73    return [self initWithColors:colors atLocations:locations colorSpace:[NSColorSpace deviceRGBColorSpace]];
76 -initWithColors:(NSArray *)colors {
77     NSAssert([colors count] > 1, @"A gradient needs at least 2 colors!");
78    NSInteger count=[colors count];
79    CGFloat   locations[count];
80    NSInteger i;
81    
82    for (i = 0; i < count; i++)
83     locations[i]=i/(float)(count-1);
85    return [self initWithColors:colors atLocations:locations colorSpace:[NSColorSpace deviceRGBColorSpace]];
88 -initWithColorsAndLocations:(NSColor *)firstColor,... {
89    NSMutableArray *colors=[NSMutableArray array];
90    CGFloat         locations[256]; // FIXME: seems reasonable for now
91    NSInteger       i;
92    
93    va_list arguments;
94     
95    va_start(arguments,firstColor);
96    
97    NSColor *color=firstColor;
98    for(i=0;color!=nil && i<256;i++){    
99     [colors addObject:color];
100     locations[i]=va_arg(arguments,double);
101     color=va_arg(arguments,NSColor *);
102    }
103    
104    va_end(arguments);
105    
106    return [self initWithColors:colors atLocations:locations colorSpace:[NSColorSpace deviceRGBColorSpace]];
109 -initWithColors:(NSArray *)colors atLocations:(const CGFloat *)locations colorSpace:(NSColorSpace *)colorSpace {
110    _colorSpace=[[NSColorSpace deviceRGBColorSpace] retain];
111    _numberOfColors=[colors count];
112    _numberOfComponents=4;
113    _components=NSZoneMalloc(NULL,sizeof(CGFloat *)*_numberOfColors);
114    _locations=NSZoneMalloc(NULL,sizeof(CGFloat)*_numberOfColors);
115    
116    NSInteger i;
117    
118    for(i=0;i<_numberOfColors;i++){
119     NSColor *color=[colors objectAtIndex:i];
120     
121     color=[color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
122     _components[i]=NSZoneMalloc(NULL,sizeof(CGFloat)*_numberOfComponents);
123     [color getComponents:_components[i]];
124     
125     _locations[i]=locations[i];
126    }
127    
128    return self;
131 -(void)dealloc {
132    NSInteger i;
133    
134    [_colorSpace release];
135    
136    for(i=0;i<_numberOfColors;i++)
137     NSZoneFree(NULL,_components[i]);
138     
139    NSZoneFree(NULL,_components);
140    NSZoneFree(NULL,_locations);
141         
142    [super dealloc];
146 -(void)drawFromPoint:(NSPoint)startingPoint toPoint:(NSPoint)endingPoint options:(NSGradientDrawingOptions)options {
147    CGContextRef context=[[NSGraphicsContext currentContext] graphicsPort];
148    CGFunctionCallbacks callbacks = { 0, evaluate, NULL }; 
149    CGFunctionRef function = CGFunctionCreate(self, 1, NULL, _numberOfComponents, NULL, &callbacks);
150    CGColorSpaceRef colorSpace = [_colorSpace CGColorSpace];
151    CGShadingRef shading = CGShadingCreateAxial(colorSpace, startingPoint, endingPoint, function, NO, NO);
152    
153    CGContextDrawShading(context,shading);
154    
155    CGFunctionRelease(function);
156    CGShadingRelease(shading);
159 - (void)drawFromCenter:(NSPoint)startCenter radius:(CGFloat)startRadius toCenter:(NSPoint)endCenter radius:(CGFloat)endRadius options:(NSGradientDrawingOptions)options {
160         NSUnimplementedMethod();
163 - (void)drawInRect:(NSRect)rect angle:(CGFloat)angle
165         if (_numberOfColors < 2 || 0 == rect.size.width)
166                 return;
168         CGPoint start; //start coordinate of gradient
169         CGPoint end; //end coordinate of gradient
170         //tanSize is the rectangle size for atan2 operation. It is the size of the rect in relation to the offset start point
171         CGPoint tanSize; 
173         angle = (CGFloat)fmod(angle, 360);
174         
175         if (angle < 90)
176         {
177                 start = CGPointMake(rect.origin.x, rect.origin.y);
178                 tanSize = CGPointMake(rect.size.width, rect.size.height);
179         }
180         else if (angle < 180)
181         {
182                 start = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y);
183                 tanSize = CGPointMake(-rect.size.width, rect.size.height);
184         }
185         else if (angle < 270)
186         {
187                 start = CGPointMake(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
188                 tanSize = CGPointMake(-rect.size.width, -rect.size.height);
189         }
190         else
191         {
192                 start = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);
193                 tanSize = CGPointMake(rect.size.width, -rect.size.height);
194         }
195         
196         
197         CGFloat radAngle = angle / 180 * M_PI; //Angle in radians
198         //The trig for this is difficult to describe without an illustration, so I'm attaching an illustration
199         //to Cocotron issue number 438 along with this patch
200         CGFloat distanceToEnd = cos(atan2(tanSize.y,tanSize.x) - radAngle) * sqrt(rect.size.width * rect.size.width + rect.size.height * rect.size.height);
201         end = CGPointMake(cos(radAngle) * distanceToEnd + start.x, sin(radAngle) * distanceToEnd + start.y);
202                 
203         CGContextRef context =[[NSGraphicsContext currentContext] graphicsPort];
204         CGContextSaveGState(context);
205         CGContextClipToRect(context, rect);
206     [self drawFromPoint:start toPoint:end options:NSGradientDrawsBeforeStartingLocation|NSGradientDrawsAfterEndingLocation];
207         CGContextRestoreGState(context);
208         
211 -(void)drawInBezierPath:(NSBezierPath *)path angle:(CGFloat)angle {
212    NSRect rect=[path bounds];
213    [NSGraphicsContext saveGraphicsState];
215    [path addClip];
216    [self drawInRect:rect angle:angle];  
218    [NSGraphicsContext restoreGraphicsState];
221 -(void)drawInRect:(NSRect)rect relativeCenterPosition:(NSPoint)center {
222         NSUnimplementedMethod();
225 -(void)drawInBezierPath:(NSBezierPath *)path relativeCenterPosition:(NSPoint)center {
226         NSUnimplementedMethod();
229 - (NSColorSpace *)colorSpace {
230         return _colorSpace;
233 - (NSInteger)numberOfColorStops {
234         return _numberOfColors;
237 -(void)getColor:(NSColor **)color location:(CGFloat *)location atIndex:(NSInteger)index {
238    if (location)
239     *location=_locations[index];
240     
241    if (color)
242     *color=[NSColor colorWithCalibratedRed:_components[index][0] green:_components[index][1] blue:_components[index][2] alpha:_components[index][3]];
245 -(NSColor *)interpolatedColorAtLocation:(CGFloat)location {
246    float input[1]={location};
247    float output[4];
248    
249    evaluate(self,input,output);
251    return [NSColor colorWithCalibratedRed:output[0] green:output[1] blue:output[2] alpha:output[3]];
254 @end