Clipping a CGGradient to a CGPath
I've been banging my head against the wall for a long while trying to figure out why this is not working.
Basically, I am trying to plot a graph (chart) with CGPath and then use that to clip a gradient. The end effect should be like the Stocks app which comes with the iPhone.
The gradient and the path draw fine separately as two layered (unclipped) elements. But if I comment out the CGContextDrawPath, neither the line nor the gradient draw to the screen.
Here's my drawRect code:
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor whiteColor] set];
CGContextSetLineWidth(context, 2.0f);
CGPoint lastDrawnPt = [[points objectAtIndex:0] CGPointValue];
CGPoint firstDrawnPt = lastDrawnPt;
//create the path for the gradient
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height); // bottom left
CGPathAddLineToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y);
for (int i=0; i<(points.count-1); i++) {
//CGPoint pt1 = [[points objectAtIndex:i] CGPointValue];
CGPoint pt2 = [[points objectAtIndex:i+1] CGPointValue];
if (pt2.x > lastDrawnPt.x+2) {
// only draw if we've moved sunstantially to the right
//for the gradient
CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y);
CGPathAddLineToPoint(thePath, NULL, pt2.x, pt2.y);
lastDrawnPt = pt2;
}
}
//finish the gradient clipping path
CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y);
CGPathAddLineToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height); // bottom right
CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height);
CGPathAddLineToPoint(thePath, NULL, firstDrawnPt.x, self.bounds.size.height); // bottom right
CGPathCloseSubpath(thePath);
//add the gradient clipping path to the context
CGContextSaveGState(context);
CGContextAddPath(context, thePath);
//draw the path
float components[4] = {1.0, 1.0, 1.0, 1.0};
CGContextSetStrokeColor(context, components);
CGContextDrawPath(context,kCGPathStroke);
//clip the path
CGContextClip(context);
//Draw Gradient
UIColor *topColor = [UIColor colorWithRed: 1.0 green:1.0 blue:1.0 alpha:1.0];
UIColor *bottomColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.0];
CGColorRef colorRef[] = { [topColor CGColor], [bottomColor CGColor] };
CFArrayRef colors = CFArrayCreate(NULL, (const void**)colorRef, sizeof(colorRef) / sizeof(CGColorRef), &kCFTypeArrayCallBacks);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, colors, NULL);
CFRelease(colorSpace);
CFRelease(colors);
// Draw a linear gradient from top to bottom
CGPoint gradStartPoint = CGPointMa开发者_开发问答ke(50.0, self.bounds.size.height);
CGPoint gradEndPoint = CGPointMake(50.0, 0.0);
CGContextDrawLinearGradient(context, gradient, gradStartPoint, gradEndPoint, 0);
CFRelease(gradient);
// Cleanup
CGColorSpaceRelease(colorSpace);
CGContextRestoreGState(context);
CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height); // bottom left CGPathAddLineToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y); for (int i=0; i<(points.count-1); i++) { //CGPoint pt1 = [[points objectAtIndex:i] CGPointValue]; CGPoint pt2 = [[points objectAtIndex:i+1] CGPointValue]; if (pt2.x > lastDrawnPt.x+2) { // only draw if we've moved sunstantially to the right //for the gradient CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y); CGPathAddLineToPoint(thePath, NULL, pt2.x, pt2.y); lastDrawnPt = pt2; } } //finish the gradient clipping path CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, lastDrawnPt.y); CGPathAddLineToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height); // bottom right CGPathMoveToPoint(thePath, NULL, lastDrawnPt.x, self.bounds.size.height); CGPathAddLineToPoint(thePath, NULL, firstDrawnPt.x, self.bounds.size.height); // bottom right
This just plots a series of line segments. For certain point values, it might look something like this:
| |||| |
It does not plot a single continuous shape. As such, this path is useless for clipping, as it is effectively empty; as you've seen, clipping to it will result in no further drawing being inside the clipping path.
Every lineto
, curveto
, arc
, etc. works from the current point. You set that initially with moveto
, but each lineto
, curveto
, arc
, etc. does not clear the current point, it updates it. Thus, to create a single shape, you do one moveto
followed by a succession of lineto
(or curveto
or arc
), followed eventually by closepath
.
Speaking of closepath
…
CGPathCloseSubpath(thePath);
This creates the only closed shape in the path, but since that shape has zero area (being only a line segment that doubles back on itself), it is still not helpful for clipping purposes.
I suspect that all you need to do is cut out at least one of the moveto
segments (the one in the loop, if not also the one after it). Then, you can simplify the loop—use fast enumeration on the array, instead of using indexes, and cut out keeping track of the “last drawn point”.
Also, the correct type for indexes into an NSArray is NSUInteger
, not int
. Keep your types matched—it avoids pain later down the road.
精彩评论