Draw ellipse with start and end angle in Objective-C
Asked Answered
G

3

8

I am writing an iPad app in which I am rendering XML objects that represent shapes into graphics on the screen. One of the objects I am trying to render is arcs. Essentially these arcs provide me with a bounding rectangle as well as a start and end angle.

Given attributes:

  • x
  • y
  • width
  • height
  • startAngle
  • endAngle

With these values I need to draw the arc (which is essentially part of an ellipse). I can not use the following:

    UIBezierPath *arc = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x, y, width, height)];
    [UIColor blackColor] setStroke];
    [arc stroke];

because it draws a full ellipse. Basically I need the above but it needs to take into account the start and end angles so only part of the ellipse is displayed. I am thinking this will involve either drawing a cubic Bezier curve or a quadratic Bezier curve. The problem is I have no clue how to calculate the start point, end point, or control points with the information I am given.

Gmur answered 17/5, 2012 at 21:41 Comment(1)
I would dare drawing an arc then transforming it, because ellipse is just a squished circle. But I'm too lazy to do all the math required to turn this comment into an answer.Notion
T
10

You can probably achieve what you want by setting up a clip path around the drawing of the ellipse.

CGContextSaveGState(theCGContext);
CGPoint center = CGPointMake(x + width / 2.0, y + height / 2.0);
UIBezierPath* clip = [UIBezierPath bezierPathWithArcCenter:center
                                                    radius:max(width, height)
                                                startAngle:startAngle
                                                  endAngle:endAngle
                                                 clockwise:YES];
[clip addLineToPoint:center];
[clip closePath];
[clip addClip];

UIBezierPath *arc = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x, y, width, height)];
[[UIColor blackColor] setStroke];
[arc stroke];

CGContextRestoreGState(theCGContext);

The exact radius for the clipping isn't important. It needs to be big enough so it only clips the ellipse at the ends, not through the desired arc.

Tamasha answered 17/5, 2012 at 22:5 Comment(1)
Thanks, worked like a charm (still new to Objective-C as well as drawing graphics).Gmur
W
4

This category will help.

#import <Foundation/Foundation.h>

@interface UIBezierPath (OvalSegment)

+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle;
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle angleStep:(CGFloat)angleStep;

@end

#import "UIBezierPath+OvalSegment.h"

@implementation UIBezierPath (OvalSegment)

+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle angleStep:(CGFloat)angleStep {
    CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
    CGFloat xRadius = CGRectGetWidth(rect)/2.0f;
    CGFloat yRadius = CGRectGetHeight(rect)/2.0f;

    UIBezierPath *ellipseSegment = [UIBezierPath new];

    CGPoint firstEllipsePoint = [self ellipsePointForAngle:startAngle withCenter:center xRadius:xRadius yRadius:yRadius];
    [ellipseSegment moveToPoint:firstEllipsePoint];

    for (CGFloat angle = startAngle + angleStep; angle < endAngle; angle += angleStep) {
        CGPoint ellipsePoint = [self ellipsePointForAngle:angle withCenter:center xRadius:xRadius yRadius:yRadius];
        [ellipseSegment addLineToPoint:ellipsePoint];
    }

    CGPoint lastEllipsePoint = [self ellipsePointForAngle:endAngle withCenter:center xRadius:xRadius yRadius:yRadius];
    [ellipseSegment addLineToPoint:lastEllipsePoint];

    return ellipseSegment;
}

+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle {
    return [UIBezierPath bezierPathWithOvalInRect:rect startAngle:startAngle endAngle:endAngle angleStep:M_PI/20.0f];
}

+ (CGPoint)ellipsePointForAngle:(CGFloat)angle withCenter:(CGPoint)center xRadius:(CGFloat)xRadius yRadius:(CGFloat)yRadius {
    CGFloat x = center.x + xRadius * cosf(angle);
    CGFloat y = center.y - yRadius * sinf(angle);
    return CGPointMake(x, y);
}

@end
Wootan answered 13/5, 2015 at 20:6 Comment(2)
Great category!!! Just to make it perfect, the first point to add line to should not include the start point which is firstEllipsePoint in the code above. So the for statement should be for (CGFloat angle = startAngle + angleStep; angle < endAngle; angle += angleStep) {...Almemar
The ellipsePointForAngle seems to be incorrect, or there's a discrepancy that differs for the method I use. As the angle increases around the oval, it looks to offset slightly back and forth. This is partially caused by not including the width and height of the ellipse, which is important and different from just finding a point on a circle from an angle.Wasson
E
2

You will have to use addQuadCurveToPoint:controlPoint:instead of bezierPathWithOvalInRect.

The method definition is as:-

- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint

that is CGPoint are the arguments(input) for both the parameters.

You will also have to set the start point using moveToPoint:before calling addQuadCurveToPoint which will act as current point(As you can see their's no start point as parameter in the method).

In your case you will have

1>x,y as your starting point

2>x+width and y+height as endpoint

you don't need angles here or you can implement your logic to use the angles.Am uploading an image to make thing clear.

Here is the image describing the start point,end point and control point

Eleonoreleonora answered 17/5, 2012 at 21:57 Comment(4)
A quadratic bezier curve won't do. Maybe you can calculate control points for a cubic bezier curve such that an ellipse is drawn with the given properties. Without the math needed to translate the given information into a bezier curve, this does not answer the question.Uncoil
Actually, I think if you want to draw the path explicitly (ie NOT with clipping) you need the parametric equation for the elipse. Then you'll have to make a bunch of short segments (quadratic or cubic, or even linear) which APPROXIMATE the elipse. Clipping is better.Sejant
This is a great Techotopia: techotopia.com/index.php/…Resnick
Here is the solution in swift...techotopia.com/index.php/…Slapjack

© 2022 - 2024 — McMap. All rights reserved.