From 081d835c040a90f8ee76807354355062ac521dfb Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Tue, 13 Apr 2021 14:23:45 +0200 Subject: [PATCH] Avoid processing-intensive painting of high number of tiny dashes When stroking a dashed path, an unnecessary amount of processing would be spent if there is a huge number of dashes visible, e.g. because of scaling. Since the dashes are too small to be indivdually visible anyway, just replace with a semi-transparent solid line for such cases. Pick-to: 6.1 6.0 5.15 Change-Id: I9e9f7861257ad5bce46a0cf113d1a9d7824911e6 Reviewed-by: Allan Sandfeld Jensen (cherry picked from commit f4d791b330d02777fcaf02938732892eb3167e9b) * asturmlechner 2021-08-21: Conflict from preceding 94dd2ceb in dev branch: src/gui/painting/qpaintengineex.cpp Resolved via: if (pen.style() > Qt::SolidLine) { QRectF cpRect = path.controlPointRect(); const QTransform &xf = state()->matrix; - if (pen.isCosmetic()) { + if (qt_pen_is_cosmetic(pen, state()->renderHints)){ clipRect = d->exDeviceRect; cpRect.translate(xf.dx(), xf.dy()); } else { FTBFS from preceding 471e4fcb in dev branch changing QVector to QList: Resolved via: QRectF extentRect = cpRect.adjusted(-pw, -pw, pw, pw) & clipRect; qreal extent = qMax(extentRect.width(), extentRect.height()); qreal patternLength = 0; - const QList pattern = pen.dashPattern(); + const QVector pattern = pen.dashPattern(); const int patternSize = qMin(pattern.size(), 32); for (int i = 0; i < patternSize; i++) patternLength += qMax(pattern.at(i), qreal(0)); --- src/gui/painting/qpaintengineex.cpp | 44 +++++++++++++++---- .../other/lancelot/scripts/tinydashes.qps | 34 ++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 tests/auto/other/lancelot/scripts/tinydashes.qps diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp index 5d8f89eadd..55fdb0c2a0 100644 --- a/src/gui/painting/qpaintengineex.cpp +++ b/src/gui/painting/qpaintengineex.cpp @@ -385,7 +385,7 @@ QPainterState *QPaintEngineEx::createState(QPainterState *orig) const Q_GUI_EXPORT extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp -void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) +void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &inPen) { #ifdef QT_DEBUG_DRAW qDebug() << "QPaintEngineEx::stroke()" << pen; @@ -403,6 +403,38 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) d->stroker.setCubicToHook(qpaintengineex_cubicTo); } + QRectF clipRect; + QPen pen = inPen; + if (pen.style() > Qt::SolidLine) { + QRectF cpRect = path.controlPointRect(); + const QTransform &xf = state()->matrix; + if (qt_pen_is_cosmetic(pen, state()->renderHints)){ + clipRect = d->exDeviceRect; + cpRect.translate(xf.dx(), xf.dy()); + } else { + clipRect = xf.inverted().mapRect(QRectF(d->exDeviceRect)); + } + // Check to avoid generating unwieldy amount of dashes that will not be visible anyway + QRectF extentRect = cpRect & clipRect; + qreal extent = qMax(extentRect.width(), extentRect.height()); + qreal patternLength = 0; + const QVector pattern = pen.dashPattern(); + const int patternSize = qMin(pattern.size(), 32); + for (int i = 0; i < patternSize; i++) + patternLength += qMax(pattern.at(i), qreal(0)); + if (pen.widthF()) + patternLength *= pen.widthF(); + if (qFuzzyIsNull(patternLength)) { + pen.setStyle(Qt::NoPen); + } else if (extent / patternLength > 10000) { + // approximate stream of tiny dashes with semi-transparent solid line + pen.setStyle(Qt::SolidLine); + QColor color(pen.color()); + color.setAlpha(color.alpha() / 2); + pen.setColor(color); + } + } + if (!qpen_fast_equals(pen, d->strokerPen)) { d->strokerPen = pen; d->stroker.setJoinStyle(pen.joinStyle()); @@ -430,14 +462,8 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &pen) return; } - if (pen.style() > Qt::SolidLine) { - if (qt_pen_is_cosmetic(pen, state()->renderHints)){ - d->activeStroker->setClipRect(d->exDeviceRect); - } else { - QRectF clipRect = state()->matrix.inverted().mapRect(QRectF(d->exDeviceRect)); - d->activeStroker->setClipRect(clipRect); - } - } + if (!clipRect.isNull()) + d->activeStroker->setClipRect(clipRect); if (d->activeStroker == &d->stroker) d->stroker.setForceOpen(path.hasExplicitOpen()); diff --git a/tests/auto/other/lancelot/scripts/tinydashes.qps b/tests/auto/other/lancelot/scripts/tinydashes.qps new file mode 100644 index 0000000000..d41ced7f5f --- /dev/null +++ b/tests/auto/other/lancelot/scripts/tinydashes.qps @@ -0,0 +1,34 @@ +# Version: 1 +# CheckVsReference: 5% + +path_addEllipse mypath 20.0 20.0 200.0 200.0 + +save +setPen blue 20 SolidLine FlatCap +pen_setCosmetic true +pen_setDashPattern [ 0.0004 0.0004 ] +setBrush yellow + +drawPath mypath +translate 300 0 +setRenderHint Antialiasing true +drawPath mypath +restore + +path_addEllipse bigpath 200000.0 200000.0 2000000.0 2000000.0 + +setPen blue 20 DotLine FlatCap +setBrush yellow + +save +translate 0 300 +scale 0.0001 0.00011 +drawPath bigpath +restore + +save +translate 300 300 +setRenderHint Antialiasing true +scale 0.0001 0.00011 +drawPath bigpath +restore -- GitLab From 6b400e3147dcfd8cc3a393ace1bd118c93762e0c Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Fri, 23 Jul 2021 15:53:56 +0200 Subject: [PATCH] Improve fix for avoiding huge number of tiny dashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some pathological cases were not caught by the previous fix. Fixes: QTBUG-95239 Pick-to: 6.2 6.1 5.15 Change-Id: I0337ee3923ff93ccb36c4d7b810a9c0667354cc5 Reviewed-by: Robert Löhning --- src/gui/painting/qpaintengineex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp index 2997d533107..22e9a621c3c 100644 --- a/src/gui/painting/qpaintengineex.cpp +++ b/src/gui/painting/qpaintengineex.cpp @@ -426,7 +426,7 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &inPen) patternLength *= pen.widthF(); if (qFuzzyIsNull(patternLength)) { pen.setStyle(Qt::NoPen); - } else if (extent / patternLength > 10000) { + } else if (qFuzzyIsNull(extent) || extent / patternLength > 10000) { // approximate stream of tiny dashes with semi-transparent solid line pen.setStyle(Qt::SolidLine); QColor color(pen.color()); From 427df34efdcb56582a9ae9f7d2d1f39eeff70328 Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Fri, 30 Jul 2021 13:03:49 +0200 Subject: [PATCH] Refix for avoiding huge number of tiny dashes Previous fix hit too widely so some valid horizontal and vertical lines were affected; the root problem being that such lines have an empty control point rect (width or height is 0). Fix by caculating in the pen width. Pick-to: 6.2 6.1 5.15 Change-Id: I7a436e873f6d485028f6759d0e2c6456f07eebdc Reviewed-by: Allan Sandfeld Jensen (cherry picked from commit 84aba80944a2e1c3058d7a1372e0e66676411884) --- src/gui/painting/qpaintengineex.cpp | 8 ++--- .../gui/painting/qpainter/tst_qpainter.cpp | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp index 19e4b23423..9fe510827a 100644 --- a/src/gui/painting/qpaintengineex.cpp +++ b/src/gui/painting/qpaintengineex.cpp @@ -415,18 +415,18 @@ void QPaintEngineEx::stroke(const QVectorPath &path, const QPen &inPen) clipRect = xf.inverted().mapRect(QRectF(d->exDeviceRect)); } // Check to avoid generating unwieldy amount of dashes that will not be visible anyway - QRectF extentRect = cpRect & clipRect; + qreal pw = pen.widthF() ? pen.widthF() : 1; + QRectF extentRect = cpRect.adjusted(-pw, -pw, pw, pw) & clipRect; qreal extent = qMax(extentRect.width(), extentRect.height()); qreal patternLength = 0; const QVector pattern = pen.dashPattern(); const int patternSize = qMin(pattern.size(), 32); for (int i = 0; i < patternSize; i++) patternLength += qMax(pattern.at(i), qreal(0)); - if (pen.widthF()) - patternLength *= pen.widthF(); + patternLength *= pw; if (qFuzzyIsNull(patternLength)) { pen.setStyle(Qt::NoPen); - } else if (qFuzzyIsNull(extent) || extent / patternLength > 10000) { + } else if (extent / patternLength > 10000) { // approximate stream of tiny dashes with semi-transparent solid line pen.setStyle(Qt::SolidLine); QColor color(pen.color()); diff --git a/tests/auto/gui/painting/qpainter/tst_qpainter.cpp b/tests/auto/gui/painting/qpainter/tst_qpainter.cpp index 42e98ce363..d7c3f95f1d 100644 --- a/tests/auto/gui/painting/qpainter/tst_qpainter.cpp +++ b/tests/auto/gui/painting/qpainter/tst_qpainter.cpp @@ -308,6 +308,7 @@ private slots: void fillPolygon(); void drawImageAtPointF(); + void scaledDashes(); private: void fillData(); @@ -5468,6 +5469,36 @@ void tst_QPainter::drawImageAtPointF() paint.end(); } +void tst_QPainter::scaledDashes() +{ + // Test that we do not hit the limit-huge-number-of-dashes path + QRgb fore = qRgb(0, 0, 0xff); + QRgb back = qRgb(0xff, 0xff, 0); + QImage image(5, 32, QImage::Format_RGB32); + image.fill(back); + QPainter p(&image); + QPen pen(QColor(fore), 3, Qt::DotLine); + p.setPen(pen); + p.scale(1, 2); + p.drawLine(2, 0, 2, 16); + p.end(); + + bool foreFound = false; + bool backFound = false; + int i = 0; + while (i < 32 && (!foreFound || !backFound)) { + QRgb pix = image.pixel(3, i); + if (pix == fore) + foreFound = true; + else if (pix == back) + backFound = true; + i++; + } + + QVERIFY(foreFound); + QVERIFY(backFound); +} + QTEST_MAIN(tst_QPainter) #include "tst_qpainter.moc" -- GitLab