Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a smooth curved line that goes through several points in QT?

Is there a way to draw a smooth line through a set of points in QT? The number and position of the points is set during run time.

Currently, I draw a QPainterPath which contains lineTo's going from point to point, creating a path. I do use render-hints antialiasing but the path is still jagged.

I've seen QSplineSeries which seems to give this kind of curved path but it is not available in Qt4.8, which is the QT Version I'm using.

Another option that is being suggested often is using Bezier Curves but those use one start and end point and two control point, so I would need to calculate it for every segment (every lineTo) and somehow calculate those control points which I don't have at the moment.

like image 713
Damir Porobic Avatar asked Feb 05 '23 15:02

Damir Porobic


1 Answers

In the end I've implemented some kind of workaround which basically takes two connected lines, removes the connection point between them and replaces it with a curve. As I have a lot of small lines where such a change would not be visible I remove all lines that are very short and reconnect the open ends. The function was mostly provided by Bojan Kverh, check out his tutorial: https://www.toptal.com/c-plus-plus/rounded-corners-bezier-curves-qpainter

And here the functions:

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();
}
like image 193
Damir Porobic Avatar answered Feb 08 '23 16:02

Damir Porobic