在Objective-C中按给定的起始角度和结束角度绘制椭圆

8
我正在编写一款iPad应用程序,其中我正在将表示形状的XML对象渲染成屏幕上的图形。我正在尝试渲染的对象之一是弧形,这些弧形为我提供了一个边界矩形以及起始和结束角度。
给定的属性如下:
  • x
  • y
  • width
  • height
  • startAngle
  • endAngle
根据这些值,我需要绘制弧形(本质上是椭圆的一部分)。我不能使用以下方法:
    UIBezierPath *arc = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(x, y, width, height)];
    [UIColor blackColor] setStroke];
    [arc stroke];

因为它绘制了完整的椭圆。基本上我需要上面的代码,但需要考虑起始角度和结束角度,以便只显示部分椭圆。 我认为这将涉及绘制立方贝塞尔曲线或二次贝塞尔曲线。问题是我不知道如何根据所给信息计算起始点、结束点或控制点。


我敢画一条弧线然后对其进行变换,因为椭圆只是一个被压扁的圆形。但是我太懒了,不想做所有需要将这个评论转化为答案所需的数学计算。 - kelin
3个回答

10

通过在椭圆绘制周围设置剪辑路径,您可能可以实现所需的效果。

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);

确切的裁剪半径并不重要。它需要足够大,以便仅在两端剪裁椭圆,并且不通过所需弧。


谢谢,非常顺利(我对Objective-C和绘图还很陌生)。 - user1331999

4

这个分类将会提供帮助。

#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

非常好的分类!!!只是为了使它更完美,第一个要添加到的线条点不应包括上面代码中的firstEllipsePoint起始点。所以for语句应该是for (CGFloat angle = startAngle + angleStep; angle < endAngle; angle += angleStep) { ... - Ge Liu
ellipsePointForAngle似乎不正确,或者存在与我使用的方法不同的差异。随着角度在椭圆周围增加,它看起来会稍微前后偏移。这部分是由于未包括椭圆的宽度和高度造成的,这很重要,并且与仅从角度找到圆上的点不同。 - Chewie The Chorkie

2

您需要使用addQuadCurveToPoint:controlPoint:方法,而不是bezierPathWithOvalInRect方法。

该方法的定义如下:

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

CGPoint是两个参数的输入参数。

在调用addQuadCurveToPoint之前,您还需要使用moveToPoint:设置起始点,它将作为当前点(正如您所看到的,在该方法中没有起始点作为参数)。

在您的情况下,您将有:

1> x,y作为您的起始点

2> x + width和y + height作为终点

这里不需要角度,或者您可以实现自己的逻辑来使用角度。我上传了一张图片以使事情更清晰。

这是描述起始点、终点和控制点的图像


2
一个二次贝塞尔曲线不行。也许你可以计算控制点,使得用给定属性绘制椭圆的立方贝塞尔曲线。如果没有将给定信息转换为贝塞尔曲线所需的数学知识,这并不能回答问题。 - thundersteele
其实,我认为如果你想显式地绘制路径(即不使用剪辑),你需要用参数方程表示椭圆。然后,你将不得不创建一堆短线段(二次、三次或者线性的)来近似表示椭圆。但是,使用剪辑会更好些。 - samson
1
这是一个很棒的Techotopia网站:http://www.techotopia.com/index.php/An_iPhone_Graphics_Drawing_Tutorial_using_Quartz_2D#Drawing_an_Ellipse_or_Circle - Aardvark
以下是Swift语言的解决方案...http://www.techotopia.com/index.php/An_iOS_8_Swift_Graphics_Tutorial_using_Core_Graphics_and_Core_Image - Dasoga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接