java - 如何使用贝塞尔曲线绘制通过一组点的平滑线?

标签 java android

我需要通过一组顶点画一条平滑的线。顶点集是通过用户在触摸屏上拖动他们的手指来编译的,该集往往相当大,顶点之间的距离相当小。但是,如果我简单地用一条直线连接每个顶点,结果会很粗糙(不平滑)。

我找到了解决方案,它使用样条插值(和/或其他我不理解的东西)通过添加一堆额外的顶点来平滑线。这些效果很好,但由于顶点列表已经相当大,将其增加 10 倍左右会对性能产生重大影响。

看起来平滑应该可以通过使用贝塞尔曲线来完成,而无需添加额外的顶点。

下面是一些基于此处解决方案的代码:

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

当顶点之间的距离较大时效果很好,但当顶点靠得很近时效果不佳。

对于在不添加额外顶点的情况下通过大量顶点绘制平滑曲线的更好方法有何建议?

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

最佳答案

贝塞尔曲线并非旨在通过提供的点!它们旨在塑造一条受控制点影响的平滑曲线。 此外,您不希望平滑曲线穿过所有数据点!

您应该考虑过滤数据集而不是插值:

过滤

对于这种情况,您需要一系列数据,作为点数组,按照手指绘制手势的顺序:

您应该在 wiki 中查找“滑动平均值”。
您应该使用一个小的平均窗口。 (尝试 5 - 10 点)。其工作方式如下:(查找 wiki 以获得更详细的描述)

我在这里使用 10 个点的平均窗口: 首先计算点 0 - 9 的平均值,并将结果作为结果点 0 输出 然后计算点 1 - 10 的平均值并输出,结果 1 等等。

计算N个点之间的平均值:
avgX = (x0+ x1 .... xn)/N
avgY = (y0+ y1 .... yn)/N

最后用线连接结果点。

如果您仍然需要在缺失点之间进行插值,则应使用分段三次样条。
一个三次样条通过所有 3 个提供的点。
您需要计算一系列它们。

但首先尝试滑动平均。这很容易。

关于java - 如何使用贝塞尔曲线绘制通过一组点的平滑线?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9916119/

相关文章:

android - 使用 Robolectric 2.X 创建自定义阴影

android - 为 Android 编译 gloox

java - 如何在JAVA中向进程发送ctrl+c信号

java - Java中按实体类型检索存储库

java - 如何正确设计具有内部条件转换的游戏状态机

java - 如何在 Android 应用程序中保留我的主题设置?

android - 如何显示带有图标和名称的复选框列表(android)

java - 即使没有内容长度 header ,也从 HTTP 请求中获取内容

java - Java中枚举字段的序列化

c# - 我想用以下方法将 short 转换为 byte