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.
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.
Formula for Linear Interpolation showing the value of
L0
at different values oft
.
To understand what is going on visually, take a look at the image below.
An image showing linear interpolation at different values of
t
. Note:P0
is the start-point,P1
is the end-point andL0
is the linear interpolation att
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.
To understand visually, below is an image showing the magic of the Quadratic Bezier Curve
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.
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
.
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.
Formula for a cubic bezier curve.
To understand this formula visually, look at the image below.
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.
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.
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.
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!!