0

I am drawing a sine wave (as a test) to check the time it takes to draw a curve using either QPainter::drawPolyline() or QPainter::drawPath(). In either case, if I set the pen width to more than 1, then the time it takes to draw the sine wave increases by more than 10 times.

Widget::Widget(QWidget* parent)
    : QWidget(parent)
{
    setAutoFillBackground(true);
    setAttribute(Qt::WA_NoSystemBackground, true);
    
    // creating the sine wave
    for(qsizetype ii = 0; ii < 1000000; ++ii)
        sineWave << (1.0 + sin(2 * M_PI * 3 * ii / 1000000));
}

void Widget::paintEvent(QPaintEvent* evt)
{
    QPainter painter(this);
    painter.fillRect(rect(), QColor(Qt::white));
    painter.setRenderHint(QPainter::Antialiasing);

    QPen pen;
    pen.setWidthF(2); // setting pen width
    painter.setPen(pen);

    QElapsedTimer timer;
    timer.start();

    qDebug() << "-> Paint Event\tPen Width:" << pen.widthF() << "\tWidget WxH:" << width() << "x" << height();

    QList<QPointF> pixPoints;
    for(qsizetype ii = 0; ii < sineWave.length(); ++ii) {
        double yPos = height() * (sineWave[ii] / 2.0);
        double xPos = width() * (ii / 1000000.0);
        pixPoints << QPointF(xPos, yPos);
    }
    qDebug() << "--> 1: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0);
    painter.drawPolyline(pixPoints);
    qDebug() << "--> 2: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0);
}

The output when I set the pen width = 2

-> Paint Event  Pen Width: 2    Widget WxH: 722 x 494
--> 1: Time [ms]: 109.352
--> 2: Time [ms]: 5887.08

The output when I set the pen width = 1

-> Paint Event  Pen Width: 1    Widget WxH: 741 x 535
--> 1: Time [ms]: 105.882
--> 2: Time [ms]: 233.165

The time taken for creating the QList<QPointF> pixPoints in both cases is the similar (marked as 1:), but the time it takes to draw the curve using QPainter::drawPolyline() function (marked as 2:) is extremely high when I set the pen width to 2 compared to when the pen width is set to 1.

Is there any way I could reduce this time gap?

P.S. I have posted the same question on Qt Forum here

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Chaitanya
  • 177
  • 2
  • 9
  • 1
    It's normal... a single pixel-wide pens have a specific "easy" algorithm, while fat pens essentially require building a region (calculating joints correctly) and then doing a polyfill. – Matteo Italia Jul 08 '22 at 10:17
  • 1
    Those times are very high, though, and that's expected due to the high number of points you are painting, way too many for your target resolution. To speed this up you should probably sample your sine wave at a period that is sensible for the required size of your widget. Once you have one point per target pixel (or IDK 4 if you really want to put antialiasing under stress) you are good. You can just calculate it on the fly in your paint event (and saving ~8 MB of points in memory for something you can easily recalculate is a bad idea anyhow). – Matteo Italia Jul 08 '22 at 10:21
  • Thanks! Here I am taking so many points to see the difference more clearly. In reality, the number of points is max 2x times the width of my widget. I was hoping to find a (maybe hidden) trick to reduce this time difference. – Chaitanya Jul 08 '22 at 10:27
  • There's no easy way out... the usual tricks are to disable antialiasing or decimate the number of points; I think that you can also play a bit with the types of pen joints/miter length and find the faster one (bevel IIRC), but you are going to take a perf hit for any kind of fat pen. – Matteo Italia Jul 08 '22 at 10:29

1 Answers1

1

(moving from comments)

It's normal... a single pixel-wide pens have a specific "easy" algorithm, while fat pens essentially require building a region (calculating joints correctly) and then doing a polyfill.

Those times are very high, though, and that's expected due to the high number of points you are painting, way too many for your target resolution. To speed this up you should probably sample your sine wave at a period that is sensible for the required size of your widget. Once you have one point per target pixel (or IDK 4 if you really want to put antialiasing under stress) you are good. You can just calculate it on the fly in your paint event (and saving ~8 MB of points in memory for something you can easily recalculate is a bad idea anyhow).

void Widget::paintEvent(QPaintEvent* evt)
{
    QPainter painter(this);
    painter.fillRect(rect(), QColor(Qt::white));
    painter.setRenderHint(QPainter::Antialiasing);

    QPen pen;
    pen.setWidthF(2); // setting pen width
    painter.setPen(pen);

    QElapsedTimer timer;
    timer.start();

    qDebug() << "-> Paint Event\tPen Width:" << pen.widthF() << "\tWidget WxH:" << width() << "x" << height();

    double periods = 3.;
    int w = width(), h = height();
    QList<QPointF> pixPoints;
    for (int x = 0; x < w; ++x) {
        double alpha = double(x) / w * 2 * M_PI * periods;
        points << QPointF(x, (1. + sin(alpha)) / 2 * h);
    }
    qDebug() << "--> 1: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0);
    painter.drawPolyline(pixPoints);
    qDebug() << "--> 2: Time [ms]:" << (timer.nsecsElapsed() / 1000000.0);
}
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299