简体   繁体   中英

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

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};
    }

Bezier Curves are not designed to go the provided points! 提供的点! They are designed to shape a smooth curve 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:

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.

What is this for? Why do you need to be so accurate? I would assume you only need something around 4 vertices stored for every inch the user drags his finger. With that in mind:

Try using one vertex out of every X to actually draw between, with the middle vertex used for specifying the weighted point of the curve.

int interval = 10; //how many points to skip
gesture.moveTo(gesture.get(0).x, gesture.get(0).y);
for(int i =0; i +interval/2 < gesture.size(); i+=interval)
{
   Gesture ngp = gesture.get(i+interval/2);
   gesturePath.quadTo(ngp.x,ngp.y, gp.x,gp.y);
}

You'll need to adjust this to actually work but the idea is there.

Nice question. Your (wrong) result is obvious, but you can try to apply it to a much smaller dataset, maybe by replacing groups of close points with an average point . The appropriate distance in this case to tell if two or more points belong to the same group may be expressed in time , not space, so you'll need to store the whole touch event (x, y and timestamp). I was thinking of this because I need a way to let users draw geometric primitives (rectangles, lines and simple curves) by touch

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM