From 5a9623cb54c598aa4226883076fb413ef88215e5 Mon Sep 17 00:00:00 2001 From: Bjoern Erik Nilsen Date: Wed, 29 Apr 2009 16:42:07 +0200 Subject: Wrong clip in QWidget::render(QPainter *, ...) when using Qt::(Replace|No)Clip. The problem was that we didn't take the painter's clip into account when setting the system viewport ("hard clip"). We only used the system clip, but we have to use system clip + painter clip, which is the current engine clip. Unfortunately, we have to calculate it again since there's no cross-platform way of retrieving it. This was only a problem with Qt::(Replace|No)Clip, since we in all other cases combine the old clip with the new one. (Uber cool) auto test included. Task-number: 250482 Reviewed-by: Samuel --- src/gui/kernel/qwidget.cpp | 9 +++- tests/auto/qwidget/tst_qwidget.cpp | 102 +++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/gui/kernel/qwidget.cpp b/src/gui/kernel/qwidget.cpp index f612601..fb9c8cb 100644 --- a/src/gui/kernel/qwidget.cpp +++ b/src/gui/kernel/qwidget.cpp @@ -4824,8 +4824,13 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, const QRegion oldSystemClip = enginePriv->systemClip; const QRegion oldSystemViewport = enginePriv->systemViewport; - // This ensures that transformed system clips are inside the current system clip. - enginePriv->setSystemViewport(oldSystemClip); + // This ensures that all painting triggered by render() is clipped to the current engine clip. + if (painter->hasClipping()) { + const QRegion painterClip = painter->deviceTransform().map(painter->clipRegion()); + enginePriv->setSystemViewport(oldSystemClip.isEmpty() ? painterClip : oldSystemClip & painterClip); + } else { + enginePriv->setSystemViewport(oldSystemClip); + } render(target, targetOffset, toBePainted, renderFlags); diff --git a/tests/auto/qwidget/tst_qwidget.cpp b/tests/auto/qwidget/tst_qwidget.cpp index 767553a..72ffcc3 100644 --- a/tests/auto/qwidget/tst_qwidget.cpp +++ b/tests/auto/qwidget/tst_qwidget.cpp @@ -285,6 +285,8 @@ private slots: void render_systemClip(); void render_systemClip2_data(); void render_systemClip2(); + void render_systemClip3_data(); + void render_systemClip3(); void setContentsMargins(); @@ -6995,6 +6997,106 @@ void tst_QWidget::render_systemClip2() } } +void tst_QWidget::render_systemClip3_data() +{ + QTest::addColumn("size"); + QTest::addColumn("useSystemClip"); + + // Reference: http://en.wikipedia.org/wiki/Flag_of_Norway + QTest::newRow("Norwegian Civil Flag") << QSize(220, 160) << false; + QTest::newRow("Norwegian War Flag") << QSize(270, 160) << true; +} + +// This test ensures that the current engine clip (systemClip + painter clip) +// is preserved after QPainter::setClipRegion(..., Qt::ReplaceClip); +void tst_QWidget::render_systemClip3() +{ + QFETCH(QSize, size); + QFETCH(bool, useSystemClip); + + // Calculate the inner/outer cross of the flag. + QRegion outerCross(0, 0, size.width(), size.height()); + outerCross -= QRect(0, 0, 60, 60); + outerCross -= QRect(100, 0, size.width() - 100, 60); + outerCross -= QRect(0, 100, 60, 60); + outerCross -= QRect(100, 100, size.width() - 100, 60); + + QRegion innerCross(0, 0, size.width(), size.height()); + innerCross -= QRect(0, 0, 70, 70); + innerCross -= QRect(90, 0, size.width() - 90, 70); + innerCross -= QRect(0, 90, 70, 70); + innerCross -= QRect(90, 90, size.width() - 90, 70); + + const QRegion redArea(QRegion(0, 0, size.width(), size.height()) - outerCross); + const QRegion whiteArea(outerCross - innerCross); + const QRegion blueArea(innerCross); + QRegion systemClip; + + // Okay, here's the image that should look like a Norwegian civil/war flag in the end. + QImage flag(size, QImage::Format_ARGB32); + flag.fill(QColor(Qt::transparent).rgba()); + + if (useSystemClip) { + QPainterPath warClip(QPoint(size.width(), 0)); + warClip.lineTo(size.width() - 110, 60); + warClip.lineTo(size.width(), 80); + warClip.lineTo(size.width() - 110, 100); + warClip.lineTo(size.width(), 160); + warClip.closeSubpath(); + systemClip = QRegion(0, 0, size.width(), size.height()) - QRegion(warClip.toFillPolygon().toPolygon()); + flag.paintEngine()->setSystemClip(systemClip); + } + + QPainter painter(&flag); + painter.fillRect(QRect(QPoint(), size), Qt::red); // Fill image background with red. + painter.setClipRegion(outerCross); // Limit widget painting to inside the outer cross. + + // Here's the widget that's supposed to draw the inner/outer cross of the flag. + // The outer cross (white) should be drawn when the background is auto-filled, and + // the inner cross (blue) should be drawn in the paintEvent. + class MyWidget : public QWidget + { public: + void paintEvent(QPaintEvent *) + { + QPainter painter(this); + // Be evil and try to paint outside the outer cross. This should not be + // possible since the shared painter is clipped to the outer cross. + painter.setClipRect(0, 0, 60, 60, Qt::ReplaceClip); + painter.fillRect(rect(), Qt::green); + painter.setClipRegion(clip, Qt::ReplaceClip); + painter.fillRect(rect(), Qt::blue); + } + QRegion clip; + }; + + MyWidget widget; + widget.clip = innerCross; + widget.setFixedSize(size); + widget.setPalette(Qt::white); + widget.setAutoFillBackground(true); + widget.render(&painter); + +#ifdef RENDER_DEBUG + flag.save("flag.png"); +#endif + + // Let's make sure we got a Norwegian flag. + for (int i = 0; i < flag.height(); ++i) { + for (int j = 0; j < flag.width(); ++j) { + const QPoint pixel(j, i); + const QRgb pixelValue = flag.pixel(pixel); + if (useSystemClip && !systemClip.contains(pixel)) + QCOMPARE(pixelValue, QColor(Qt::transparent).rgba()); + else if (redArea.contains(pixel)) + QCOMPARE(pixelValue, QColor(Qt::red).rgba()); + else if (whiteArea.contains(pixel)) + QCOMPARE(pixelValue, QColor(Qt::white).rgba()); + else + QCOMPARE(pixelValue, QColor(Qt::blue).rgba()); + } + } +} + void tst_QWidget::setContentsMargins() { QLabel label("why does it always rain on me?"); -- cgit v0.12