Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a smooth line through a set of points using Bezier curves?

Tags:

java

android

I need to draw a smooth line through a set of vertices. The set of vertices is compiled by a user dragging their finger across a touch screen, the set tends to be fairly large and the distance between the vertices is fairly small. However, if I simply connect each vertex with a straight line, the result is very rough (not-smooth).

I found solutions to this which use spline interpolation (and/or other things I don't understand) to smooth the line by adding a bunch of additional vertices. These work nicely, but because the list of vertices is already fairly large, increasing it by 10x or so has significant performance implications.

It seems like the smoothing should be accomplishable by using Bezier curves without adding additional vertices.

Below is some code based on the solution here:

http://www.antigrain.com/research/bezier_interpolation/

It works well when the distance between the vertices is large, but doesn't work very well when the vertices are close together.

Any suggestions for a better way to draw a smooth curve through a large set of vertices, without adding additional vertices?

        Vector<PointF> gesture;
        protected void onDraw(Canvas canvas)
        {
            if(gesture.size() > 4 )
            {
                Path gesturePath = new Path();

                gesturePath.moveTo(gesture.get(0).x, gesture.get(0).y);
                gesturePath.lineTo(gesture.get(1).x, gesture.get(1).y);

                for (int i = 2; i < gesture.size() - 1; i++)
                {
                    float[] ctrl = getControlPoint(gesture.get(i), gesture.get(i - 1), gesture.get(i), gesture.get(i + 1));
                    gesturePath.cubicTo(ctrl[0], ctrl[1], ctrl[2], ctrl[3], gesture.get(i).x, gesture.get(i).y);
                }

                gesturePath.lineTo(gesture.get(gesture.size() - 1).x, gesture.get(gesture.size() - 1).y);
                canvas.drawPath(gesturePath, mPaint);
            }
        }
}


    private float[] getControlPoint(PointF p0, PointF p1, PointF p2, PointF p3)
    {
        float x0 = p0.x;
        float x1 = p1.x;
        float x2 = p2.x;
        float x3 = p3.x;
        float y0 = p0.y;
        float y1 = p1.y;
        float y2 = p2.y;
        float y3 = p3.y;

           double xc1 = (x0 + x1) / 2.0;
            double yc1 = (y0 + y1) / 2.0;
            double xc2 = (x1 + x2) / 2.0;
            double yc2 = (y1 + y2) / 2.0;
            double xc3 = (x2 + x3) / 2.0;
            double yc3 = (y2 + y3) / 2.0;

            double len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
            double len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
            double len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));

            double k1 = len1 / (len1 + len2);
            double k2 = len2 / (len2 + len3);

            double xm1 = xc1 + (xc2 - xc1) * k1;
            double ym1 = yc1 + (yc2 - yc1) * k1;

            double xm2 = xc2 + (xc3 - xc2) * k2;
            double ym2 = yc2 + (yc3 - yc2) * k2;

            // Resulting control points. Here smooth_value is mentioned
            // above coefficient K whose value should be in range [0...1].
            double k = .1;

            float ctrl1_x = (float) (xm1 + (xc2 - xm1) * k + x1 - xm1);
            float ctrl1_y = (float) (ym1 + (yc2 - ym1) * k + y1 - ym1);

            float ctrl2_x = (float) (xm2 + (xc2 - xm2) * k + x2 - xm2);
            float ctrl2_y = (float) (ym2 + (yc2 - ym2) * k + y2 - ym2);

            return new float[]{ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y};
    }
like image 522
ab11 Avatar asked Mar 28 '12 21:03

ab11


1 Answers

Bezier Curves are not designed to go through the provided points! They are designed to shape a smooth curve influenced by the control points. Further you don't want to have your smooth curve going through all data points!

Instead of interpolating you should consider filtering your data set:

Filtering

For that case you need a sequence of your data, as array of points, in the order the finger has drawn the gesture:

You should look in wiki for "sliding average".
You should use a small averaging window. (try 5 - 10 points). This works as follows: (look for wiki for a more detailed description)

I use here an average window of 10 points: start by calculation of the average of points 0 - 9, and output the result as result point 0 then calculate the average of point 1 - 10 and output, result 1 And so on.

to calculate the average between N points:
avgX = (x0+ x1 .... xn) / N
avgY = (y0+ y1 .... yn) / N

Finally you connect the resulting points with lines.

If you still need to interpolate between missing points, you should then use piece - wise cubic splines.
One cubic spline goes through all 3 provided points.
You would need to calculate a series of them.

But first try the sliding average. This is very easy.

like image 61
AlexWien Avatar answered Oct 23 '22 09:10

AlexWien