运用贝塞尔曲线绘制笔锋效果

2,070 阅读5分钟

“笔锋”是指用户通过手指、或者鼠标工具在屏幕上拖动模拟真实场景下用笔写文字的效果,文字的书写过程是会根据我们书写的速度实时变化的,通过笔锋算法我们能够模拟出真实场景下书写文字时的线条变化以及结束时的带笔效果。

那么我们如何来绘制笔锋线条呢?

本文将通过介绍贝塞尔曲线的基本概念以及笔锋轨迹算法,来给大家详细解答如何运用贝塞尔曲线实现绘制笔锋的功能。

一、关于贝塞尔曲线

首先,什么是贝塞尔曲线呢?

维基百科上介绍,贝塞尔曲线于1962年,由法国工程师皮埃尔·贝兹(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。贝塞尔曲线最初由保尔·德·卡斯特里奥于1959年运用德卡斯特里奥算法开发,以稳定数值的方法求出贝塞尔曲线。

数学数值分析领域中,贝塞尔曲线(英语:Bézier curve)是计算机图形学中相当重要的参数曲线。更高维度的广泛化贝塞尔曲线就称作贝兹曲面,其中贝兹三角是一种特殊的实例。

二、UIBezierPath 的基本使用

接下来为大家讲解一下如何生成一条贝塞尔曲线路径以及笔锋的绘制原理。

这里我们通过 iOS 的 UIBezierPath 类来举例说明:

UIBezierPath 是 CGPathRef 数据类型的封装。path 如果是基于矢量形状的,都用直线和曲线段去创建。

如下 UIBezierPath 的API 我们可以看到,系统已提供了丰富的接口供我们使用来绘制一条贝塞尔轨迹:

UIKIT_EXTERN API_AVAILABLE(ios(3.2)) @interface UIBezierPath : NSObject<NSCopying, NSSecureCoding>


+ (instancetype)bezierPath;
+ (instancetype)bezierPathWithRect:(CGRect)rect;
+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius; // rounds all corners with the same horizontal and vertical radius
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;


- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;


// Returns an immutable CGPathRef which is only valid until the UIBezierPath is further mutated.
// Setting the path will create an immutable copy of the provided CGPathRef, so any further mutations on a provided CGMutablePathRef will be ignored.
@property(nonatomic) CGPathRef CGPath;
- (CGPathRef)CGPath NS_RETURNS_INNER_POINTER CF_RETURNS_NOT_RETAINED;

// Path construction
- (void)moveToPoint:(CGPoint)point;
- (void)addLineToPoint:(CGPoint)point;
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise API_AVAILABLE(ios(4.0));
- (void)closePath;

- (void)removeAllPoints;

// Appending paths
- (void)appendPath:(UIBezierPath *)bezierPath;

如何实例化一条 UIBezierPath 路径,下图举例了实现一条二阶贝塞尔曲线 Path:

CGFloat speedFactor//速度因子 用来配合距离求得速度 
CGFloat distance//两点间的距离 
CGFloat speed = distance ✖️ speedFactor;//求得速度

三、笔锋实现

通过上面的介绍我们已经了解了 UIBezierPath 的大致用法,以及如何生成一条贝塞尔路径出来,现在我们来介绍一下关于如何通过 UIBezierPath 来生成一条笔锋路径。

我们知道要实现笔锋需要根据绘制速度来实现路径的宽度变化,可以根据我们知道的距离/时间 可以得到速度,如下示例代码:

CGFloat speedFactor//速度因子 用来配合距离求得速度 
CGFloat distance//两点间的距离 
CGFloat speed = distance ✖️ speedFactor;//求得速度

通过上面求得的速度值,我们可以用来求取我们的路径当前的线宽 LineWidth(此处求线宽的函数就不做具体描述,这里可以根据具体的业务需求变化),我们拿到线宽就能够求得当前点的左右2个点 leftPoint 和 rightPoint。

如下图所示:

通过以上方式我们就能求得每一个点的左右2点了,因此屏幕每采集2个点就能够求的矩形的4个点,通过4个点连线得到一个矩形,然后通过矩形的拼接形成一条笔锋路径。

///构建矩形路径
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint:leftPoint1];
[bezierPath addLineToPoint:leftPoint2];
[bezierPath addLineToPoint:rightPoint2];
[bezierPath addLineToPoint:rightPoint1];
[bezierPath addLineToPoint:leftPoint1];
[bezierPath closePath];

现在假设屏幕采集到的点为 startPoint、point1、point2、endPoint,如何通过这四点绘制出一条笔锋路径呢?

如下图所示:

通过上图我们可以看出我们实际显示出来的 path 应该是 startPoint->leftPoint1->leftPoint2->endPoint->rightPoint2->rightPoint1->startPoint 这么一条 path,如果不加笔锋我们实际path应该是startPoint->point1->point2→endPoint.

轨迹优化

当然我们还能优化上图中的轨迹,让轨迹看起来更加的顺滑,我们可以使用二阶贝塞尔曲线来连接2点,轨迹看起来更加的平滑。

系统也提供了对应的 api 供我们使用:

///三阶贝塞尔
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
///二阶贝塞尔
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

通过上述的方法实现效果如下图所示:

 四、结语

本文对 iOS 中的 UIBezierPath 的用法及使用场景进行了基本的介绍,我们可以看到 UIBezierPath 提供了很多便捷的 API 方法,我们在实际的场景中如果熟练的运用这些 API ,结合我们自身的一些算法能够容易的绘制出各种图形。

56a453160411f1d5238ea7abf536e0f2.jpg