我需要通过一组顶点绘制一条平滑线。该组顶点是由用户在触摸屏上拖动他们的手指来编译的,该组趋于相当大并且顶点之间的距离相当小。但是,如果我只是用一条直线连接每个顶点,结果非常粗糙(不平滑)。
我找到了使用样条插值(和/或其他我不理解的东西)的解决方案,通过添加一堆额外的顶点来平滑线条。这些工作很好,但由于顶点列表已经相当大,将其增加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};
}
答案 0 :(得分:2)
贝塞尔曲线并非旨在通过提供的点数!它们被设计成通过控制点塑造受影响的平滑曲线。 此外,您不希望平滑的曲线遍历所有数据点!
不应进行插值,而应考虑过滤数据集:
过滤
对于这种情况,你需要一个数据序列,作为点数组,按照手指绘制手势的顺序:
你应该在wiki中查看“滑动平均值” 您应该使用一个小的平均窗口。 (尝试5 - 10分)。其工作原理如下:(查找维基以获取更详细的描述)
我在这里使用10点的平均窗口: 首先计算点0-9的平均值,然后将结果输出为结果点0 然后计算点1-10的平均值和输出,结果1 等等。
计算N点之间的平均值:
avgX =(x0 + x1 .... xn)/ N
avgY =(y0 + y1 .... yn)/ N
最后,您将结果点与线连接。
如果仍需要在缺失点之间进行插值,则应使用分段三次样条 一个三次样条曲线遍历所有3个提供的点 你需要计算一系列它们。
但首先尝试滑动平均值。这很容易。
答案 1 :(得分:0)
这是为了什么?为什么你需要如此准确?我假设你只需要为用户拖动手指的每英寸存储4个顶点。考虑到这一点:
尝试使用每个X中的一个顶点来实际绘制,其中中间顶点用于指定曲线的加权点。
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);
}
你需要调整这个以实际工作,但想法就在那里。
答案 2 :(得分:0)
好问题。您的(错误)结果很明显,但您可以尝试将其应用于更小的数据集,可以将关闭点组替换为平均点。在这种情况下判断两个或多个点是否属于同一组的适当距离可以用 time 表示,而不是空格,所以你需要存储整个触摸事件(x,y和时间戳)。我正在考虑这个因为我需要一种让用户通过触摸绘制几何图元(矩形,线条和简单曲线)的方法