2016-11-23 63 views
1

有没有办法通过QT中的一组点来画一条平滑线? 点的数量和位置在运行期间设置。如何绘制经过QT中几个点的平滑曲线?

当前,我画了一个QPainterPath,其中包含lineTo的从一个点到另一个点,创建一个路径。我使用渲染提示抗锯齿,但路径仍然锯齿状。

我见过QSplineSeries这似乎给了这种曲线路径,但它是不具备的Qt4.8,这是我使用的QT版本。

另一种经常被建议的选择是使用贝塞尔曲线,但那些使用一个起点和终点以及两个控制点,所以我需要为每个线段(每条线)计算它,并以某种方式计算那些控制点目前没有。

回答

1

最后我实现了一种基本上采用两条连接线的解决方法,删除它们之间的连接点并用曲线替换它。由于我有许多小行,如果这种变化不可见,我将删除所有非常短的行,并重新连接开放的终端。该功能主要是受博扬Kverh提供,看看他的教程:https://www.toptal.com/c-plus-plus/rounded-corners-bezier-curves-qpainter

这里的功能:

namespace 
{ 
    float distance(const QPointF& pt1, const QPointF& pt2) 
    { 
     float hd = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()); 
     float vd = (pt1.y() - pt2.y()) * (pt1.y() - pt2.y()); 
     return std::sqrt(hd + vd); 
    } 

    QPointF getLineStart(const QPointF& pt1, const QPointF& pt2) 
    { 
     QPointF pt; 
     float rat = 10.0/distance(pt1, pt2); 
     if (rat > 0.5) { 
      rat = 0.5; 
     } 
     pt.setX((1.0 - rat) * pt1.x() + rat * pt2.x()); 
     pt.setY((1.0 - rat) * pt1.y() + rat * pt2.y()); 
     return pt; 
    } 

    QPointF getLineEnd(const QPointF& pt1, const QPointF& pt2) 
    { 
     QPointF pt; 
     float rat = 10.0/distance(pt1, pt2); 
     if (rat > 0.5) { 
      rat = 0.5; 
     } 
     pt.setX(rat * pt1.x() + (1.0 - rat)*pt2.x()); 
     pt.setY(rat * pt1.y() + (1.0 - rat)*pt2.y()); 
     return pt; 
    } 

} 

void PainterPath::smoothOut(const float& factor) 
{ 
    QList<QPointF> points; 
    QPointF p; 
    for (int i = 0; i < mPath->elementCount() - 1; i++) { 
     p = QPointF(mPath->elementAt(i).x, mPath->elementAt(i).y); 

     // Except for first and last points, check what the distance between two 
     // points is and if its less then min, don't add them to the list. 
     if (points.count() > 1 && (i < mPath->elementCount() - 2) && (distance(points.last(), p) < factor)) { 
      continue; 
     } 
     points.append(p); 
    } 

    // Don't proceed if we only have 3 or less points. 
    if (points.count() < 3) { 
     return; 
    } 

    QPointF pt1; 
    QPointF pt2; 
    QPainterPath* path = new QPainterPath(); 
    for (int i = 0; i < points.count() - 1; i++) { 
     pt1 = getLineStart(points[i], points[i + 1]); 
     if (i == 0) { 
      path->moveTo(pt1); 
     } else { 
      path->quadTo(points[i], pt1); 
     } 
     pt2 = getLineEnd(points[i], points[i + 1]); 
     path->lineTo(pt2); 
    } 

    delete mPath; 
    mPath = path; 
    prepareGeometryChange(); 
} 
0

我不认为Qt 4.8中没有开箱即用的解决方案(因为您注意到QSplineSeries是Qt 5.x功能)。另外QSplineSeriesQtCharts模块的一部分,这是一个商业模块(如QtDataVisualization),所以除非您有商业许可或您的项目是GPL,否则您不能使用它。

你必须这样做手工是经历了它所需要的数学和实现它自己(或找到一个不错的实现(不必是即使在C++更不用说Qt的兼容))。

既然你提到的贝塞尔曲线,我建议给composite Bezier curve了一枪。我记得为我所从事的一个项目实施了这件事。它需要一些...工作。 :D This article可能会帮助您开始。

贝塞尔曲线实际上是B样条(如果我没记错的话)。特别是如果你可以解决一定的缺乏光滑性,你可以很快产生复合Bezier曲线。为了他们的健壮性和知名度,我100%肯定你可以在网上找到体面的实施。可能不是Qt友好的,但如果写得不错,你应该能够很快适应代码。

This看起来相当有前途的(这是在ActionScript但MEH)。或者你可以给QPainterPath::cubicTo()一个镜头,它可以为你创建贝塞尔曲线,因为你也可以提供计算曲线所需的两个控制点。

+0

是啊,我很害怕,没有简单的解决办法,我将有一个第二看看贝塞尔曲线和B样条曲线。 –

+0

对不起,这个坏消息。 :D我用一个非常有用的链接更新了我的答案(使用ActionScript实现)。 – rbaleksandar

+0

谢谢,它看起来很有趣,虽然凌乱,我会试试看:D –

0

几乎每个人都使用三次插值来完成此任务,而您选择的是Bezier曲线或Catmull-Rom样条曲线。如果你必须击中每一点,那么你需要保持Beziers控制点之间的“手柄”或直线。然后,你适合使用最小二乘法,正如你发现有一点涉及。

卡特莫尔罗花键具有的优点是,他们只需要两个额外的控制点(开始和结束,只是镜面点来创建它们)。只要积分相当平稳,线就会表现良好。 QT图形不太可能直接绘制CatMull Rom样条,所以转换为Beziers,这是一种标准的已发布方法,您可以很容易地从Catmull Rom转到Bezier,但不是相反 - 不是每个Bezier都可以用Catmull Rom代表只有几点。

如果立方体不会给你想要的曲线,你可以使用其他插值方法,eq quintic。