Realization of smooth curve of canvas Sketchpad

Time:2020-11-23

functional requirement

Project requirements: need to implement a small Sketchpad that can write freely

Simple implementation

For students who are familiar with canvas, this requirement is very simple, and the general logic is as follows:

1) Monitoring events pointerdown, pointermove, pointerup

2) Mark whether to drag and drop the drawing mode variable isdrawing, which is set to true in down event and false in up event

3) Use the line API to set the line style

In just a few dozen lines of code:

<!doctype html>
<html>

<head>
    <meta charset=utf-8>
    <style>
        canvas {
            border: 1px solid #ccc
        }

        body {
            margin: 0;
        }
    </style>
</head>

<body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;">
    <canvas id="c" width="1920" height="1080"></canvas>
    <script>
        var el = document.getElementById('c');
        var ctx = el.getContext('2d');
        //Set drawing line style
        ctx.strokeStyle = 'red';
        ctx.lineWidth = 1;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';
        Var isdrawing; // mark whether to draw
        //Store coordinate points
        let lastX, lastY;
        document.body.onpointerdown = function (e) {
            console.log('pointerdown');
            isDrawing = true;
            lastX = e.clientX;
            lastY = e.clientY;
        };
        document.body.onpointermove = function (e) {
            console.log('pointermove');
            if (isDrawing) {
                draw(e.clientX, e.clientY, lastX, lastY);
            }
            lastX = e.clientX, lastY = e.clientY;
        };
        document.body.onpointerup = function (e) {
            if (isDrawing) {
                draw(e.clientX, e.clientY, lastX, lastY);
            }
            lastX = e.clientX, lastY = e.clientY;
            isDrawing = false;
        };

        function draw(x, y, lastX, lastY) {
            ctx.beginPath();
            ctx.moveTo(lastX, lastY);
            ctx.lineTo(x, y);
            ctx.stroke();
        }
    </script>
</body>
</html>

The implementation effect is as follows:

The above is a simple implementation of the sketchpad function, if the user can not high requirements, but once encountered a little request users can not deliver this product, look carefully is the lineBroken line feelingToo strong.

Why do you have a broken line?

main cause:

The API method lineto we call is a two-point line, which is a straight line

The browser collects mouse event MouseMove with frequency. Not every pixel that the mouse moves through will trigger the event.

When the mouse moves faster, then the distance between the two points is farther, so the broken line feeling is more obvious.

How can I draw a smooth curve?

There are ready-made interfaces in the API provided by canvas. The interfaces of Bessel series can meet our requirements. Next, we will talk about drawing smooth curves with quadratic Bezier curves.

quadraticCurveTo(cpx,cpy,x,y)

The interface of quadratic Bezier curve needs four parameters. CPX and CPY are the control points of the curve, and X and y are the end points of the curve.

Someone asked, where is the starting point of that curve? In fact, the starting point of the curve depends on the previous operation state. It can be the position of moveto, the position of lineto, or the end of Bessel.

So how to call quadricccurveto and how to pass the parameters?

We need to find out the key position and tell you directly with examples

1) If we use the mouse to collect ABCDEF six points

2) Take the first three points ABC calculation, BC midpoint B1, with a as the starting point, B as the control point, B1 as the end point, then the use of quadric curveto can draw such a Bezier curve

3) Next, the midpoint C1 of CD is calculated, with B1 as the starting point, C as the control point and C1 as the end point. Then, such a Bessel curve can be drawn by using the quadric curveto

4) In this way, when the last point is reached, D1 is the starting point, e is the control point, and F is the end point.

Code transformation according to the algorithm

OK, we introduce the influence of the specific algorithm, and then use this algorithm to modify our previous code

<!doctype html>
<html>

<head>
    <meta charset=utf-8>
    <style>
        canvas {
            border: 1px solid #ccc
        }

        body {
            margin: 0;
        }
    </style>
</head>

<body style="overflow: hidden;background-color: rgb(250, 250, 250);touch-action: none;">
    <canvas id="c" width="1920" height="1080"></canvas>
    <script>
        var el = document.getElementById('c');
        var ctx = el.getContext('2d');
        //Set drawing line style
        ctx.strokeStyle = 'red';
        ctx.lineWidth = 1;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';
        Var isdrawing; // mark whether to draw
        //Store coordinate points
        let points = [];
        document.body.onpointerdown = function (e) {
            console.log('pointerdown');
            isDrawing = true;
            points.push({ x: e.clientX, y: e.clientY });
        };
        document.body.onpointermove = function (e) {
            console.log('pointermove');
            if (isDrawing) {
                draw(e.clientX, e.clientY);
            }

        };
        document.body.onpointerup = function (e) {
            if (isDrawing) {
                draw(e.clientX, e.clientY);
            }
            points = [];
            isDrawing = false;
        };

        function draw(mousex, mousey) {
            points.push({ x: mousex, y: mousey });
            ctx.beginPath();
            let x = (points[points.length - 2].x + points[points.length - 1].x) / 2,
                y = (points[points.length - 2].y + points[points.length - 1].y) / 2;
            if (points.length == 2) {
                ctx.moveTo(points[points.length - 2].x, points[points.length - 2].y);
                ctx.lineTo(x, y);
            } else {
                let lastX = (points[points.length - 3].x + points[points.length - 2].x) / 2,
                    lastY = (points[points.length - 3].y + points[points.length - 2].y) / 2;
                ctx.moveTo(lastX, lastY);
                ctx.quadraticCurveTo(points[points.length - 2].x, points[points.length - 2].y, x, y);
            }
            ctx.stroke();
            points.slice(0, 1);

        }
    </script>
</body>

</html>

On the basis of the original, we use an array of points to save the points passed by the mouse. According to the algorithm, we need at least three points to draw the Bezier curve, and maintain the points array during the drawing process.

The implementation effect is as follows, we can see a lot of smoothing!

Follow up articles:

To achieve the effect of crayon, to achieve the effect of brush tip, brush performance optimization

This article on the canvas small Sketchpad smooth curve implementation of the article introduced here, more related canvas smooth curve content, please search the previous articles of developeppaer or continue to browse the related articles below, I hope you can support developeppaer more in the future!