QRegion: re-add rects() and port setRects() to QSpan

In Qt 5, we had QVector<QRect> QRegion::rects(), but it was
deprecated, because just iterating over the QRegion as a container of
QRects was more efficient (QRegion has a SSO for the case of one
rectangle). With QSpan, we can now bring it back with the same
efficiency as iteration, supporting Qt 5 code that never made the move
away from rects() and new code that wishes to make the conversion into
rectangles more explicit. Re-add the Qt 5 tests, which show that the
function is nearly a drop-in replacement for the Qt 5 rects() (QSpan,
at the time of this commit, doesn't have relational operators, yet).

Also add a QSpan overload of setRects(). The old (ptr, n) function
(now obsoleted, but not deprecated) allowed nullptr + n != 0, which
QSpan doesn't accept, so print a warning in that case. Also, QSpan can
hold more than INT_MAX elements on 64-bit platforms, but QRegion's API
was never ported from int to qsizetype, so we need to catch oversized
spans used as inputs, too.

[ChangeLog][QtGui][QRegion] Added QSpan overload of setRects();
re-added Qt5's rects(), but returning QSpan instead of QVector now.

Fixes: QTBUG-124712
Change-Id: I24570c886cbf77abd8d1f4a3f42ae53c892cd9ff
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Marc Mutz 2024-05-02 11:19:35 +02:00
parent f22e9795d9
commit 0076999031
3 changed files with 82 additions and 10 deletions

View File

@ -876,9 +876,15 @@ QRegion QRegion::intersect(const QRect &r) const
/*!
\fn void QRegion::setRects(const QRect *rects, int number)
\overload
\obsolete Use the QSpan overload instead.
*/
Sets the region using the array of rectangles specified by \a rects and
\a number.
/*!
\fn void QRegion::setRects(QSpan<const QRect> rects)
\since 6.8
Sets the region using the array of rectangles specified by \a rects.
The rectangles \e must be optimally Y-X sorted and follow these restrictions:
\list
@ -892,6 +898,11 @@ QRegion QRegion::intersect(const QRect &r) const
\omit
Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos).
\endomit
\note For historical reasons, \c{rects.size()} must be less than \c{INT_MAX}
(see rectCount()).
\sa rects()
*/
namespace {
@ -4214,18 +4225,39 @@ QRegion::const_iterator QRegion::end() const noexcept
return d->qt_rgn ? d->qt_rgn->end() : nullptr;
}
void QRegion::setRects(const QRect *rects, int num)
static Q_DECL_COLD_FUNCTION
void set_rects_warn(const char *what)
{
qWarning("QRegion::setRects(): %s", what);
}
void QRegion::setRects(const QRect *r, int n)
{
if (!r && n) { // old setRects() allowed this, but QSpan doesn't
set_rects_warn("passing num != 0 when rects == nullptr is deprecated.");
n = 0;
}
setRects(QSpan<const QRect>(r, n));
}
void QRegion::setRects(QSpan<const QRect> rects)
{
const auto num = int(rects.size());
if (num != rects.size()) {
set_rects_warn("span size exceeds INT_MAX, ignoring");
return;
}
*this = QRegion();
if (!rects || num == 0 || (num == 1 && rects->isEmpty()))
if (!rects.data() || num == 0 || (num == 1 && rects.front().isEmpty()))
return;
detach();
d->qt_rgn->numRects = num;
if (num == 1) {
d->qt_rgn->extents = *rects;
d->qt_rgn->innerRect = *rects;
d->qt_rgn->extents = rects.front();
d->qt_rgn->innerRect = rects.front();
} else {
d->qt_rgn->rects.resize(num);
@ -4246,12 +4278,30 @@ void QRegion::setRects(const QRect *rects, int num)
}
}
/*!
\since 6.8
Returns a span of non-overlapping rectangles that make up the region. The
span remains valid until the next call of a mutating (non-const) method on
this region.
The union of all the rectangles is equal to the original region.
\note This functions existed in Qt 5, too, but returned QVector<QRect>
instead.
\sa setRects()
*/
QSpan<const QRect> QRegion::rects() const noexcept
{
return {begin(), end()};
};
int QRegion::rectCount() const noexcept
{
return (d->qt_rgn ? d->qt_rgn->numRects : 0);
}
bool QRegion::operator==(const QRegion &r) const
{
if (!d->qt_rgn)

View File

@ -8,11 +8,11 @@
#include <QtCore/qatomic.h>
#include <QtCore/qrect.h>
#include <QtGui/qwindowdefs.h>
#include <QtCore/qcontainerfwd.h>
#ifndef QT_NO_DATASTREAM
#include <QtCore/qdatastream.h>
#endif
#include <QtCore/qspan.h>
QT_BEGIN_NAMESPACE
@ -75,6 +75,8 @@ public:
QRect boundingRect() const noexcept;
void setRects(const QRect *rect, int num);
void setRects(QSpan<const QRect> r);
QSpan<const QRect> rects() const noexcept;
int rectCount() const noexcept;
QRegion operator|(const QRegion &r) const;

View File

@ -138,12 +138,15 @@ void tst_QRegion::rects()
QRegion region(rect);
QVERIFY(region.isEmpty());
QCOMPARE(region.begin(), region.end());
QVERIFY(region.rects().isEmpty());
}
{
QRect rect(10, -20, 30, 40);
QRegion region(rect);
QCOMPARE(region.end(), region.begin() + 1);
QCOMPARE(*region.begin(), rect);
QCOMPARE(region.rects().size(), 1);
QCOMPARE(region.rects()[0], rect);
}
{
QRect r(QPoint(10, 10), QPoint(40, 40));
@ -190,6 +193,7 @@ void tst_QRegion::setRects()
QCOMPARE(region, QRegion());
QCOMPARE(region.begin(), region.end());
QVERIFY(!region.boundingRect().isValid());
QVERIFY(region.rects().isEmpty());
}
{
QRegion region;
@ -197,12 +201,15 @@ void tst_QRegion::setRects()
region.setRects(&rect, 1);
QCOMPARE(region.begin(), region.end());
QVERIFY(!region.boundingRect().isValid());
QVERIFY(region.rects().isEmpty());
}
{
QRegion region;
QRect rect(10, -20, 30, 40);
region.setRects(&rect, 1);
QCOMPARE(region.end(), region.begin() + 1);
QCOMPARE(region.rects().size(), 1);
QCOMPARE(region.rects()[0], rect);
QCOMPARE(*region.begin(), rect);
}
}
@ -316,10 +323,12 @@ void tst_QRegion::emptyPolygonRegion()
QRegion r(pa);
QTEST(r.isEmpty(), "isEmpty");
QTEST(int(std::distance(r.begin(), r.end())), "numRects");
QList<QRect> rects;
std::copy(r.begin(), r.end(), std::back_inserter(rects));
QList<QRect> rects{r.begin(), r.end()};
QTEST(int(rects.size()), "numRects");
QTEST(rects, "rects");
const auto span = r.rects();
rects.assign(span.begin(), span.end());
QTEST(rects, "rects");
}
@ -862,6 +871,7 @@ void tst_QRegion::isEmpty()
QCOMPARE(region, QRegion());
QCOMPARE(region.rectCount(), 0);
QCOMPARE(region.boundingRect(), QRect());
QVERIFY(region.rects().isEmpty());
}
void tst_QRegion::regionFromPath()
@ -877,6 +887,10 @@ void tst_QRegion::regionFromPath()
QCOMPARE(rgn.begin()[0], QRect(0, 0, 10, 10));
QCOMPARE(rgn.begin()[1], QRect(0, 100, 100, 1000));
QCOMPARE(rgn.rects().size(), 2);
QCOMPARE(rgn.rects()[0], QRect(0, 0, 10, 10));
QCOMPARE(rgn.rects()[1], QRect(0, 100, 100, 1000));
QCOMPARE(rgn.boundingRect(), QRect(0, 0, 100, 1100));
}
@ -893,6 +907,12 @@ void tst_QRegion::regionFromPath()
QCOMPARE(rgn.begin()[2], QRect(90, 10, 10, 80));
QCOMPARE(rgn.begin()[3], QRect(0, 90, 100, 10));
QCOMPARE(rgn.rects().size(), 4);
QCOMPARE(rgn.rects()[0], QRect(0, 0, 100, 10));
QCOMPARE(rgn.rects()[1], QRect(0, 10, 10, 80));
QCOMPARE(rgn.rects()[2], QRect(90, 10, 10, 80));
QCOMPARE(rgn.rects()[3], QRect(0, 90, 100, 10));
QCOMPARE(rgn.boundingRect(), QRect(0, 0, 100, 100));
}
}