how to draw smooth curve through N points using javascript HTML5 canvas?

后端 未结 11 735
时光说笑
时光说笑 2020-11-22 16:40

For a drawing application, I\'m saving the mouse movement coordinates to an array then drawing them with lineTo. The resulting line is not smooth. How can I produce a sing

11条回答
  •  时光说笑
    2020-11-22 17:26

    A bit late, but for the record.

    You can achieve smooth lines by using cardinal splines (aka canonical spline) to draw smooth curves that goes through the points.

    I made this function for canvas - it's split into three function to increase versatility. The main wrapper function looks like this:

    function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {
    
        showPoints  = showPoints ? showPoints : false;
    
        ctx.beginPath();
    
        drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
    
        if (showPoints) {
            ctx.stroke();
            ctx.beginPath();
            for(var i=0;i

    To draw a curve have an array with x, y points in the order: x1,y1, x2,y2, ...xn,yn.

    Use it like this:

    var myPoints = [10,10, 40,30, 100,10]; //minimum two points
    var tension = 1;
    
    drawCurve(ctx, myPoints); //default tension=0.5
    drawCurve(ctx, myPoints, tension);
    

    The function above calls two sub-functions, one to calculate the smoothed points. This returns an array with new points - this is the core function which calculates the smoothed points:

    function getCurvePoints(pts, tension, isClosed, numOfSegments) {
    
        // use input value if provided, or use a default value   
        tension = (typeof tension != 'undefined') ? tension : 0.5;
        isClosed = isClosed ? isClosed : false;
        numOfSegments = numOfSegments ? numOfSegments : 16;
    
        var _pts = [], res = [],    // clone array
            x, y,           // our x,y coords
            t1x, t2x, t1y, t2y, // tension vectors
            c1, c2, c3, c4,     // cardinal points
            st, t, i;       // steps based on num. of segments
    
        // clone array so we don't change the original
        //
        _pts = pts.slice(0);
    
        // The algorithm require a previous and next point to the actual point array.
        // Check if we will draw closed or open curve.
        // If closed, copy end points to beginning and first points to end
        // If open, duplicate first points to befinning, end points to end
        if (isClosed) {
            _pts.unshift(pts[pts.length - 1]);
            _pts.unshift(pts[pts.length - 2]);
            _pts.unshift(pts[pts.length - 1]);
            _pts.unshift(pts[pts.length - 2]);
            _pts.push(pts[0]);
            _pts.push(pts[1]);
        }
        else {
            _pts.unshift(pts[1]);   //copy 1. point and insert at beginning
            _pts.unshift(pts[0]);
            _pts.push(pts[pts.length - 2]); //copy last point and append
            _pts.push(pts[pts.length - 1]);
        }
    
        // ok, lets start..
    
        // 1. loop goes through point array
        // 2. loop goes through each segment between the 2 pts + 1e point before and after
        for (i=2; i < (_pts.length - 4); i+=2) {
            for (t=0; t <= numOfSegments; t++) {
    
                // calc tension vectors
                t1x = (_pts[i+2] - _pts[i-2]) * tension;
                t2x = (_pts[i+4] - _pts[i]) * tension;
    
                t1y = (_pts[i+3] - _pts[i-1]) * tension;
                t2y = (_pts[i+5] - _pts[i+1]) * tension;
    
                // calc step
                st = t / numOfSegments;
    
                // calc cardinals
                c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1; 
                c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); 
                c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st; 
                c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);
    
                // calc x and y cords with common control vectors
                x = c1 * _pts[i]    + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
                y = c1 * _pts[i+1]  + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
    
                //store points in array
                res.push(x);
                res.push(y);
    
            }
        }
    
        return res;
    }
    

    And to actually draw the points as a smoothed curve (or any other segmented lines as long as you have an x,y array):

    function drawLines(ctx, pts) {
        ctx.moveTo(pts[0], pts[1]);
        for(i=2;i

    var ctx = document.getElementById("c").getContext("2d");
    
    
    function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {
    
      ctx.beginPath();
    
      drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
      
      if (showPoints) {
        ctx.beginPath();
        for(var i=0;i
    canvas { border: 1px solid red; }

    This results in this:

    Example pix

    You can easily extend the canvas so you can call it like this instead:

    ctx.drawCurve(myPoints);
    

    Add the following to the javascript:

    if (CanvasRenderingContext2D != 'undefined') {
        CanvasRenderingContext2D.prototype.drawCurve = 
            function(pts, tension, isClosed, numOfSegments, showPoints) {
           drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)}
    }
    

    You can find a more optimized version of this on NPM (npm i cardinal-spline-js) or on GitLab.

提交回复
热议问题