4

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

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

我见过 QSplineSeries 似乎提供了这种弯曲的路径,但它在 Qt4.8 中不可用,这是我正在使用的 QT 版本。

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

4

3 回答 3

6

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

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

Catmull Rom 样条曲线的优点是它们只需要两个额外的控制点(起点和终点,只需镜像点即可创建它们)。只要这些点相当平滑,这条线就会表现良好。QT 图形不太可能直接绘制 CatMull Rom 样条线,因此转换为 Bezier,这是一种标准发布的方法,您可以很容易地从 Catmull Rom 转到 Bezier,但不是相反 - 不是每个 Bezier 都可以用 Catmull Rom 表示只有几点。

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

于 2016-12-02T14:31:55.193 回答
1

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

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

既然您提到了贝塞尔曲线,我建议您试一试复合贝塞尔曲线。我记得在我从事的一个项目中实现了那个东西。它需要一些……工作。:D本文可能会帮助您入门。

贝塞尔曲线实际上是 B 样条曲线(如果我没记错的话)。特别是如果您可以解决缺乏平滑度的问题,您可以非常快速地生成复合贝塞尔曲线。为了它们的健壮性和受欢迎程度,我 100% 确信您可以在线找到一个不错的实现。可能对 Qt 不友好,但如果编写得当,您应该能够立即调整代码。

看起来很有希望(它在 ActionScript 中,但没用)。或者您可以给出QPainterPath::cubicTo()一个可以为您创建贝塞尔曲线的镜头,因为您还可以提供计算曲线所需的两个控制点。

于 2016-11-23T12:17:56.150 回答