Bay
Bay

Reputation: 71

CGGradient: Drawing a linear gradient on an angle

I am trying to draw a linear CGGradient on an angle. Because "CGContextDrawLinearGradientWithAngle()" does not exist, I am trying to use CGContextDrawLinearGradient(CGContextRef, CGGradientRef, CGPoint startPoint, CGPoint endPoint, CGGradientDrawingOptions).

With that in mind, I need to convert an angle (degrees) into a starting point and an ending point. I would like to mimic NSGradient's drawInBezierPath:angle. (As a part of AppKit, NSGradient is sadly not available to iOS developers.) Fortunately, the documentation tells us how to get the starting gradient:

- (CGPoint)startingPointForAngle:(CGFloat)angle rect:(CGRect)rect { 
    CGPoint point = CGPointZero;
    if (angle < 90.0f)
        point = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
    else if (angle < 180.0f)
        point = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
    else if (angle < 270.0f)
        point = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
    else
        point = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));

    return point;
}

Unfortunately, the documentation does not tell us how to get the ending point. (Using either the height or the width of the rect as the distance is only sufficient for certain angles.) Several sites out there tells us how we can find the ending point. Unfortunately, the distance needs to be known before I can compute the ending point. Yet the ending point needs to be computed to get the distance. There is clearly more to it, as NSGradient seems to have it figured out.

- (CGPoint)endingPointForAngle:(CGFloat)angle rect:(CGRect)rect startingPoint:(CGPoint)startingPoint {
    //http://www.zahniser.net/~russell/computer/index.php?title=Angle%20and%20Coordinates
    //(x + distance * cos(a), y + distance * sin(a))
    CGFloat angleInRadians = (CGFloat)M_PI/180.0f * angle;
    CGFloat distance = ????????;
    CGPoint point = CGPointMake(startingPoint.x + distance * cosf(angleInRadians), startingPoint.y + distance * sinf(angleInRadians));
    return point;
}

CGPoint startingGradientPoint = [self startingPointForAngle:self.fillGradientAngle rect:rect];
CGPoint endingGradientPoint = [self endingPointForAngle:self.fillGradientAngle rect:rect startingPoint:startingGradientPoint];
CGContextDrawLinearGradient(graphicsContext, self.fillGradient, startingGradientPoint, endingGradientPoint, 0);

Any ideas.

Upvotes: 6

Views: 2740

Answers (2)

ian
ian

Reputation: 116

I'm dealing with the same problem, with a little different way, I use the center point and and angle, and extend the side left and right to find it's points on the edge, my problem was there will be white space if the angel is not pointing any axis, and the drawing option the functions provide a "kCGGradientDrawsBeforeStartLocation or kCGGradientDrawsAfterEndLocation" so it looks like one side will be empty.

But it turns out I can combine the two options with '|', so problem solved, here's my code:

CGGradientRef grRef = CGGradientCreateWithColors(colorSpace,  (__bridge CFArrayRef)gradient.colorArray, NULL);
CGFloat degree = angle * M_PI / 180;
CGPoint center = CGPointMake(width/2, height/2);
CGPoint startPoint = CGPointMake(center.x - cos (degree) * width/2, center.y - sin(degree) * height/2);
CGPoint endPoint = CGPointMake(center.x + cos (degree) * width/2, center.y + sin(degree) * height/2);

NSLog(@"start point:%@ \n, end point: %@",NSStringFromCGPoint(startPoint),NSStringFromCGPoint(endPoint));

CGContextDrawLinearGradient(gradientContext, grRef, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);

Upvotes: 2

Rupert Horlick
Rupert Horlick

Reputation: 681

I'm not 100% sure how this gradient thing works but from what you've written I'm assuming that you basically just want the length of a line from the starting point until it hits the side of the rectangle.

If this is the case you simply need to do some trigonometry. Lets call the distance x and the angle a.

Between 0 and 45 degrees: width = xcos(a) so x = width/cos(a)

Between 45 and 90 degress: height = xsin(a) so x = height/sin(a)

Between 90 and 135 degrees we have moved to a new corner. Here x = height/cos(a-90).

Between 135 and 180 x = width/sin(a-90)

Between 180 and 225 we have again moved corner. Here x = width/cos(a-180).

Between 225 and 270 x = height/sin(a-180)

Last corner! Between 270 and 315 x = height/sin(a-270)

And finally between 315 and 360 x = width/cos(a-270)

Some of these probably simplify but its easiest to think about the line starting in the bottom left corner pointing right and sweeping round anticlockwise which is what appears to happen in your starting point calculation.

Upvotes: 0

Related Questions