QPainterPath: Add option to cache calculations
Speeds up repeated calls to length and percentage functions. Change-Id: I357732e35de648e7da07ad492e58d5b94c2c0870 Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
76b1424048
commit
90247966a7
@ -2821,6 +2821,44 @@ QPolygonF QPainterPath::toFillPolygon(const QTransform &matrix) const
|
||||
return polygon;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns true if caching is enabled; otherwise returns false.
|
||||
|
||||
\since 6.10
|
||||
\sa setCachingEnabled()
|
||||
*/
|
||||
bool QPainterPath::isCachingEnabled() const
|
||||
{
|
||||
Q_D(QPainterPath);
|
||||
return d && d->cacheEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables or disables length caching according to the value of \a enabled.
|
||||
|
||||
Enabling caching speeds up repeated calls to the member functions involving path length
|
||||
and percentage values, such as length(), percentAtLength(), pointAtPercent() etc., at the cost
|
||||
of some extra memory usage for storage of intermediate calculations. By default it is disabled.
|
||||
|
||||
Disabling caching will release any allocated cache memory.
|
||||
|
||||
\since 6.10
|
||||
\sa isCachingEnabled(), length(), percentAtLength(), pointAtPercent()
|
||||
*/
|
||||
void QPainterPath::setCachingEnabled(bool enabled)
|
||||
{
|
||||
ensureData();
|
||||
if (d_func()->cacheEnabled == enabled)
|
||||
return;
|
||||
detach();
|
||||
QPainterPathPrivate *d = d_func();
|
||||
d->cacheEnabled = enabled;
|
||||
if (!enabled) {
|
||||
d->m_runLengths.clear();
|
||||
d->m_runLengths.squeeze();
|
||||
}
|
||||
}
|
||||
|
||||
//derivative of the equation
|
||||
static inline qreal slopeAt(qreal t, qreal a, qreal b, qreal c, qreal d)
|
||||
{
|
||||
@ -2835,6 +2873,11 @@ qreal QPainterPath::length() const
|
||||
Q_D(QPainterPath);
|
||||
if (isEmpty())
|
||||
return 0;
|
||||
if (d->cacheEnabled) {
|
||||
if (d->dirtyRunLengths)
|
||||
d->computeRunLengths();
|
||||
return d->m_runLengths.last();
|
||||
}
|
||||
|
||||
qreal len = 0;
|
||||
for (int i=1; i<d->elements.size(); ++i) {
|
||||
@ -2883,6 +2926,32 @@ qreal QPainterPath::percentAtLength(qreal len) const
|
||||
if (len > totalLength)
|
||||
return 1;
|
||||
|
||||
if (d->cacheEnabled) {
|
||||
const int ei = qMax(d->elementAtT(len / totalLength), 1); // Skip initial MoveTo
|
||||
qreal res = 0;
|
||||
const QPainterPath::Element &e = d->elements[ei];
|
||||
switch (e.type) {
|
||||
case QPainterPath::LineToElement:
|
||||
res = len / totalLength;
|
||||
break;
|
||||
case CurveToElement:
|
||||
{
|
||||
QBezier b = QBezier::fromPoints(d->elements.at(ei-1),
|
||||
e,
|
||||
d->elements.at(ei+1),
|
||||
d->elements.at(ei+2));
|
||||
qreal prevLen = d->m_runLengths[ei - 1];
|
||||
qreal blen = d->m_runLengths[ei] - prevLen;
|
||||
qreal elemRes = b.tAtLength(len - prevLen);
|
||||
res = (elemRes * blen + prevLen) / totalLength;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
qreal curLen = 0;
|
||||
for (int i=1; i<d->elements.size(); ++i) {
|
||||
const Element &e = d->elements.at(i);
|
||||
@ -2927,7 +2996,8 @@ qreal QPainterPath::percentAtLength(qreal len) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline QBezier bezierAtT(const QPainterPath &path, qreal t, qreal *startingLength, qreal *bezierLength)
|
||||
static inline QBezier uncached_bezierAtT(const QPainterPath &path, qreal t, qreal *startingLength,
|
||||
qreal *bezierLength)
|
||||
{
|
||||
*startingLength = 0;
|
||||
if (t > 1)
|
||||
@ -2981,6 +3051,35 @@ static inline QBezier bezierAtT(const QPainterPath &path, qreal t, qreal *starti
|
||||
return QBezier();
|
||||
}
|
||||
|
||||
QBezier QPainterPathPrivate::bezierAtT(const QPainterPath &path, qreal t, qreal *startingLength,
|
||||
qreal *bezierLength) const
|
||||
{
|
||||
Q_ASSERT(t >= 0 && t <= 1);
|
||||
QPainterPathPrivate *d = path.d_func();
|
||||
if (!path.isEmpty() && d->cacheEnabled) {
|
||||
const int ei = qMax(d->elementAtT(t), 1); // Avoid the initial MoveTo element
|
||||
const qreal prevRunLength = d->m_runLengths[ei - 1];
|
||||
*startingLength = prevRunLength;
|
||||
*bezierLength = d->m_runLengths[ei] - prevRunLength;
|
||||
const QPointF prev = d->elements[ei - 1];
|
||||
const QPainterPath::Element &e = d->elements[ei];
|
||||
switch (e.type) {
|
||||
case QPainterPath::LineToElement:
|
||||
{
|
||||
QPointF delta = (e - prev) / 3;
|
||||
return QBezier::fromPoints(prev, prev + delta, prev + 2 * delta, e);
|
||||
}
|
||||
case QPainterPath::CurveToElement:
|
||||
return QBezier::fromPoints(prev, e, elements[ei + 1], elements[ei + 2]);
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
return uncached_bezierAtT(path, t, startingLength, bezierLength);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the point at at the percentage \a t of the current path.
|
||||
The argument \a t has to be between 0 and 1.
|
||||
@ -3006,7 +3105,7 @@ QPointF QPainterPath::pointAtPercent(qreal t) const
|
||||
qreal totalLength = length();
|
||||
qreal curLen = 0;
|
||||
qreal bezierLen = 0;
|
||||
QBezier b = bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
QBezier b = d_ptr->bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
qreal realT = (totalLength * t - curLen) / bezierLen;
|
||||
|
||||
return b.pointAt(qBound(qreal(0), realT, qreal(1)));
|
||||
@ -3034,7 +3133,7 @@ qreal QPainterPath::angleAtPercent(qreal t) const
|
||||
qreal totalLength = length();
|
||||
qreal curLen = 0;
|
||||
qreal bezierLen = 0;
|
||||
QBezier bez = bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
QBezier bez = d_ptr->bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
qreal realT = (totalLength * t - curLen) / bezierLen;
|
||||
|
||||
qreal m1 = slopeAt(realT, bez.x1, bez.x2, bez.x3, bez.x4);
|
||||
@ -3063,7 +3162,7 @@ qreal QPainterPath::slopeAtPercent(qreal t) const
|
||||
qreal totalLength = length();
|
||||
qreal curLen = 0;
|
||||
qreal bezierLen = 0;
|
||||
QBezier bez = bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
QBezier bez = d_ptr->bezierAtT(*this, t, &curLen, &bezierLen);
|
||||
qreal realT = (totalLength * t - curLen) / bezierLen;
|
||||
|
||||
qreal m1 = slopeAt(realT, bez.x1, bez.x2, bez.x3, bez.x4);
|
||||
@ -3283,9 +3382,10 @@ bool QPainterPath::contains(const QPainterPath &p) const
|
||||
|
||||
void QPainterPath::setDirty(bool dirty)
|
||||
{
|
||||
d_func()->pathConverter.reset();
|
||||
d_func()->dirtyBounds = dirty;
|
||||
d_func()->dirtyControlBounds = dirty;
|
||||
d_func()->pathConverter.reset();
|
||||
d_func()->dirtyRunLengths = dirty;
|
||||
d_func()->convex = false;
|
||||
}
|
||||
|
||||
@ -3358,6 +3458,43 @@ void QPainterPath::computeControlPointRect() const
|
||||
d->controlBounds = QRectF(minx, miny, maxx - minx, maxy - miny);
|
||||
}
|
||||
|
||||
void QPainterPathPrivate::computeRunLengths()
|
||||
{
|
||||
Q_ASSERT(!elements.isEmpty());
|
||||
|
||||
m_runLengths.clear();
|
||||
const int numElems = elements.size();
|
||||
m_runLengths.reserve(numElems);
|
||||
|
||||
QPointF runPt = elements[0];
|
||||
qreal runLen = 0.0;
|
||||
for (int i = 0; i < numElems; i++) {
|
||||
QPainterPath::Element e = elements[i];
|
||||
switch (e.type) {
|
||||
case QPainterPath::LineToElement:
|
||||
runLen += QLineF(runPt, e).length();
|
||||
runPt = e;
|
||||
break;
|
||||
case QPainterPath::CurveToElement: {
|
||||
Q_ASSERT(i < numElems - 2);
|
||||
QPainterPath::Element ee = elements[i + 2];
|
||||
runLen += QBezier::fromPoints(runPt, e, elements[i + 1], ee).length();
|
||||
runPt = ee;
|
||||
break;
|
||||
}
|
||||
case QPainterPath::MoveToElement:
|
||||
runPt = e;
|
||||
break;
|
||||
case QPainterPath::CurveToDataElement:
|
||||
break;
|
||||
}
|
||||
m_runLengths.append(runLen);
|
||||
}
|
||||
Q_ASSERT(m_runLengths.size() == elements.size());
|
||||
|
||||
dirtyRunLengths = false;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
QDebug operator<<(QDebug s, const QPainterPath &p)
|
||||
{
|
||||
|
@ -134,8 +134,10 @@ public:
|
||||
QPainterPath::Element elementAt(int i) const;
|
||||
void setElementPositionAt(int i, qreal x, qreal y);
|
||||
|
||||
bool isCachingEnabled() const;
|
||||
void setCachingEnabled(bool enabled);
|
||||
qreal length() const;
|
||||
qreal percentAtLength(qreal t) const;
|
||||
qreal percentAtLength(qreal len) const;
|
||||
QPointF pointAtPercent(qreal t) const;
|
||||
qreal angleAtPercent(qreal t) const;
|
||||
qreal slopeAtPercent(qreal t) const;
|
||||
@ -174,6 +176,7 @@ private:
|
||||
|
||||
friend class QPainterPathStroker;
|
||||
friend class QPainterPathStrokerPrivate;
|
||||
friend class QPainterPathPrivate;
|
||||
friend class QTransform;
|
||||
friend class QVectorPath;
|
||||
friend Q_GUI_EXPORT const QVectorPath &qtVectorPathForPath(const QPainterPath &);
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <private/qvectorpath_p.h>
|
||||
#include <private/qstroker_p.h>
|
||||
#include <private/qbezier_p.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@ -111,7 +112,8 @@ public:
|
||||
dirtyBounds(false),
|
||||
dirtyControlBounds(false),
|
||||
convex(false),
|
||||
hasWindingFill(false)
|
||||
hasWindingFill(false),
|
||||
cacheEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -124,21 +126,25 @@ public:
|
||||
dirtyBounds(false),
|
||||
dirtyControlBounds(false),
|
||||
convex(false),
|
||||
hasWindingFill(false)
|
||||
hasWindingFill(false),
|
||||
cacheEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
QPainterPathPrivate(const QPainterPathPrivate &other) noexcept
|
||||
: QSharedData(other),
|
||||
elements(other.elements),
|
||||
m_runLengths(other.m_runLengths),
|
||||
bounds(other.bounds),
|
||||
controlBounds(other.controlBounds),
|
||||
cStart(other.cStart),
|
||||
require_moveTo(false),
|
||||
dirtyBounds(other.dirtyBounds),
|
||||
dirtyControlBounds(other.dirtyControlBounds),
|
||||
dirtyRunLengths(other.dirtyRunLengths),
|
||||
convex(other.convex),
|
||||
hasWindingFill(other.hasWindingFill)
|
||||
hasWindingFill(other.hasWindingFill),
|
||||
cacheEnabled(other.cacheEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
@ -149,6 +155,10 @@ public:
|
||||
inline void close();
|
||||
inline void maybeMoveTo();
|
||||
inline void clear();
|
||||
void computeRunLengths();
|
||||
int elementAtT(qreal t);
|
||||
QBezier bezierAtT(const QPainterPath &path, qreal t, qreal *startingLength,
|
||||
qreal *bezierLength) const;
|
||||
|
||||
const QVectorPath &vectorPath() {
|
||||
if (!pathConverter)
|
||||
@ -159,6 +169,7 @@ public:
|
||||
private:
|
||||
QList<QPainterPath::Element> elements;
|
||||
std::unique_ptr<QVectorPathConverter> pathConverter;
|
||||
QList<qreal> m_runLengths;
|
||||
QRectF bounds;
|
||||
QRectF controlBounds;
|
||||
|
||||
@ -167,8 +178,10 @@ private:
|
||||
bool require_moveTo : 1;
|
||||
bool dirtyBounds : 1;
|
||||
bool dirtyControlBounds : 1;
|
||||
bool dirtyRunLengths : 1;
|
||||
bool convex : 1;
|
||||
bool hasWindingFill : 1;
|
||||
bool cacheEnabled : 1;
|
||||
};
|
||||
|
||||
class QPainterPathStrokerPrivate
|
||||
@ -257,6 +270,7 @@ inline void QPainterPathPrivate::clear()
|
||||
Q_ASSERT(ref.loadRelaxed() == 1);
|
||||
|
||||
elements.clear();
|
||||
m_runLengths.clear();
|
||||
|
||||
cStart = 0;
|
||||
bounds = {};
|
||||
@ -265,12 +279,23 @@ inline void QPainterPathPrivate::clear()
|
||||
require_moveTo = false;
|
||||
dirtyBounds = false;
|
||||
dirtyControlBounds = false;
|
||||
dirtyRunLengths = false;
|
||||
convex = false;
|
||||
|
||||
pathConverter.reset();
|
||||
}
|
||||
#define KAPPA qreal(0.5522847498)
|
||||
|
||||
inline int QPainterPathPrivate::elementAtT(qreal t)
|
||||
{
|
||||
Q_ASSERT(cacheEnabled);
|
||||
if (dirtyRunLengths)
|
||||
computeRunLengths();
|
||||
qreal len = t * m_runLengths.constLast();
|
||||
const auto it = std::lower_bound(m_runLengths.constBegin(), m_runLengths.constEnd(), len);
|
||||
return (it == m_runLengths.constEnd()) ? m_runLengths.size() - 1 : int(it - m_runLengths.constBegin());
|
||||
}
|
||||
|
||||
#define KAPPA qreal(0.5522847498)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
@ -1145,10 +1145,16 @@ void tst_QPainterPath::pointAtPercent()
|
||||
QFETCH(qreal, percent);
|
||||
QFETCH(QPointF, point);
|
||||
|
||||
QVERIFY(!path.isCachingEnabled());
|
||||
QPointF result = path.pointAtPercent(percent);
|
||||
|
||||
QVERIFY(pathFuzzyCompare(point.x() , result.x()));
|
||||
QVERIFY(pathFuzzyCompare(point.y() , result.y()));
|
||||
|
||||
path.setCachingEnabled(true);
|
||||
QVERIFY(path.isCachingEnabled());
|
||||
result = path.pointAtPercent(percent);
|
||||
QVERIFY2(pathFuzzyCompare(point.x() , result.x()), "caching");
|
||||
QVERIFY2(pathFuzzyCompare(point.y() , result.y()), "caching");
|
||||
}
|
||||
|
||||
void tst_QPainterPath::lengths_data()
|
||||
@ -1190,11 +1196,20 @@ void tst_QPainterPath::lengths()
|
||||
QFETCH(qreal, lenAt50);
|
||||
QFETCH(qreal, lenAt75);
|
||||
|
||||
QVERIFY(!path.isCachingEnabled());
|
||||
QVERIFY(pathFuzzyCompare(path.length() / 1000, length / 1000));
|
||||
QVERIFY(pathFuzzyCompare(path.percentAtLength(lenAt25), qreal(0.25)));
|
||||
QVERIFY(pathFuzzyCompare(path.percentAtLength(lenAt50), qreal(0.50)));
|
||||
QVERIFY(pathFuzzyCompare(path.percentAtLength(lenAt75), qreal(0.75)));
|
||||
QVERIFY(pathFuzzyCompare(path.percentAtLength(length), qreal(1)));
|
||||
|
||||
path.setCachingEnabled(true);
|
||||
QVERIFY(path.isCachingEnabled());
|
||||
QVERIFY2(pathFuzzyCompare(path.length() / 1000, length / 1000), "caching");
|
||||
QVERIFY2(pathFuzzyCompare(path.percentAtLength(lenAt25), qreal(0.25)), "caching");
|
||||
QVERIFY2(pathFuzzyCompare(path.percentAtLength(lenAt50), qreal(0.50)), "caching");
|
||||
QVERIFY2(pathFuzzyCompare(path.percentAtLength(lenAt75), qreal(0.75)), "caching");
|
||||
QVERIFY2(pathFuzzyCompare(path.percentAtLength(length), qreal(1)), "caching");
|
||||
}
|
||||
|
||||
void tst_QPainterPath::setElementPositionAt()
|
||||
@ -1226,6 +1241,11 @@ void tst_QPainterPath::angleAtPercent()
|
||||
path.moveTo(line.p1());
|
||||
path.lineTo(line.p2());
|
||||
|
||||
QVERIFY(!path.isCachingEnabled());
|
||||
QCOMPARE(path.angleAtPercent(0.5), line.angle());
|
||||
|
||||
path.setCachingEnabled(true);
|
||||
QVERIFY(path.isCachingEnabled());
|
||||
QCOMPARE(path.angleAtPercent(0.5), line.angle());
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,12 @@ public:
|
||||
private slots:
|
||||
void initTestCase_data();
|
||||
|
||||
void general_data();
|
||||
void length_data() { general_data(); }
|
||||
void length();
|
||||
void percentAtLength_data() { general_data(); }
|
||||
void percentAtLength();
|
||||
void pointAtPercent_data() { general_data(); }
|
||||
void pointAtPercent();
|
||||
|
||||
};
|
||||
@ -62,11 +66,19 @@ void tst_QPainterPath::initTestCase_data()
|
||||
QTest::newRow("2k_text") << p;
|
||||
}
|
||||
|
||||
void tst_QPainterPath::general_data()
|
||||
{
|
||||
QTest::addColumn<bool>("caching");
|
||||
|
||||
QTest::newRow("Uncached") << false;
|
||||
QTest::newRow("Cached") << true;
|
||||
}
|
||||
|
||||
void tst_QPainterPath::length()
|
||||
{
|
||||
QFETCH_GLOBAL(QPainterPath, path);
|
||||
|
||||
//const qreal len = path.length() * 0.72;
|
||||
QFETCH(bool, caching);
|
||||
path.setCachingEnabled(caching);
|
||||
|
||||
QBENCHMARK {
|
||||
path.length();
|
||||
@ -76,6 +88,8 @@ void tst_QPainterPath::length()
|
||||
void tst_QPainterPath::percentAtLength()
|
||||
{
|
||||
QFETCH_GLOBAL(QPainterPath, path);
|
||||
QFETCH(bool, caching);
|
||||
path.setCachingEnabled(caching);
|
||||
|
||||
const qreal len = path.length() * 0.72;
|
||||
|
||||
@ -87,6 +101,8 @@ void tst_QPainterPath::percentAtLength()
|
||||
void tst_QPainterPath::pointAtPercent()
|
||||
{
|
||||
QFETCH_GLOBAL(QPainterPath, path);
|
||||
QFETCH(bool, caching);
|
||||
path.setCachingEnabled(caching);
|
||||
|
||||
const qreal t = 0.72;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user