API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPGradient.j
Go to the documentation of this file.
1 /*
2  * CPGradient.j
3  * AppKit
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2012, SlevenBits Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
27 
31 @implementation CPGradient : CPObject
32 {
33  CGGradient _gradient;
34 }
35 
36 - (id)initWithStartingColor:(CPColor)startingColor endingColor:(CPColor)endingColor
37 {
38  return [self initWithColors:[startingColor, endingColor]];
39 }
40 
41 - (id)initWithColors:(CPArray)someColors
42 {
43  var count = [someColors count];
44 
45  if (count < 2)
46  [CPException raise:CPInvalidArgumentException reason:@"at least 2 colors required"];
47 
48  var distance = 1.0 / (count - 1),
49  locations = [CPMutableArray array],
50  location = 0.0;
51 
52  for (var i = 0; i < count; i++)
53  {
54  [locations addObject:location];
55  location += distance;
56  }
57 
58  return [self initWithColors:someColors atLocations:locations colorSpace:nil];
59 }
60 
61 - (id)initWithColors:(CPArray)someColors atLocations:(CPArray)someLocations colorSpace:(CPColorSpace)aColorSpace
62 {
63  if (self = [super init])
64  {
65  var cgColors = [],
66  count = [someColors count],
67  colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB;
68  for (var i = 0; i < count; i++)
69  cgColors.push(CGColorCreate(colorSpace, [someColors[i] components]));
70  _gradient = CGGradientCreateWithColors(colorSpace, cgColors, someLocations);
71  }
72 
73  return self;
74 }
75 
76 - (void)drawInRect:(CGRect)rect angle:(float)angle
77 {
79 
81  CGContextClipToRect(ctx, rect);
82  CGContextAddRect(ctx, rect);
83 
84  [self _drawInRect:rect atAngle:angle];
85 
87 }
88 
92 - (void)_drawInRect:(CGRect)rect atAngle:(float)angle
93 {
95 
96  startPoint,
97  endPoint;
98 
99  // Modulo of negative values doesn't work as expected in JS.
100  angle = ((angle % 360.0) + 360.0) % 360.0;
101 
102  if (angle < 90.0)
103  startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
104  else if (angle < 180.0)
105  startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
106  else if (angle < 270.0)
107  startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
108  else
109  startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
110 
111  // A line segment comes out of the starting point at the given angle, with the first colour
112  // at the starting point and the last at the end. To do what drawInRect: is supposed to do
113  // we want the opposite corner of the starting corner to just reach the final colour stop.
114  // So when the angle isn't a right angle, the segment has to extend beyond the edge of the
115  // rectangle just far enough. This is hard to describe without a picture but if we place
116  // another line through the opposite corner at -90 degrees, it'll help form some triangles
117  // from which we can derive this formula:
118  //
119  // length = cos(angle) * (rectWidth + rectHeight * tan(angle))
120  //
121  // This simplifies down to (in the first quadrant) rectWidth * cos(a) + rectHeight * sin(a).
122 
123  var radians = PI * angle / 180.0,
124  length = ABS(CGRectGetWidth(rect) * COS(radians)) + ABS(CGRectGetHeight(rect) * SIN(radians));
125 
126  endPoint = CGPointMake(startPoint.x + length * COS(radians),
127  startPoint.y + length * SIN(radians));
128 
129  [self drawFromPoint:startPoint toPoint:endPoint options:CPGradientDrawsBeforeStartingLocation | CPGradientDrawsAfterEndingLocation];
130 }
131 
132 - (void)drawInBezierPath:(CPBezierPath)aPath angle:(float)anAngle
133 {
135 
137 
138  // Nail down the path which CGContextDrawLinearGradient will cause to be filled.
139  // Note we don't do this by clipping to the path and then calling drawInRect:atAngle:
140  // as this would cut off any antialias of the drawing, plus any active context shadow.
141  CGContextBeginPath(ctx);
142  CGContextAddPath(ctx, aPath._path);
143  CGContextSetLineWidth(ctx, [aPath lineWidth]);
144  CGContextClosePath(ctx);
145 
146  [self _drawInRect:[aPath bounds] atAngle:anAngle];
147 
149 }
150 
151 - (void)drawFromPoint:(NSPoint)startingPoint toPoint:(NSPoint)endingPoint options:(NSGradientDrawingOptions)options
152 {
154 
155  // TODO kCGGradientDrawsBeforeStartLocation and kCGGradientDrawsAfterEndLocation are not actually supported
156  // by CGContextDrawLinearGradient yet.
157  CGContextDrawLinearGradient(ctx, _gradient, startingPoint, endingPoint, options);
158 }
159 
160 @end