Canvas Advancement – How to draw a smooth curve?

Time:2019-8-14

Summary of Background

It is believed that when you study canvas or use canvas in project development, you should meet the requirement of realizing a writing Sketchpad widget.

Well, I believe it’s only a matter of dozens of lines of code for canvas to use more sophisticated children’s shoes. Here’s a simple example of demo:

<!DOCTYPE html>
<html>
<head>
    <title>Sketchpad demo</title>
    <style type="text/css">
        canvas {
            border: 1px blue solid; 
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="800" height="500"></canvas>
    <script type="text/javascript">
        let isDown = false;
        let beginPoint = null;
        const canvas = document.querySelector('#canvas');
        const ctx = canvas.getContext('2d');

        // Setting Line Colors
        ctx.strokeStyle = 'red';
        ctx.lineWidth = 1;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';

        canvas.addEventListener('mousedown', down, false);
        canvas.addEventListener('mousemove', move, false);
        canvas.addEventListener('mouseup', up, false);
        canvas.addEventListener('mouseout', up, false);

        function down(evt) {
            isDown = true;
            beginPoint = getPos(evt);
        }

        function move(evt) {
            if (!isDown) return;
            const endPoint = getPos(evt);
            drawLine(beginPoint, endPoint);
            beginPoint = endPoint;
        }

        function up(evt) {
            if (!isDown) return;
            
            const endPoint = getPos(evt);
            drawLine(beginPoint, endPoint);

            beginPoint = null;
            isDown = false;
        }

        function getPos(evt) {
            return {
                x: evt.clientX,
                y: evt.clientY
            }
        }

        function drawLine(beginPoint, endPoint) {
            ctx.beginPath();
            ctx.moveTo(beginPoint.x, beginPoint.y);
            ctx.lineTo(endPoint.x, endPoint.y);
            ctx.stroke();
            ctx.closePath();
        }
    </script>
</body>
</html>

Its implementation logic is also simple:

  1. We monitored three major events on the canvas canvas canvas:mousedownmouseupandmousemoveAt the same time, we also created aisDownVariables;
  2. When the user presses the mouse(mousedownWhen you start writing, you willisDownSet astruePut down the mouse(mouseup) When you do, set it to ____________falseThe advantage of this method is that it can judge whether the user is currently in the painting state.
  3. adoptmousemoveEvents continuously collect coordinate points of the mouse, if and only ifisDownbytruePass the current point through the canvaslineToThe method connects and draws the points in front of it.

Through the above steps we can achieve the basic Sketchpad function, but things are not so simple. Careful children’s shoes may find a very serious problem – the lines drawn in this way are jagged, not smooth enough, and the faster you draw, the stronger the sense of broken lines. As shown in the following figure:

Canvas Advancement - How to draw a smooth curve?

Why?

problem analysis

The main reasons for this phenomenon are:

  • We are based on canvas.lineToMethod Connecting points, connecting adjacent two points is a straight line, non-curve, so it is a broken line drawn by this way.
    Canvas Advancement - How to draw a smooth curve?
  • Restricted browser pairsmousemoveThe frequency of events is known to all.mousemoveThe browser collects the coordinates of the current mouse every little time, so the faster the mouse moves, the farther the two adjacent points are collected, so “the more obvious the sense of broken lines”;

How can we draw a smooth curve?

There are ways to draw a smooth curve.lineToIf you can’t depend on it, we can use another drawing API of canvas.——quadraticCurveTo It is used to draw quadratic Bessel curves.

quadratic Bezier curve

quadraticCurveTo(cp1x, cp1y, x, y)

callquadraticCurveToThe method requires four parameters.cp1xcp1yIt describes control points, andxyIt is the end of the curve:

Canvas Advancement - How to draw a smooth curve?

More detailed information to move to MDN

Since we are going to use the Bessel curve, it is clear that our data is not enough.To fully describe a quadratic Bessel curve, we need to: start point, control point and end point.How do these data come about?

There’s a very clever algorithm that can help us get this information.

Algorithms for Obtaining Quadratic Bessel Key Points

This algorithm is not difficult to understand. Let me give you an example.

  1. Suppose we collected six mouse coordinates in a painting, which areA, B, C, D, E, F
  2. Take the front one.A, B, CThree points, calculateBandCMidpointB1In order toAAs a starting point,BFor the control point,B1For the end, usequadraticCurveToDraw a line segment of quadratic Bessel curve.
    Canvas Advancement - How to draw a smooth curve?
  3. Next, calculate it.CandDThe midpoint of a pointC1In order toB1As a starting point,CFor control points,C1Continue to draw the curve for the end point.
    Canvas Advancement - How to draw a smooth curve?
  4. Continue drawing by analogy until the last point.FWhenDandEMidpointD1Starting withEFor the control point,FEnd the Bessel curve for the end point.
    Canvas Advancement - How to draw a smooth curve?

OK, that’s how the algorithm works. Then we upgrade the existing code based on the algorithm.

let isDown = false;
let points = [];
let beginPoint = null;
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');

// Setting Line Colors
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';

canvas.addEventListener('mousedown', down, false);
canvas.addEventListener('mousemove', move, false);
canvas.addEventListener('mouseup', up, false);
canvas.addEventListener('mouseout', up, false);

function down(evt) {
    isDown = true;
    const { x, y } = getPos(evt);
    points.push({x, y});
    beginPoint = {x, y};
}

function move(evt) {
    if (!isDown) return;

    const { x, y } = getPos(evt);
    points.push({x, y});

    if (points.length > 3) {
        const lastTwoPoints = points.slice(-2);
        const controlPoint = lastTwoPoints[0];
        const endPoint = {
            x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2,
            y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2,
        }
        drawLine(beginPoint, controlPoint, endPoint);
        beginPoint = endPoint;
    }
}

function up(evt) {
    if (!isDown) return;
    const { x, y } = getPos(evt);
    points.push({x, y});

    if (points.length > 3) {
        const lastTwoPoints = points.slice(-2);
        const controlPoint = lastTwoPoints[0];
        const endPoint = lastTwoPoints[1];
        drawLine(beginPoint, controlPoint, endPoint);
    }
    beginPoint = null;
    isDown = false;
    points = [];
}

function getPos(evt) {
    return {
        x: evt.clientX,
        y: evt.clientY
    }
}

function drawLine(beginPoint, controlPoint, endPoint) {
    ctx.beginPath();
    ctx.moveTo(beginPoint.x, beginPoint.y);
    ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
    ctx.stroke();
    ctx.closePath();
}

On the original basis, we created a variable.pointsPrior to storagemousemoveAccording to this algorithm, we can know that it takes at least three points to draw a quadratic Bessel curve, so we only have one point.pointsDrawing begins when the number of points in the plot is greater than 3. The next step is the same as the algorithm, which will not be discussed here.

After code updates, our curves have become much smoother, as shown in the following figure:

Canvas Advancement - How to draw a smooth curve?

This is the end of this article. I hope you have a good time painting on the canvas drawing board. ~See you next time.

Interested children’s shoes can be stamped here to pay attention to my blog, any fresh and interesting blog will be shared here for the first time.~