Drawing Bezier curves and Splines with CustomPaint flutter

Drawing Bezier curves and Splines with CustomPaint flutter

Understand the maths behind bezier curves and splines

Of recent, I implemented an animation that required me to draw bezier curves and splines. I used methods like Path.quadraticBezierTo(x1, y1, x2, y2), Path.conicTo(x1, y1, x2, y2, w) and Path.cubicTo(x1, y1, x2, y2, x3, y3). I also used a class you might not have heard about CatmullRolSpline. Below is the animation I did, to check out the code click here.

ezgif.com-gif-maker.gif

The methods listed above are all used to draw Bezier curves, but what are they and how do they work? How can you draw Bezier curves or Splines on flutter's CustomPainter canvas? If you're curious about this follow me along as we answer all these questions. But first let's understand some concepts, if you know about Flutter's CustomPaint/CustomPainter you can choose to skip this section.

While a non-flutter developer can read this article, when it comes to the coding section this article assumes the reader has little or more familiarity with the Flutter framework, specifically CustomPainter.

What is canvas in flutter?

Picture a scenario with you being the artist, you have this insane piece of art you can't wait to paint, you have your set of brushes, paints, and pencils ready, but without a surface to paint, you can't proceed with the art. Think of canvas as that surface where you get to paint on.

What is Path in flutter

The Path is a collection of drawn elements. It functions as a pencil you use to draw continuous strokes.

What is Paint in flutter

As the name implies, Paint object is used to apply a style to your object drawn on canvas, style, in this case, refers to, but is not limited to applying colors, shaders, blend mode, stroke width e.t.c, as we proceed you'll notice that most Canvas methods take in paint as an argument.

What is a Bezier curve

A bezier curve is basically an interpolation between control points, in this article, we will be looking at the Linear, Quadratic, and Cubic bezier curves.

Linear Bezier curve

Remember we said the Bezier curve is just an interpolation between control points, Hence we can say that the Linear Bezier curve is just an interpolation between two points.

What does interpolation mean? Interpolation is the process of finding a value between two points on a line or curve. In this case, that "value" depends on a factor t which varies between 0 and 1. To get that "value" at t we'll pass it through a maths function as we'll see soon.

Linear Bezier curve is the simplest form of a Bezier curve, hence why we are starting with this. When you gain an understanding of how a Linear Bezier curve works it solidifies your knowledge on how Bezier curves work generally.

Screenshot 2022-01-18 at 20.58.50.png

Formula for Linear Interpolation showing the value of L0 at different values of t.

To understand what is going on visually, take a look at the image below.

linear interpolation (1).png

An image showing linear interpolation at different values of t. Note:P0 is the start-point, P1 is the end-point and L0 is the linear interpolation at t value.

From both the formula and the image, we see that the impact on each point (P0 and P1) depends on t (moves from 0 to 1). You might be wondering how this makes sense with regards to Bezier curves? I promise it will make more sense soon as we consider Quadratic Bezier Curve, the takeaway point is that Linear Bezier Curve is an interpolation between two points (P0 and P1).

Quadratic Bezier Curve

Quadratic Bezier Curve is an interpolation between two Linear Interpolations. It's an addition of three more variables to the picture, P2 which is the third point, L1 which is the second Linear Interpolation, and Q0 which is a Linear Interpolation between L0 and L1. By the end of this section, you'll come to appreciate the beauty of the Linear Interpolation formula.

But first, for the sake of the Math geeks We'll look at the formula for this type of curve first.

Screenshot 2022-01-19 at 00.19.11.png

To understand visually, below is an image showing the magic of the Quadratic Bezier Curve

quadratic interpolation.png

Image showing the Quadratic Bezier curve at specific values of t

I must admit, a lot is going on in the picture above (lol.. just wait until you see cubic-bezier curve). Do not fret if you don't grasp the image above, I'll walk you through it. Basically what is happening is this:

  • The value of t is incrementing from 0 to 1
  • As the value t increments, L0 moves from P0 to P1
  • At the same time, L1 moves from P1 to P2
  • At the same time, Q0 (the blue dot) moves from L0 to L1
  • While Q0 (the blue dot) is moving, its path forms a curve.

Fun fact: A cool French Engineer named Pierre Bézier invented this cool formula, hence the term Bezier curve was named after him.

Time to write some code !!! Let's draw a Quadratic Curve with flutter's CustomPainter.

import 'package:flutter/material.dart';

class QuadraticBezierCurvePainter extends CustomPainter {
   const QuadraticBezierCurvePainter();

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPaint(Paint()..color = Colors.white);

    Path bezierPath = Path()
      ..moveTo(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 =
          LinearGradient(colors: [Colors.purple[400]!, Colors.teal[400]!])
              .createShader(Offset(0, size.height * 0.8) & size);

    canvas.drawPath(bezierPath, bezierPaint);
  }

  @override
  bool shouldRepaint(QuadraticBezierCurvePainter oldDelegate) => false;
}

The quadraticBezierTo(x1, y1, x2, y2) method draws a quadratic bezier curve from the previous point in Path to the x2, y2 point, using x1, y1 as the control point. When you run that, the image below is what you get.

Screenshot 2022-01-20 at 18.59.36.png

Side note, you can add gradients to a drawn object by using Shader.

