Skip to main content

Finding Control Points for Cubic Bezier Curves

Free2015-09-06#Math#JS#Solution#平滑曲线#贝塞尔曲线#三次贝塞尔曲线#Bezier Control Point#贝塞尔曲线控制点#JavaScript贝塞尔曲线

Currently, Canvas supports up to cubic Bezier curves. This article introduces a perfect method for calculating control points that is computationally light, simple, and easy to use.

Foreword

Initially, I wanted to represent traditional bar-style frequency spectrum charts using smooth curves; the "breathing" effect should feel more natural.

I previously tried least squares curve fitting, but the conclusion was that least squares curve fitting cannot solve this problem:

  • Least squares curve fitting: Can find a line or an n-degree curve that evenly splits all scattered points into two parts.

  • Bezier curve: Can find a curve that smoothly passes through each scattered point.

I. Problem Restatement

Cubic Bezier curves require two control points, as shown below:

[caption id="attachment_751" align="alignnone" width="360"]Cubic Bezier Curve Cubic Bezier Curve[/caption]

(P1 and P2 in the figure are the control points)

Scattered points (the tops of each bar in a spectrum chart) can be obtained via the Audio API. The difficulty lies in calculating the two control points; setting them arbitrarily will definitely not work and will produce very poor results.

II. Solution

I found an empirical solution based on practice. Although it has no strict mathematical basis, the actual effect is perfect. The specific steps are as follows:

  1. Assume the control points are between (x1, y1) and (x2, y2). The first and last points are the previous and next points on the curve path, respectively.

  2. Calculate midpoints.

  3. Calculate the lengths of the lines connecting the midpoints.

  4. Calculate the ratios of the lengths of the lines connecting the midpoints (used to determine the positions of p2 and p3 before translation).

  5. Translate p2.

  6. Translate p3.

  7. [Optional] Fine-tune the distance between the control points and the vertices; the larger the distance, the straighter the curve.

For more details, please see Interpolation with Bezier Curves: A very simple method of smoothing polygons.

For the smoothing effect, you can refer to Jianshu: Android Handwriting Optimization - Implementing Smoother Signature Effects.

III. JavaScript Implementation for Finding Cubic Bezier Control Points

The original version is implemented in Java. I have made simple modifications and encapsulated it as follows:

window.Bezier = {
    /**
     * Get control point coordinates
     * @param  {Array} arr Array of 4 point coordinates
     * @param  {Float} smooth_value [0, 1] Smoothness
     *   p1 Previous point
     *   p2 Left endpoint
     *   P3 Right endpoint
     *   p4 Next point
     * @return {Array}     Array of 2 point coordinates
     */
    getControlPoints: function(arr, smooth_value) {
        var x0 = arr[0].x, y0 = arr[0].y;
        var x1 = arr[1].x, y1 = arr[1].y;
        var x2 = arr[2].x, y2 = arr[2].y;
        var x3 = arr[3].x, y3 = arr[3].y;

        // Assume we need to calculate the control
        // points between (x1,y1) and (x2,y2).
        // Then x0,y0 - the previous vertex,
        //      x3,y3 - the next one.
        // 1. Assume the control points are between (x1, y1) and (x2, y2). The first and last points are the previous and next points on the curve path, respectively.

        // 2. Calculate midpoints
        var xc1 = (x0 + x1) / 2.0;
        var yc1 = (y0 + y1) / 2.0;
        var xc2 = (x1 + x2) / 2.0;
        var yc2 = (y1 + y2) / 2.0;
        var xc3 = (x2 + x3) / 2.0;
        var yc3 = (y2 + y3) / 2.0;

        // 3. Calculate the lengths of the lines connecting the midpoints
        var len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
        var len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
        var len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

        // 4. Calculate the ratios of the lengths of the lines connecting the midpoints (used to determine the positions of p2 and p3 before translation)
        var k1 = len1 / (len1 + len2);
        var k2 = len2 / (len2 + len3);

        // 5. Translate p2
        var xm1 = xc1 + (xc2 - xc1) * k1;
        var ym1 = yc1 + (yc2 - yc1) * k1;

        // 6. Translate p3
        var xm2 = xc2 + (xc3 - xc2) * k2;
        var ym2 = yc2 + (yc3 - yc2) * k2;

        // Resulting control points. Here smooth_value is mentioned
        // above coefficient K whose value should be in range [0...1].
        // 7. Fine-tune the distance between the control points and the vertices; the larger the distance, the straighter the curve
        var ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
        var ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1;

        var ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
        var ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2;

        return [{x: ctrl1_x, y: ctrl1_y}, {x: ctrl2_x, y: ctrl2_y}];
    }
};

The comments should be quite detailed now and can be understood in conjunction with the Interpolation with Bezier Curves: A very simple method of smoothing polygons mentioned above.

IV. Result Screenshot

[caption id="attachment_752" align="alignnone" width="794"]Bezier Curve Effect Bezier Curve Effect[/caption]

The smoothing effect is excellent, and the computational overhead is low, satisfying the requirements for real-time drawing (the original project was for Android handwriting signatures).

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment