Quartz 2D 贝塞尔曲线

1、贝塞尔曲线

  • 贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如 PhotoShop 等。在 Flash4 中还没有完整的曲线工具,而在 Flash5 里面已经提供出贝塞尔曲线工具。

  • 二阶贝塞尔曲线示意图

  • 三阶贝塞尔曲线示意图

  • 贝塞尔路径(UIBezierPath)是 iOS UIKit 框架中对 Quartz 2D 绘图的封装。实际操作起来,使用贝塞尔路径,更为方便。用法与 CGContextRef 类似,但是 OC 对其进行了封装,更加面向对象。

  • 贝塞尔路径常用的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 设置起始点
    - (void)moveToPoint:(CGPoint)point;

    // 添加直线到一点
    - (void)addLineToPoint:(CGPoint)point;

    // 封闭闭路径
    - (void)closePath;

    // 返回一个描述椭圆的路径
    + (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect;

    // 贝塞尔曲线
    - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

    // 三次贝塞尔曲线
    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;

    // 绘制圆弧
    - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;

2、基本图形的绘制

3、二三阶贝塞尔曲线示例

3.1 二阶贝塞尔曲线示例

  • QBezierPathView.h

    1
    2
    3
    @interface QBezierPathView : UIView

    @end
  • QBezierPathView.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    @interface QBezierPathView ()

    /// 路径
    @property (nonatomic, strong) UIBezierPath *path;

    /// 起始点
    @property (nonatomic, assign) CGPoint startP;

    /// 终止点
    @property (nonatomic, assign) CGPoint endP;

    /// 控制点
    @property (nonatomic, assign) CGPoint controlP;

    /// 线的颜色
    @property (nonatomic, strong) UIColor *pathColor;

    /// 线的宽度
    @property (nonatomic, assign) CGFloat pathWidth;

    /// 当前触摸的点
    @property (nonatomic, assign) NSUInteger currentTouchP;

    @end

    @implementation QBezierPathView

    /// 初始化
    - (instancetype)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {

    // 设置初始值

    self.startP = CGPointMake(20, 300);
    self.endP = CGPointMake(250, 300);
    self.controlP = CGPointMake(100, 100);

    self.pathColor = [UIColor redColor];
    self.pathWidth = 2;
    }
    return self;
    }

    /// 绘制二阶贝塞尔曲线
    - (void)drawRect:(CGRect)rect {

    // 绘制贝塞尔曲线

    self.path = [UIBezierPath bezierPath];
    [self.path moveToPoint:self.startP];

    [self.path addQuadCurveToPoint:self.endP controlPoint:self.controlP];

    self.path.lineWidth = self.pathWidth;
    [self.pathColor setStroke];
    [self.path stroke];

    // 绘制辅助线

    self.path = [UIBezierPath bezierPath];

    self.path.lineWidth = 1;
    [[UIColor grayColor] setStroke];

    CGFloat lengths[] = {5};
    [self.path setLineDash:lengths count:1 phase:1];

    [self.path moveToPoint:self.controlP];
    [self.path addLineToPoint:self.startP];
    [self.path stroke];

    [self.path moveToPoint:self.controlP];
    [self.path addLineToPoint:self.endP];
    [self.path stroke];

    // 绘制辅助点及信息

    self.path = [UIBezierPath bezierPathWithArcCenter:self.startP radius:4 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor blackColor] setStroke];
    [self.path fill];

    self.path = [UIBezierPath bezierPathWithArcCenter:self.endP radius:4 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor blackColor] setStroke];
    [self.path fill];

    self.path = [UIBezierPath bezierPathWithArcCenter:self.controlP radius:3 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    self.path.lineWidth = 2;
    [[UIColor blackColor] setStroke];
    [self.path stroke];

    CGRect startMsgRect = CGRectMake(self.startP.x + 8, self.startP.y - 7, 50, 20);
    [@"起始点" drawInRect:startMsgRect withAttributes:nil];

    CGRect endMsgRect = CGRectMake(self.endP.x + 8, self.endP.y - 7, 50, 20);
    [@"终止点" drawInRect:endMsgRect withAttributes:nil];

    CGRect control1MsgRect = CGRectMake(self.controlP.x + 8, self.controlP.y - 7, 50, 20);
    [@"控制点" drawInRect:control1MsgRect withAttributes:nil];
    }

    /// 触摸开始
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸点位置
    CGPoint startPoint = [touches.anyObject locationInView:self];

    CGRect startR = CGRectMake(self.startP.x - 4, self.startP.y - 4, 8, 8);
    CGRect endR = CGRectMake(self.endP.x - 4, self.endP.y - 4, 8, 8);
    CGRect controlR = CGRectMake(self.controlP.x - 4, self.controlP.y - 4, 8, 8);

    // 判断当前触摸点
    if (CGRectContainsPoint(startR, startPoint)) {
    self.currentTouchP = 1;
    } else if (CGRectContainsPoint(endR, startPoint)) {
    self.currentTouchP = 2;
    } else if (CGRectContainsPoint(controlR, startPoint)) {
    self.currentTouchP = 3;
    }
    }

    /// 触摸移动
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸点位置
    CGPoint touchPoint = [touches.anyObject locationInView:self];

    // 限制触摸点的边界

    if (touchPoint.x < 0) {
    touchPoint.x = 0;
    }

    if (touchPoint.x > self.bounds.size.width) {
    touchPoint.x = self.bounds.size.width;
    }

    if (touchPoint.y < 0) {
    touchPoint.y = 0;
    }

    if (touchPoint.y > self.bounds.size.height) {
    touchPoint.y = self.bounds.size.height;
    }

    // 设置当前触摸点的值
    switch (self.currentTouchP) {
    case 1:
    self.startP = touchPoint;
    break;

    case 2:
    self.endP = touchPoint;
    break;

    case 3:
    self.controlP = touchPoint;
    break;

    default:
    break;
    }

    // 刷新
    [self setNeedsDisplay];
    }

    /// 触摸结束
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 释放之前的触摸点
    self.currentTouchP = 0;
    }

    /// 触摸取消
    - (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event {
    [self touchesEnded:touches withEvent:event];
    }

    @end
  • ViewController.m

    1
    2
    3
    4
    5
    6
    7
    8
    CGRect frame = CGRectMake(20, 50, self.view.bounds.size.width - 40, 400);

    QBezierPathView *pathView = [[QBezierPathView alloc] initWithFrame:frame];

    pathView.backgroundColor = [UIColor whiteColor];
    pathView.layer.borderWidth = 1;

    [self.view addSubview:pathView];
  • 效果

3.2 三阶贝塞尔曲线示例

  • QBezierPathView.h

    1
    2
    3
    @interface QBezierPathView : UIView

    @end
  • QBezierPathView.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    @interface QBezierPathView : UIView
    @interface QBezierPathView ()

    /// 路径
    @property (nonatomic, strong) UIBezierPath *path;

    /// 起始点
    @property (nonatomic, assign) CGPoint startP;

    /// 终止点
    @property (nonatomic, assign) CGPoint endP;

    /// 控制点
    @property (nonatomic, assign) CGPoint controlP1;
    @property (nonatomic, assign) CGPoint controlP2;

    /// 线的颜色
    @property (nonatomic, strong) UIColor *pathColor;

    /// 线的宽度
    @property (nonatomic, assign) CGFloat pathWidth;

    /// 当前触摸的点
    @property (nonatomic, assign) NSUInteger currentTouchP;

    @end

    @implementation QBezierPathView

    /// 初始化
    - (instancetype)initWithFrame:(CGRect)frame {

    if (self = [super initWithFrame:frame]) {

    // 设置初始值

    self.startP = CGPointMake(20, 300);
    self.endP = CGPointMake(250, 300);
    self.controlP1 = CGPointMake(100, 100);
    self.controlP2 = CGPointMake(200, 350);

    self.pathColor = [UIColor redColor];
    self.pathWidth = 2;
    }
    return self;
    }

    /// 绘制二阶贝塞尔曲线
    - (void)drawRect:(CGRect)rect {

    // 绘制贝塞尔曲线

    self.path = [UIBezierPath bezierPath];
    [self.path moveToPoint:self.startP];

    [self.path addCurveToPoint:self.endP controlPoint1:self.controlP1 controlPoint2:self.controlP2];

    self.path.lineWidth = self.pathWidth;
    [self.pathColor setStroke];
    [self.path stroke];

    // 绘制辅助线

    self.path = [UIBezierPath bezierPath];

    self.path.lineWidth = 1;
    [[UIColor grayColor] setStroke];

    CGFloat lengths[] = {5};
    [self.path setLineDash:lengths count:1 phase:1];

    [self.path moveToPoint:self.controlP1];
    [self.path addLineToPoint:self.startP];
    [self.path stroke];

    [self.path moveToPoint:self.controlP1];
    [self.path addLineToPoint:self.controlP2];
    [self.path stroke];

    [self.path moveToPoint:self.controlP2];
    [self.path addLineToPoint:self.endP];
    [self.path stroke];

    // 绘制辅助点及信息

    self.path = [UIBezierPath bezierPathWithArcCenter:self.startP radius:4 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor blackColor] setStroke];
    [self.path fill];

    self.path = [UIBezierPath bezierPathWithArcCenter:self.endP radius:4 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    [[UIColor blackColor] setStroke];
    [self.path fill];

    self.path = [UIBezierPath bezierPathWithArcCenter:self.controlP1 radius:3 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    self.path.lineWidth = 2;
    [[UIColor blackColor] setStroke];
    [self.path stroke];

    self.path = [UIBezierPath bezierPathWithArcCenter:self.controlP2 radius:3 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    self.path.lineWidth = 2;
    [[UIColor blackColor] setStroke];
    [self.path stroke];

    CGRect startMsgRect = CGRectMake(self.startP.x + 8, self.startP.y - 7, 50, 20);
    [@"起始点" drawInRect:startMsgRect withAttributes:nil];

    CGRect endMsgRect = CGRectMake(self.endP.x + 8, self.endP.y - 7, 50, 20);
    [@"终止点" drawInRect:endMsgRect withAttributes:nil];

    CGRect control1MsgRect = CGRectMake(self.controlP1.x + 8, self.controlP1.y - 7, 50, 20);
    [@"控制点1" drawInRect:control1MsgRect withAttributes:nil];

    CGRect control2MsgRect = CGRectMake(self.controlP2.x + 8, self.controlP2.y - 7, 50, 20);
    [@"控制点2" drawInRect:control2MsgRect withAttributes:nil];
    }

    /// 触摸开始
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸点位置
    CGPoint startPoint = [touches.anyObject locationInView:self];

    CGRect startR = CGRectMake(self.startP.x - 4, self.startP.y - 4, 8, 8);
    CGRect endR = CGRectMake(self.endP.x - 4, self.endP.y - 4, 8, 8);
    CGRect controlR1 = CGRectMake(self.controlP1.x - 4, self.controlP1.y - 4, 8, 8);
    CGRect controlR2 = CGRectMake(self.controlP2.x - 4, self.controlP2.y - 4, 8, 8);

    // 判断当前触摸点
    if (CGRectContainsPoint(startR, startPoint)) {
    self.currentTouchP = 1;
    } else if (CGRectContainsPoint(endR, startPoint)) {
    self.currentTouchP = 2;
    } else if (CGRectContainsPoint(controlR1, startPoint)) {
    self.currentTouchP = 3;
    } else if (CGRectContainsPoint(controlR2, startPoint)) {
    self.currentTouchP = 4;
    }
    }

    /// 触摸移动
    - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 获取触摸点位置
    CGPoint touchPoint = [touches.anyObject locationInView:self];

    // 限制触摸点的边界

    if (touchPoint.x < 0) {
    touchPoint.x = 0;
    }

    if (touchPoint.x > self.bounds.size.width) {
    touchPoint.x = self.bounds.size.width;
    }

    if (touchPoint.y < 0) {
    touchPoint.y = 0;
    }

    if (touchPoint.y > self.bounds.size.height) {
    touchPoint.y = self.bounds.size.height;
    }

    // 设置当前触摸点的值
    switch (self.currentTouchP) {
    case 1:
    self.startP = touchPoint;
    break;

    case 2:
    self.endP = touchPoint;
    break;

    case 3:
    self.controlP1 = touchPoint;
    break;

    case 4:
    self.controlP2 = touchPoint;
    break;

    default:
    break;
    }

    // 刷新
    [self setNeedsDisplay];
    }

    /// 触摸结束
    - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {

    // 释放之前的触摸点
    self.currentTouchP = 0;
    }

    /// 触摸取消
    - (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event {
    [self touchesEnded:touches withEvent:event];
    }

    @end
  • ViewController.m

    1
    2
    3
    4
    5
    6
    7
    8
    CGRect frame = CGRectMake(20, 50, self.view.bounds.size.width - 40, 400);

    QBezierPathView *pathView = [[QBezierPathView alloc] initWithFrame:frame];

    pathView.backgroundColor = [UIColor whiteColor];
    pathView.layer.borderWidth = 1;

    [self.view addSubview:pathView];
  • 效果

文章目录
  1. 1. 1、贝塞尔曲线
  2. 2. 2、基本图形的绘制
  3. 3. 3、二三阶贝塞尔曲线示例
    1. 3.1. 3.1 二阶贝塞尔曲线示例
    2. 3.2. 3.2 三阶贝塞尔曲线示例
隐藏目录