如何绘制通过一组用贝塞尔曲线点的流畅的线条?线条、塞尔、曲线、流畅

2023-09-05 03:07:01 作者:何必再高傲

我需要通过一组顶点画一条流畅的线条。该组顶点的编译由用户拖动他们的手指在触摸屏上,该组往往是相当大,并且所述顶点之间的距离相当小。不过,如果我只是用直线连接每个顶点,效果是非常粗糙(不光滑)。

我发现的解决方案,这里面使用的样条插值(和/或其他的东西我不明白),通过添加一些额外的顶点平滑线。这很好地工作,但由于顶点的名单已经是相当大的,它通过增加10倍左右,有显著的性能影响。

这似乎是平滑应该accomplishable用贝塞尔曲线无需增加额外的顶点。

下面是基于该解决方案在这里约code:

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

有工作良好时的顶点间的距离大,但是不能很好地工作,当顶点靠近在一起。

一个更好的方式,通过一个大组顶点画一条光滑的曲线任何建议,无需增加额外的顶点?

 矢量<&的PointF GT;手势;
        保护无效的OnDraw(帆布油画)
        {
            如果(gesture.size()→4)
            {
                路径gesturePath =新路径();

                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;我++)
                {
                    浮[] CTRL = getControlPoint(gesture.get(i)中,gesture.get(ⅰ -  1),gesture.get(i)中,gesture.get第(i + 1));
                    gesturePath.cubicTo(CTRL [0],CTRL [1],CTRL [2],CTRL [3],gesture.get(ⅰ).X,gesture.get(ⅰ).Y);
                }

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


    私人浮法[] getControlPoint(P0的PointF,的PointF P1,P2的PointF,的PointF P3)
    {
        浮X0 = p0.x;
        浮X1 = p1.x;
        浮X2 = p2.x;
        浮X3 = p3.x;
        浮Y0 = p0.y;
        浮Y1 = p1.y;
        浮Y2 = p2.y;
        浮Y3 = p3.y;

           双XC1 =(X0 +​​ X1)/ 2.0;
            双YC1 =(Y0 + Y1)/ 2.0;
            双XC2 =(X1 + X2)/ 2.0;
            双YC2 =(Y1 + Y2)/ 2.0;
            双XC3 =(X2 + X3)/ 2.0;
            双YC3 =(Y2 + Y3)/ 2.0;

            双LEN1 =的Math.sqrt((X1-X0)*(X1-X0)+(Y1-Y0)*(Y1-Y0));
            双LEN2 =的Math.sqrt((X2-X1)*(X2-X1)+(Y2-Y1)*(Y2-Y1));
            双LEN3 =的Math.sqrt((X3-X2)*(×3-X2)+(Y3-Y2)*(Y3-Y2));

            双K1 = LEN1 /(LEN1 + LEN2);
            双K2 = LEN2 /(LEN2 + LEN3);

            双XM1 = XC1 +(XC2  -  XC1)* K1;
            双YM1 = YC1 +(YC2  -  YC1)* K1;

            双XM2 = XC2 +(XC3  -  XC2)* K2;
            双YM2 = YC2 +(YC3  -  YC2)* K2;

            //产生的控制点。在这里,smooth_value被提及
            //上述系数K,其值应在范围[0 ... 1]。
            双K = .1;

            浮动ctrl1_x =(浮点)(XM1 +(XC2  -  XM1)* K + X1  -  XM1);
            浮动ctrl1_y =(浮点)(YM1 +(YC2  -  YM1)* K + Y1  -  YM1);

            浮动ctrl2_x =(浮点)(XM2 +(XC2  -  XM2)* K + X2  -  XM2);
            浮动ctrl2_y =(浮点)(YM2 +(YC2  -  YM2)* K + Y2  -  YM2);

            返回新浮法[] {ctrl1_x,ctrl1_y,ctrl2_x,ctrl2_y};
    }
 

解决方案

贝塞尔曲线的设计并不去到所提供的分!它们被设计成通过控制点来塑造的平滑曲线的影响的。 此外,您不希望您的光滑的曲线通过所有的数据点!

相反,内插,你应该考虑过滤数据集:

过滤

Android 贝塞尔曲线原理及其绘制实现

有关这种情况下,你需要你的数据序列,为点阵列的顺序在手指绘制的姿势:

您应该在维基百科中有关滑动平均。 你应该用一个小的平均窗。 (尝试5 - 10分)。这种工作方式如下:(查找维基更详细的说明)

我在这里使用了10个点的平均窗口: 通过计算点0的平均启动 - 9,并输出结果作为结果点0 然后计算点1的平均 - 10和输出,造成1 等等。

计算N点之间的平均: avgX =(X0 +​​ X1 .... XN)/ N avgY =(Y0 + Y1 .... YN)/ N

最后,你用线连接所产生的点。

如果您仍然需要缺少点之间进行插值,那么你就应该用一块 - 明智的三次样条。 其中三次样条贯穿全部3提供了点。 您将需要计算系列当中。

不过,首先尝试的滑动平均值。这是很容易的。

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 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.