diff options
Diffstat (limited to 'AppKit/GTMNSBezierPath+Shading.m')
-rw-r--r-- | AppKit/GTMNSBezierPath+Shading.m | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/AppKit/GTMNSBezierPath+Shading.m b/AppKit/GTMNSBezierPath+Shading.m new file mode 100644 index 0000000..f5935ab --- /dev/null +++ b/AppKit/GTMNSBezierPath+Shading.m @@ -0,0 +1,214 @@ +// +// GTMNSBezierPath+Shading.m +// +// Category for radial and axial stroke and fill functions for NSBezierPaths +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMNSBezierPath+Shading.h" +#import "GTMNSBezierPath+CGPath.h" +#import "GTMShading.h" +#import "GTMGeometryUtils.h" + +@interface NSBezierPath (GTMBezierPathShadingAdditionsPrivate) +// Fills a CGPathRef either axially or radially with the given shading. +// +// Args: +// path: path to fill +// axially: if YES fill axially, otherwise fill radially +// asStroke: if YES, clip to the stroke of the path, otherwise +// clip to the fill +// from: where to shade from +// fromRadius: in a radial fill, the radius of the from circle +// to: where to shade to +// toRadius: in a radial fill, the radius of the to circle +// extendingStart: if true, extend the fill with the first color of the shade +// beyond |from| away from |to| +// extendingEnd: if true, extend the fill with the last color of the shade +// beyond |to| away from |from| +// shading: the shading to use for the fill +// +- (void)gtm_fillCGPath:(CGPathRef)path + axially:(BOOL)axially + asStroke:(BOOL)asStroke + from:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading; + +// Returns the point which is the projection of a line from point |pointA| +// to |pointB| by length +// +// Args: +// pointA: first point +// pointB: second point +// length: distance to project beyond |pointB| which is in line with +// |pointA| and |pointB| +// +// Returns: +// the projected point +- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA + to:(NSPoint)pointB + by:(float)length; +@end + + +@implementation NSBezierPath (GTMBezierPathAdditionsPrivate) + +- (void)gtm_fillCGPath:(CGPathRef)path + axially:(BOOL)axially asStroke:(BOOL)asStroke + from:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGFunctionRef shadingFunction = [shading shadeFunction]; + if (nil != shadingFunction) { + CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + if (nil != currentContext) { + CGContextSaveGState(currentContext); + float lineWidth = [self lineWidth]; + CGContextSetLineWidth(currentContext,lineWidth); + if (asStroke) { + // if we are using the stroke, we offset the from and to points + // by half the stroke width away from the center of the stroke. + // Otherwise we tend to end up with fills that only cover half of the + // because users set the start and end points based on the center + // of the stroke. + float halfWidth = lineWidth * 0.5f; + fromPoint = [self gtm_projectLineFrom:toPoint to:fromPoint by:halfWidth]; + toPoint = [self gtm_projectLineFrom:fromPoint to:toPoint by:-halfWidth]; + } + CGColorSpaceRef colorspace = [shading colorSpace]; + if (nil != colorspace) { + CGPoint toCGPoint = GTMNSPointToCGPoint(toPoint); + CGPoint fromCGPoint = GTMNSPointToCGPoint(fromPoint); + CGShadingRef myCGShading; + if(axially) { + myCGShading = CGShadingCreateAxial(colorspace, fromCGPoint, + toCGPoint, shadingFunction, + extendingStart == YES, + extendingEnd == YES); + } + else { + myCGShading = CGShadingCreateRadial(colorspace, fromCGPoint, fromRadius, + toCGPoint, toRadius, shadingFunction, + extendingStart == YES, + extendingEnd == YES); + } + + if (nil != myCGShading) { + CGContextAddPath(currentContext,path); + if(asStroke) { + CGContextReplacePathWithStrokedPath(currentContext); + } + CGContextClip(currentContext); + CGContextDrawShading(currentContext, myCGShading); + CGShadingRelease(myCGShading); + } + } + CGContextRestoreGState(currentContext); + } + } +} + + +- (NSPoint)gtm_projectLineFrom:(NSPoint)pointA + to:(NSPoint)pointB + by:(float)length { + NSPoint newPoint = pointB; + float x = (pointB.x - pointA.x); + float y = (pointB.y - pointA.y); + if (x == 0.0f) { + newPoint.y += length; + } else if (y == 0.0f) { + newPoint.x += length; + } else { + float angle = atanf(y / x); + newPoint.x += sinf(angle) * length; + newPoint.y += cosf(angle) * length; + } + return newPoint; +} + +@end + + +@implementation NSBezierPath (GTMBezierPathShadingAdditions) + +//METHOD_CHECK(NSBezierPath, gtm_createCGPath); + +- (void)gtm_strokeAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:YES asStroke:YES + from:fromPoint fromRadius:0.0f + to:toPoint toRadius:0.0f + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_strokeRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:NO asStroke:YES + from:fromPoint fromRadius:fromRadius + to:toPoint toRadius:toRadius + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_fillAxiallyFrom:(NSPoint)fromPoint to:(NSPoint)toPoint + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:YES asStroke:NO + from:fromPoint fromRadius:0.0f + to:toPoint toRadius:0.0f + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + + +- (void)gtm_fillRadiallyFrom:(NSPoint)fromPoint fromRadius:(float)fromRadius + to:(NSPoint)toPoint toRadius:(float)toRadius + extendingStart:(BOOL)extendingStart extendingEnd:(BOOL)extendingEnd + shading:(id<GTMShading>)shading { + CGPathRef thePath = [self gtm_createCGPath]; + if (nil != thePath) { + [self gtm_fillCGPath:thePath axially:NO asStroke:NO + from:fromPoint fromRadius:fromRadius + to:toPoint toRadius:toRadius + extendingStart:extendingStart extendingEnd:extendingEnd + shading:shading]; + CGPathRelease(thePath); + } +} + +@end |