【问题标题】:How to draw a smooth curved line that goes through several points in QT?如何在QT中绘制一条穿过多个点的平滑曲线?
【发布时间】:2020-09-03 16:14:39
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c++ qt qpainterpath


    【解决方案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();
    }
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

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

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

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

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

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

        【讨论】:

        • 是的,我担心没有简单的解决方案,我再看看贝塞尔曲线和 B 样条线。
        • 抱歉坏消息。 :D 我用一个非常有用的链接更新了我的答案(使用 ActionScript 来实现)。
        • 谢谢,虽然有点乱,但看起来很有趣,我试试看:D
        • 没有什么是完美的。 :P
        • 如果使用贝塞尔曲线,曲线不会经过点。
        猜你喜欢
        • 1970-01-01
        • 2017-01-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-16
        • 1970-01-01
        • 2017-06-10
        相关资源
        最近更新 更多