如何绘制在QT中经过几个点的平滑曲线?

时间:2016-11-23 12:01:21

标签: c++ qt qpainterpath

有没有办法在QT中通过一组点绘制平滑线? 在运行时设置点的数量和位置。

目前,我绘制了一个QPainterPath,它包含了从一点到另一点的lineTo,创建了一条路径。我确实使用渲染提示抗锯齿,但路径仍然是锯齿状的。

我见过QSplineSeries似乎提供了这种曲线路径,但Qt4.8中没有它,这是QT版本I使用的。

经常建议的另一个选项是使用Bezier曲线,但是那些使用一个起点和终点以及两个控制点,所以我需要为每个段(每个行)计算它,并以某种方式计算我不喜欢的那些控制点现在没有。

3 个答案:

答案 0 :(得分:1)

最后,我实现了某种解决方法,它基本上采用了两条连线,删除了它们之间的连接点,并用曲线替换它。由于我有很多小线条,这些变化不可见,我删除所有非常短的线并重新连接开放端。该功能主要由Bojan 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();
}

答案 1 :(得分:0)

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

你必须手动完成它所需的数学运算并自己实现(或找到一个很好的实现(不需要在C ++中,更不用说与Qt兼容)。)

既然你已经提到了Bezier曲线,我建议给composite Bezier curve一个镜头。我记得为我所从事的项目实现了那个东西。它需要一些......工作。 :D This article可能会帮助您入门。

Bezier曲线实际上是B样条(如果我没记错的话)。特别是如果你能够在一定程度上缺乏平滑度的情况下,你可以非常快速地生成复合贝塞尔曲线。为了他们的健壮性和受欢迎程度,我百分百肯定你可以在网上找到一个体面的实施。可能不是Qt友好的,但如果写得正确,你应该能够立即调整代码。

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

答案 2 :(得分:0)

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

Catmull Rom样条曲线的优势在于它们只需要两个额外的控制点(开始和结束,只需要镜像点来创建它们)。只要点数相当平滑,线条就会表现得很好。 QT图形不太可能直接绘制CatMull Rom样条,因此转换为Beziers,这是一种标准的发布方法,你可以很容易地从Catmull Rom转到Bezier,尽管不是相反 - 不是每个Bezier都可以由Catmull Rom代表只有几点。

你可以使用其他插值方法,例如quintic,如果Cubics不能给你你想要的曲线。