什么是贝塞尔曲线
贝塞尔曲线基本上是控制点之间的插值,在本文中,我们将研究线性、二次和三次贝塞尔曲线。
线性贝塞尔曲线
贝塞尔曲线只是控制点之间的插值,因此我们可以说线性贝塞尔曲线只是两点之间的插值。
插值是什么意思?插值是在直线或曲线上的两点之间找到值的过程。在这种情况下,该“值”取决于一个在 0 和 1 之间变化的因子t。我们可以将t传到数学函数中,从而得到它的值。
线性贝塞尔曲线是贝塞尔曲线的最简单形式,以下的线性插值公式显示了L0在不同值时的值t。
我们将上面值的变化图解,如下:(注意:P0是起点,P1是终点,是值L0处的线性插值t。)
从公式和图像中,我们看到对每个点 (P0和P1) 的影响取决于t (moves from 0 to 1)。
二次贝塞尔曲线
二次贝塞尔曲线是两个线性插值之间的插值。 看以下方程
图解如下: 图像显示特定t值处的二次贝塞尔曲线。
- t的值从 0 递增到 1
- 随着值t的增加,L0从P0移动到P1
- 同时,L1从P1移动到P2
- 同时,Q0(蓝点)从L0移动到L1
- 当Q0(蓝点)移动时,其路径形成一条曲线。
接下来我们使用flutter的CustomPainter
画一个二次曲线。
知识点:quadraticBezierTo(x1, y1, x2, y2)
绘制一条二次贝塞尔曲线。
代码如下:
import 'package:flutter/material.dart';
class BezierCurvePage extends StatefulWidget {
const BezierCurvePage({Key? key}) : super(key: key);
@override
State<BezierCurvePage> createState() => _BezierCurvePageState();
}
class _BezierCurvePageState extends State<BezierCurvePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('贝塞尔曲线')),
body: Center(
child: CustomPaint(
painter: BezierCurvePainter(),
child: Container(
),
),
),
);
}
}
/// 二次曲线
class BezierCurvePainter extends CustomPainter {
const BezierCurvePainter();
@override
void paint(Canvas canvas, Size size) {
// 将整个画布的颜色涂成白色
Paint paint = Paint()
..color = Colors.white;
canvas.drawPaint(paint);
// 画贝塞尔曲线
Path bezierPath = Path()
..moveTo(0, size.height)//移动起点到(0, size.height)
..lineTo(0, size.height*0.8)//画线
..quadraticBezierTo(
size.width / 2,
size.height * 0.6,
size.width,
size.height * 0.8
)
..lineTo(size.width, size.height);
final bezierPaint = Paint()
// 添加渐变色
..shader = const LinearGradient(
colors: [Colors.blue,Colors.green]
).createShader(Offset(0,size.height * 0.8) & size);
canvas.drawPath(bezierPath, bezierPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
效果:
三次贝塞尔曲线
三次贝塞尔曲线是两条二次贝塞尔曲线之间的插值。我们将引入三个新的变量:L2,Q1,C0和第四个点P3。
- P3 是第四个点
- L2是P2和P3之间的线性插值
- Q1是L1和L2之间的插值
- C0,是Q0和Q1之间的插值
以下是三次贝塞尔曲线的公式:
图解:
从图上可知随着t的值从 0 增加到 1的变化:当蓝点移动时,它会沿其路径绘制一条贝塞尔曲线。
- L0从P0移动到P1
- L1从P1移动到P2
- L2从P2移动到P3
- Q0从L0移动到L1
- Q1从L1移动到L2
- C0(蓝点)从Q0移动到Q1
下面我们用flutter 绘制三次贝塞尔曲线。
应用cubicTo(x1, y1, x2, y2, x3, y3)
/// 三次曲线
class CubicBezierCurvePainter extends CustomPainter {
const CubicBezierCurvePainter();
@override
void paint(Canvas canvas, Size size) {
//画布背景色
Paint paint = Paint()..color = Colors.white;
canvas.drawPaint(paint);
// 使用Translate方法移动画布的坐标
// 起始点是在画布的顶点正中心
canvas.translate(size.width / 2, 0);
final width = size.width / 2;
Path bezierPath = Path()
..moveTo(-width, size.height)
..lineTo(-width, size.height * 0.6)
..cubicTo(
-width * 0.2,
size.height * 0.4,
0,
size.height * 0.8,
width,
size.height * 0.6
)
..lineTo(width, size.height);
final bezierPaint = Paint()
..shader = const LinearGradient(
colors: [Colors.blue,Colors.green]
).createShader(Offset(-width,size.height) & size);
canvas.drawPath(bezierPath, bezierPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
效果:
什么是 CatmulRomSpline
Catmull-RomSpline是一种 2D 样条曲线,可以平滑地通过其控制点。如下图是具有四个控制点 PO、P1、P2、P3 的 Catmull-Rom 样条曲线。
要在 flutter 中绘制CatmulRomSpline,将一个Offset列表传递给CatmulRomSpline
构造函数,调用方法generateSamples()
返回一个Offset值列表,平滑地通过这些控制点。
/// CatmulRomSpline
class SplinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..color = Colors.white;
canvas.drawPaint(paint);
//控制点宽
const controlWidthSingle = 50;
final random = math.Random();
//生成控制点
final controlPoints = List.generate(size.width ~/ controlWidthSingle, (index) {
return Offset(
controlWidthSingle * (index + 1),
random.nextDouble() * (size.height - size.height / 2) + size.height /2
);
}).toList();
//返回一个Offset值列表
final spline = CatmullRomSpline(controlPoints);
final bezierPaint = Paint()
..strokeCap = StrokeCap.round
..strokeWidth = 10
..shader = const LinearGradient(
colors: [Colors.blue,Colors.green]
).createShader(Offset(0, size.height) & size);
//绘制所有offset点
canvas.drawPoints(
PointMode.points,
spline.generateSamples().map((e) => e.value).toList(),
bezierPaint
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
效果:
评论(0)