首页
Preview

Flutter CustomPaint 绘制贝塞尔曲线和样条曲线(CatmulRomSpline)

什么是贝塞尔曲线

贝塞尔曲线基本上是控制点之间的插值,在本文中,我们将研究线性、二次和三次贝塞尔曲线。

线性贝塞尔曲线

贝塞尔曲线只是控制点之间的插值,因此我们可以说线性贝塞尔曲线只是两点之间的插值。

插值是什么意思?插值是在直线或曲线上的两点之间找到值的过程。在这种情况下,该“值”取决于一个在 0 和 1 之间变化的因子t。我们可以将t传到数学函数中,从而得到它的值。

线性贝塞尔曲线是贝塞尔曲线的最简单形式,以下的线性插值公式显示了L0在不同值时的值t。

LUlt2gWCd.avif

我们将上面值的变化图解,如下:(注意:P0是起点,P1是终点,是值L0处的线性插值t。)

A-ycaAJ4oU.avif 从公式和图像中,我们看到对每个点 (P0和P1) 的影响取决于t (moves from 0 to 1)。

二次贝塞尔曲线

二次贝塞尔曲线是两个线性插值之间的插值。 看以下方程

0-6sPMS_h.avif 图解如下: 图像显示特定t值处的二次贝塞尔曲线。

2k4lntIbl.avif

  • 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;
  }

}

效果:

Simulator Screen Shot - iPhone 14 Pro Max - 2023-01-16 at 23.30.18.png

三次贝塞尔曲线

三次贝塞尔曲线是两条二次贝塞尔曲线之间的插值。我们将引入三个新的变量:L2,Q1,C0和第四个点P3。

  • P3 是第四个点
  • L2是P2和P3之间的线性插值
  • Q1是L1和L2之间的插值
  • C0,是Q0和Q1之间的插值

以下是三次贝塞尔曲线的公式:

POMxkRJ2cc.avif 图解:

fxFIGt97k.avif 从图上可知随着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;
  }

}

效果:

Simulator Screen Shot - iPhone 14 Pro Max - 2023-01-17 at 11.33.58.png

什么是 CatmulRomSpline

Catmull-RomSpline是一种 2D 样条曲线,可以平滑地通过其控制点。如下图是具有四个控制点 PO、P1、P2、P3 的 Catmull-Rom 样条曲线。

ysC6bI_Ou.avif

要在 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;
  }

}

效果:

Simulator Screen Shot - iPhone 14 Pro Max - 2023-01-17 at 12.02.09.png

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
励志猿
励志每天写一篇文章,有价值的文章,提升自我!

评论(0)

添加评论