Cubic Bezier Curve

A Cubic Bezier Curve is an interpolation between two Quadratic Bezier Curves. We'll introduce three new variables, they are: P3 the fourth point, L2 which is the linear interpolation between P2 and P3, Q1 which is an interpolation between L1 and L2 and finally C0 which is the interpolation between Q0 and Q1.

tom_tired_emoji.gif

Just when you thought you've seen it all for bezier curves, then boom Cubic Bezier Curves comes

Not to worry, it's not as complicated as it sounds, it's the same principle with the Linear and Quadratic Bezier Curves. Let's take a look at the formula for a Cubic Bezier Curve.

Screenshot 2022-01-19 at 15.58.51.png

Formula for a cubic bezier curve.

To understand this formula visually, look at the image below.

cubic bezier curve (1).png

An image showing how the Cubic Bezier Curve is drawn at a specific value of t , Please zoom in to see clearly what is happening

The above image of a Cubic Bezier Curve is even scarier, but it's not as scary as it looks, what's happening is this:

As the value of t increments from 0 to 1:

  • L0 moves from P0 to P1 .
  • L1 moves from P1 to P2 .
  • L2 moves from P2 to P3 .
  • Q0 moves from L0 to L1 .
  • Q1 moves from L1 to L2 .
  • C0 (the blue dot) moves from Q0 to Q1 . Note as the blue dot moves, it draws a Bezier curve along its path

That was a lot to process, but it's time to draw a Cubic Bezier Curve with flutter. Below is the code.

import 'package:flutter/material.dart';

class CubicBezierCurvePainter extends CustomPainter {
  const CubicBezierCurvePainter();

  @override
  void paint(Canvas canvas, Size size) {

   /// Paints the whole canvas white
    canvas.drawPaint(Paint()..color = Colors.white);

    /// Translate method shifts the coordinate of the canvas, in this case we are shifting
    /// the point of origin to the top-center of  the canvas
    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 = LinearGradient(colors: [
        Colors.purple[400]!,
        Colors.teal[400]!,
      ]).createShader(Offset(-width, size.height) & size);

    canvas.drawPath(bezierPath, bezierPaint);
  }

  @override
  bool shouldRepaint(CubicBezierCurvePainter oldDelegate) => false;
}

This is the result you get below.

Screenshot 2022-01-20 at 19.14.08.png

What is CatmulRomSpline

Before talking about CatmulRomSpline we need to talk about what Splines are.

A Spline is a piece wise function that interpolates a set of nodes. What the heck is piece wise function? A piece wise function is function that behaves differently or rather computes a different result based on the input x.

For a programmer, think of piece wise math function as a function that accepts a parameter x, the function returns a value based on conditions set (if-else statements). Take a look at the code below.

import 'dart:math' as math;

num pieceWiseFunction(int x) {
  if (x < 2) {
    return math.pow(x, 2);
  } else if (x == 2) {
    return 6;
  } else if (x > 2 && x <= 6) {
    return 10 - x;
  }

  return 0;
}

The above example is gotten from this site mathsisfun.com/sets/functions-piecewise.html (check for more examples)

Catmull-RomSpline is a 2D Spline that passes through its control points smoothly.

Catmull-Rom_Spline.png

A Catmull-Rom Spline with four control points PO, P1, P2, P3. Source: Wikipedia

To draw a CatmulRomSpline in flutter, a list of Offset is passed to the CatmulRomSpline constructor, calling the method generateSamples() returns a list of values (Offset) that smoothly passes these control points. Below is a code example to draw Splines.

import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';

class SplinePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPaint(Paint()..color = Colors.white);

    const controlWidthSingle = 50;
    final random = math.Random();

    /// This method generates control points, the x = 50*index(+1)
    /// the y is set to random values between half of the screen and bottom of the screen
    final controlPoints = List.generate(
      size.width ~/ controlWidthSingle,
      (index) => Offset(
        controlWidthSingle * (index + 1),
        random.nextDouble() * (size.height - size.height / 2) + size.height / 2,
      ),
    ).toList();
    final spline = CatmullRomSpline(controlPoints);

    final bezierPaint = Paint()
      // set the edges of stroke to be rounded
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 12
      // apply a gradient
      ..shader = const LinearGradient(colors: [
        Colors.purple,
        Colors.teal,
      ]).createShader(Offset(0, size.height) & size);

    // This method accepts a list of offsets and draws points for all offset
    canvas.drawPoints(
      PointMode.points,
      spline.generateSamples().map((e) => e.value).toList(),
      bezierPaint,
    );
  }

  @override
  bool shouldRepaint(SplinePainter oldDelegate) => false;
}

The result of the code will vary, but it looks somewhat like this depending on the random value generated.

Screenshot 2022-01-21 at 00.53.51.png

In conclusion, you've learnt Linear, Quadratic, and Cubic Bezier curves. Linear Bezier is an interpolation between two points, Quadratic Bezier is an interpolation between 2 Linear interpolations and Cubic Bezier is an interpolation between 2 Quadratic interpolations. You drew your own curves with flutter CustomPainter. You got to understand what Splines are and how to draw one in flutter.

If you found this helpful share, do well to leave a comment for any queries or corrections, i appreciate your feedback.

For reaching this far, here's a meme for you! Cheers!!

meme.jpeg