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) \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: The rectangles \e must be optimally Y-X sorted and follow these restrictions:
\list \list
@ -892,6 +898,11 @@ QRegion QRegion::intersect(const QRect &r) const
\omit \omit
Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos). Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos).
\endomit \endomit
\note For historical reasons, \c{rects.size()} must be less than \c{INT_MAX}
(see rectCount()).
\sa rects()
*/ */
namespace { namespace {
@ -4214,18 +4225,39 @@ QRegion::const_iterator QRegion::end() const noexcept
return d->qt_rgn ? d->qt_rgn->end() : nullptr; 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(); *this = QRegion();
if (!rects || num == 0 || (num == 1 && rects->isEmpty())) if (!rects.data() || num == 0 || (num == 1 && rects.front().isEmpty()))
return; return;
detach(); detach();
d->qt_rgn->numRects = num; d->qt_rgn->numRects = num;
if (num == 1) { if (num == 1) {
d->qt_rgn->extents = *rects; d->qt_rgn->extents = rects.front();
d->qt_rgn->innerRect = *rects; d->qt_rgn->innerRect = rects.front();
} else { } else {
d->qt_rgn->rects.resize(num); 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 int QRegion::rectCount() const noexcept
{ {
return (d->qt_rgn ? d->qt_rgn->numRects : 0); return (d->qt_rgn ? d->qt_rgn->numRects : 0);
} }
bool QRegion::operator==(const QRegion &r) const bool QRegion::operator==(const QRegion &r) const
{ {
if (!d->qt_rgn) if (!d->qt_rgn)

View File

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

View File

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