6#include <private/qsgcurveprocessor_p.h>
8#include <QtGui/private/qbezier_p.h>
10#include <QtCore/QLoggingCategory>
11#include <QtCore/QVarLengthArray>
17 static bool init =
false;
18 const int numSteps = 21;
20 static qreal t2s[numSteps];
21 static qreal tmts[numSteps];
25 const qreal step = (1 - (2 *
t)) / (numSteps - 1);
26 for (
int i = 0;
i < numSteps;
i++) {
28 tmts[
i] = 2 *
t * (1 -
t);
34 const QPointF midPoint =
b.midPoint();
35 auto distForIndex = [&](
int i) ->
qreal {
36 QPointF qp = (t2s[numSteps - 1 -
i] *
b.pt1()) + (tmts[
i] * qcp) + (t2s[
i] *
b.pt4());
41 const int halfSteps = (numSteps - 1) / 2;
43 const qreal centerDist = distForIndex(halfSteps);
44 qreal minDist = centerDist;
46 for (
int i = 0;
i < halfSteps;
i++) {
47 qreal tDist = distForIndex(halfSteps + 1 +
i);
48 if (tDist < minDist) {
58 for (
int i = 0;
i < halfSteps;
i++) {
59 qreal tDist = distForIndex(halfSteps - 1 -
i);
60 if (tDist < minDist) {
68 return foundIt ? minDist : centerDist;
73 const QLineF st =
b.startTangent();
74 const QLineF et =
b.endTangent();
75 const QPointF midPoint =
b.midPoint();
83 const QPointF ml = midPoint -
b.pt1();
84 const QPointF ql = quadControlPoint -
b.pt1();
85 qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
86 qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
87 valid = (std::signbit(cx1) == std::signbit(cx2));
89 return valid ? quadControlPoint : midPoint;
94 auto isValidRoot = [](
qreal r) {
102 xf.rotate(l.
angle());
103 xf.translate(-orig.
pt1().
x(), -orig.
pt1().
y());
107 const qreal x3 =
n.pt3().x();
108 const qreal x4 =
n.pt4().x();
110 const qreal y3 =
n.pt3().y();
117 const qreal a = 18 * ((-3 *
p) + (2 *
q) + (3 *
r) -
s);
119 if (std::signbit(
y2) != std::signbit(y3) &&
qFuzzyCompare(
float(x4 - x3),
float(
x2))) {
126 const qreal b = 18 * (((3 *
p) -
q) - (3 *
r));
132 const qreal root1 = (-
b + sqr) / (2 *
a);
133 const qreal root2 = (-
b - sqr) / (2 *
a);
136 if (isValidRoot(root1))
137 tpoints[
res++] = root1;
138 if (root2 != root1 && isValidRoot(root2))
139 tpoints[
res++] = root2;
141 if (
res == 2 && tpoints[0] > tpoints[1])
142 qSwap(tpoints[0], tpoints[1]);
165 out->append(
b.pt1());
169 const qreal f = 3.0 / 2.0;
170 const QPointF c1 =
b.pt1() +
f * (
b.pt2() -
b.pt1());
174 out->append(
b.pt4());
180 const QPointF dim = cpr.bottomRight() - cpr.topLeft();
185 const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
188 for (
int i = 0;
i < numInfPoints + 1;
i++) {
189 qreal t1 = (
i < numInfPoints) ? infPoints[
i] : 1;
199 return sp +
t * (ep - sp);
201 const float r = 1 -
t;
202 return (
r *
r * sp) + (2 *
t *
r * cp) + (
t *
t * ep);
208 if (t0 <= 0 && t1 >= 1)
212 part.sp = pointAtFraction(
t0);
213 part.ep = pointAtFraction(
t1);
216 part.cp = 0.5f * (part.sp + part.ep);
217 part.m_isLine =
true;
220 const QVector2D rcp = (1 -
t0) * controlPoint() +
t0 * endPoint();
222 float segmentT = (
t1 -
t0) / (1 -
t0);
223 part.cp = (1 - segmentT) * part.sp + segmentT * rcp;
230 swappedElement.ep =
sp;
231 swappedElement.cp = cp;
232 swappedElement.sp = ep;
233 swappedElement.m_isLine = m_isLine;
234 return swappedElement;
246 return (max - min).length();
251int QQuadPath::Element::intersectionsAtY(
float y,
float *fractions,
bool swapXY)
const
255 auto getY = [=](
QVector2D p) ->
float {
return swapXY ? -
p.x() :
p.y(); };
257 const float y0 = getY(startPoint()) -
y;
258 const float y1 = getY(controlPoint()) -
y;
259 const float y2 = getY(endPoint()) -
y;
262 const float a =
y0 - (2 *
y1) +
y2;
266 const float sqr =
qSqrt(
b);
267 const float root1 = -(-
y0 +
y1 + sqr) /
a;
268 if (
qIsFinite(root1) && root1 >= 0 && root1 <= 1)
269 fractions[numRoots++] = root1;
270 const float root2 = (
y0 -
y1 + sqr) /
a;
271 if (
qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
272 fractions[numRoots++] = root2;
274 }
else if (
y1 !=
y2) {
275 const float root1 = (
y2 - (2 *
y1)) / (2 * (
y2 -
y1));
276 if (
qIsFinite(root1) && root1 >= 0 && root1 <= 1)
277 fractions[numRoots++] = root1;
287 return (
v2.x() *
v1.y()) - (
v2.y() *
v1.x());
306 constexpr float epsilon = 0.01f;
332 int winding_number = 0;
333 for (
int ei = fromIndex; ei <=
toIndex; ei++) {
343 if (point.
y() <
y1 || point.
y() >=
y2 ||
y1 ==
y2)
348 winding_number +=
dir;
352 if (point.
y() <
y1 || point.
y() >=
y2)
355 const int numRoots = e.intersectionsAtY(point.
y(), ts);
359 for (
int i = 0;
i < numRoots;
i++) {
367 winding_number +=
dir;
380 constexpr float toleranceT = 1e-3f;
381 const QVector2D point = m_elements.
at(elementIdx).pointAtFraction(elementT);
382 const QVector2D tangent = m_elements.
at(elementIdx).tangentAtFraction(elementT);
384 const bool swapXY =
qAbs(tangent.
x()) >
qAbs(tangent.
y());
385 auto getX = [=](
QVector2D p) ->
float {
return swapXY ?
p.y() :
p.x(); };
386 auto getY = [=](
QVector2D p) ->
float {
return swapXY ? -
p.x() :
p.y(); };
388 int winding_number = 0;
399 if (getY(point) <
y1 || getY(point) >=
y2 ||
y1 ==
y2)
403 if (
x <= getX(point) && (
i != elementIdx ||
qAbs(
t - elementT) > toleranceT))
404 winding_number +=
dir;
408 if (getY(point) <
y1 || getY(point) >=
y2)
411 const int numRoots = e.intersectionsAtY(getY(point), ts, swapXY);
415 for (
int j = 0;
j < numRoots;
j++) {
417 if (
x <= getX(point) && (
i != elementIdx ||
qAbs(ts[
j] - elementT) > toleranceT)) {
424 winding_number +=
dir;
429 int left_winding_number = winding_number;
430 int right_winding_number = winding_number;
432 int dir = getY(tangent) < 0 ? -1 : 1;
435 left_winding_number +=
dir;
437 right_winding_number +=
dir;
439 bool leftInside = (
fillRule() ==
Qt::WindingFill ? (left_winding_number != 0) : ((left_winding_number % 2) != 0));
440 bool rightInside = (
fillRule() ==
Qt::WindingFill ? (right_winding_number != 0) : ((right_winding_number % 2) != 0));
442 if (leftInside && rightInside)
443 return QQuadPath::Element::FillSideBoth;
445 return QQuadPath::Element::FillSideLeft;
446 else if (rightInside)
447 return QQuadPath::Element::FillSideRight;
449 return QQuadPath::Element::FillSideUndetermined;
461 elem.sp = m_currentPoint;
462 elem.cp =
isLine ? (0.5f * (m_currentPoint + endPoint)) : control;
465 elem.m_isSubpathStart = m_subPathToStart;
466 m_subPathToStart =
false;
467 m_currentPoint = endPoint;
470void QQuadPath::addElement(
const Element &e)
472 m_subPathToStart =
false;
473 m_currentPoint = e.endPoint();
477#if !defined(QQUADPATH_CONVEX_CHECK_ERROR_MARGIN)
478# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f / 32.0f)
481QQuadPath::Element::CurvatureFlags QQuadPath::coordinateOrderOfElement(
const QQuadPath::Element &element)
const
488 QVector2D justRightOfMid = midPoint + (normal * delta);
489 bool pathContainsPoint =
contains(justRightOfMid);
490 return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
497 res.setFillRule(
path.fillRule());
503 for (
int i = 0;
i <
path.elementCount(); ++
i) {
507 switch (element.
type) {
517 ep =
path.elementAt(++
i);
519 const qreal f = 3.0 / 2.0;
525 for (
int i = 1;
i < quads.size();
i += 2) {
557 Element::CurvatureFlags
flags = Element::CurvatureUndetermined;
559 Q_ASSERT(element.childCount() == 0);
560 if (element.isSubpathStart()) {
561 if (pathHasFillOnRight && !checkAnomaly)
562 flags = Element::FillOnRight;
564 flags = coordinateOrderOfElement(element);
565 }
else if (checkAnomaly) {
566 Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
567 if (
flags != newFlags) {
568 qDebug() <<
"Curvature anomaly detected:" << element
569 <<
"Subpath fill on right:" << (
flags & Element::FillOnRight)
570 <<
"Element fill on right:" << (newFlags & Element::FillOnRight);
575 if (element.isLine()) {
576 element.m_curvatureFlags =
flags;
578 bool controlPointOnLeft = element.isControlPointOnLeft();
579 bool isFillOnRight =
flags & Element::FillOnRight;
580 bool isConvex = controlPointOnLeft == isFillOnRight;
583 element.m_curvatureFlags = Element::CurvatureFlags(
flags | Element::Convex);
585 element.m_curvatureFlags =
flags;
598 min.setX(std::min({ min.x(), e.sp.
x(), e.cp.
x(), e.ep.
x() }));
599 min.setY(std::min({ min.y(), e.sp.
y(), e.cp.
y(), e.ep.
y() }));
600 max.
setX(std::max({ max.
x(), e.sp.
x(), e.cp.
x(), e.ep.
x() }));
601 max.
setY(std::max({ max.
y(), e.sp.
y(), e.cp.
y(), e.ep.
y() }));
622 for (
const Element &element : m_elements) {
623 if (element.m_isSubpathStart)
624 res.moveTo(element.startPoint().toPointF());
625 if (element.m_isLine)
626 res.lineTo(element.endPoint().toPointF());
628 res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
637 for (
const Element &element : m_elements) {
638 if (element.isSubpathStart())
639 str <<
"M " << element.startPoint().x() <<
" " << element.startPoint().y() <<
" ";
640 if (element.isLine())
641 str <<
"L " << element.endPoint().x() <<
" " << element.endPoint().y() <<
" ";
643 str <<
"Q " << element.controlPoint().x() <<
" " << element.controlPoint().y() <<
" "
644 << element.endPoint().x() <<
" " << element.endPoint().y() <<
" ";
658 res.m_subPathToStart =
false;
662 int prevElement = -1;
664 const auto &element = m_elements.
at(
i);
665 if (element.m_isSubpathStart) {
666 if (subStart >= 0 && m_elements[
i - 1].ep != m_elements[subStart].
sp) {
667 res.m_currentPoint = m_elements[
i - 1].ep;
668 res.lineTo(m_elements[subStart].
sp);
670 auto &endElement =
res.m_elements.
last();
671 endElement.m_isSubpathEnd =
true;
675 endElement.ep = m_elements[subStart].sp;
676 }
else if (prevElement >= 0) {
677 res.m_elements[prevElement].m_isSubpathEnd =
true;
682 prevElement =
res.m_elements.size() - 1;
685 if (subStart >= 0 && m_elements.
last().ep != m_elements[subStart].sp) {
686 res.m_currentPoint = m_elements.
last().ep;
687 res.lineTo(m_elements[subStart].
sp);
690 if (!
res.m_elements.isEmpty()) {
691 auto &endElement =
res.m_elements.last();
692 endElement.m_isSubpathEnd =
true;
693 endElement.ep = m_elements[subStart].sp;
717 m_currentPoint = m_element.startPoint();
718 if (m_element.isLine())
719 m_lineLength = (m_element.endPoint() - m_element.startPoint()).
length();
726 m_lastT = m_currentT;
727 m_lastPoint = m_currentPoint;
728 float nextCut = m_consumed +
length;
729 float cutT = m_element.isLine() ? nextCut / m_lineLength : tForLength(nextCut);
732 m_currentPoint = m_element.pointAtFraction(m_currentT);
733 m_consumed = nextCut;
737 m_currentPoint = m_element.endPoint();
744 return m_currentPoint;
751 QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
753 float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
754 QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
760 float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
761 return elemLength - m_consumed;
768 QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
769 QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
773 float b =
B / (2 *
A);
775 float k =
c - (
b *
b);
776 float l2 =
b * std::sqrt(
b *
b + k);
777 float lnom =
b + std::sqrt(
b *
b + k);
778 float l0 = 0.5f * std::sqrt(
A);
780 m_lut.resize(LUTSize, 0);
781 for (
int i = 1;
i < LUTSize;
i++) {
782 float t = float(
i) / (LUTSize - 1);
784 float w = std::sqrt(u * u + k);
787 float l3 = k * std::log(std::fabs(lden / lnom));
788 float res = l0 * (l1 - l2 + l3);
793 float tForLength(
float length)
799 auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(),
length);
801 float nextLength = *
it--;
802 float prevLength = *
it;
803 int prevIndex = std::distance(m_lut.cbegin(),
it);
804 float fraction = (
length - prevLength) / (nextLength - prevLength);
805 res = (prevIndex + fraction) / (LUTSize - 1);
812 float m_currentT = 0;
815 float m_consumed = 0;
819 static constexpr int LUTSize = 21;
820 QVarLengthArray<float, LUTSize> m_lut;
825 QVarLengthArray<float, 16>
pattern;
826 float patternLength = 0;
827 for (
int i = 0;
i < 2 * (dashPattern.length() / 2);
i++) {
828 float dashLength =
qMax(lineWidth * dashPattern[
i],
qreal(0));
830 patternLength += dashLength;
832 if (patternLength == 0)
836 float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
838 startOffset += patternLength;
839 for (
float dashLength :
pattern) {
840 if (dashLength > startOffset)
842 startIndex = (startIndex + 1) %
pattern.size();
843 startOffset -= dashLength;
846 int dashIndex = startIndex;
847 float offset = startOffset;
853 dashIndex = startIndex;
858 bool gotAll = cutter.consume(
pattern.at(dashIndex) -
offset);
859 QVector2D nextPoint = cutter.currentCutPoint();
861 res.moveTo(nextPoint);
862 else if (element.
isLine())
863 res.lineTo(nextPoint);
865 res.quadTo(cutter.currentControlPoint(), nextPoint);
868 dashIndex = (dashIndex + 1) %
pattern.size();
870 offset += cutter.lastLength();
882 const int newChildIndex = m_childElements.
size();
883 m_childElements.
resize(newChildIndex + 2);
885 parent.m_numChildren = 2;
886 parent.m_firstChildIndex = newChildIndex;
888 Element &quad1 = m_childElements[newChildIndex];
890 quad1.sp = parent.sp;
891 quad1.cp = 0.5f * (parent.sp + parent.cp);
893 quad1.m_isSubpathStart = parent.m_isSubpathStart;
894 quad1.m_isSubpathEnd =
false;
895 quad1.m_curvatureFlags = parent.m_curvatureFlags;
896 quad1.m_isLine = parent.m_isLine;
898 Element &quad2 = m_childElements[newChildIndex + 1];
900 quad2.cp = 0.5f * (parent.ep + parent.cp);
901 quad2.ep = parent.ep;
902 quad2.m_isSubpathStart =
false;
903 quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
904 quad2.m_curvatureFlags = parent.m_curvatureFlags;
905 quad2.m_isLine = parent.m_isLine;
909 qCDebug(lcSGCurveProcessor) <<
"Splitting has resulted in ~null quad";
915 auto printPoint = [&](
QVector2D p) {
stream <<
"(" <<
p.x() <<
", " <<
p.y() <<
") "; };
928 stream <<
"QuadPath::Element( ";
938 stream <<
"QuadPath(" <<
path.elementCount() <<
" main elements, "
939 <<
path.elementCountRecursive() <<
" leaf elements, "
bool consume(float length)
QVector2D currentControlPoint()
QVector2D currentCutPoint()
ElementCutter(const QQuadPath::Element &element)
static QBezier fromPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
void parameterSplitLeft(qreal t, QBezier *left)
QBezier bezierOnInterval(qreal t0, qreal t1) const
QBezier mapBy(const QTransform &transform) const
\inmodule QtCore\compares equality \compareswith equality QLine \endcompareswith
qsizetype size() const noexcept
bool isEmpty() const noexcept
const_reference at(qsizetype i) const noexcept
const T & constFirst() const noexcept
void resize(qsizetype size)
void append(parameter_type t)
void reserve(int size)
Reserves a given amount of elements in QPainterPath's internal memory.
\inmodule QtCore\reentrant
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
static constexpr qreal dotProduct(const QPointF &p1, const QPointF &p2)
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
The QPolygonF class provides a list of points using floating point precision.
bool isSubpathStart() const
Element segmentFromTo(float t0, float t1) const
QVector2D tangentAtFraction(float t) const
bool isSubpathEnd() const
QVector2D startPoint() const
QVector2D midPoint() const
QVector2D endPoint() const
QVector2D pointAtFraction(float t) const
QVector2D controlPoint() const
QQuadPath flattened() const
QPainterPath toPainterPath() const
static QVector2D closestPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
Element & elementAt(int i)
bool contains(const QVector2D &point) const
QString asSvgString() const
void iterateElements(Func &&lambda)
static bool isPointOnLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
void splitElementAt(int index)
QQuadPath subPathsClosed(bool *didClose=nullptr) const
static bool isPointOnLeft(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
static bool isPointNearLine(const QVector2D &p, const QVector2D &sp, const QVector2D &ep)
int elementCountRecursive() const
QQuadPath dashed(qreal lineWidth, const QList< qreal > &dashPattern, qreal dashOffset=0) const
PathHints pathHints() const
QRectF controlPointRect() const
Element::FillSide fillSideOf(int elementIdx, float elementT) const
Qt::FillRule fillRule() const
bool testHint(PathHint hint) const
static QQuadPath fromPainterPath(const QPainterPath &path, PathHints hints={})
friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &)
\inmodule QtCore\reentrant
const_iterator cend() const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
The QVector2D class represents a vector or vertex in 2D space.
constexpr float y() const noexcept
Returns the y coordinate of this point.
QVector2D normalized() const noexcept
Returns the normalized unit vector form of this vector.
constexpr float x() const noexcept
Returns the x coordinate of this point.
static constexpr float dotProduct(QVector2D v1, QVector2D v2) noexcept
Returns the dot product of v1 and v2.
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
constexpr QPointF toPointF() const noexcept
Returns the QPointF form of this 2D vector.
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
QSet< QString >::iterator it
Combined button and popup list for selecting options.
QTextStream & endl(QTextStream &stream)
Writes '\n' to the stream and flushes the stream.
#define Q_STATIC_ASSERT(Condition)
bool qIsFinite(qfloat16 f) noexcept
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
bool qFuzzyIsNull(qfloat16 f) noexcept
qfloat16 qSqrt(qfloat16 f)
#define qCDebug(category,...)
QT_BEGIN_NAMESPACE constexpr const T & qMin(const T &a, const T &b)
constexpr const T & qBound(const T &min, const T &val, const T &max)
constexpr const T & qMax(const T &a, const T &b)
constexpr T qAbs(const T &t)
GLint GLfloat GLfloat GLfloat v2
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint GLfloat GLfloat GLfloat GLfloat y1
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
GLenum GLuint GLintptr offset
GLuint GLfloat GLfloat y0
GLfixed GLfixed GLfixed y2
GLdouble GLdouble GLdouble GLdouble q
GLsizei const GLchar *const * path
static bool isLine(const QBezier &bezier)
static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
static QPointF qt_quadraticForCubic(const QBezier &b)
static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
static void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit=0.01)
static QT_BEGIN_NAMESPACE qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
static void printElement(QDebug stream, const QQuadPath::Element &element)
#define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN
static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
static const qreal epsilon
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
static uint toIndex(ExecutionEngine *e, const Value &v)
QTextStream out(stdout)
[7]