6#include <private/qsgcurveprocessor_p.h>
8#include <QtGui/private/qbezier_p.h>
10#include <QtCore/QLoggingCategory>
11#include <QtCore/QVarLengthArray>
18 constexpr auto precomputed = [] {
20 constexpr int numSteps = 21;
21 static_assert(numSteps % 2 == 1,
"numSteps must be odd");
23 std::array<qreal, numSteps> t2s, tmts;
25 auto &[t2s, tmts] = r;
27 const qreal step = (1 - (2 * t)) / (numSteps - 1);
28 for (
int i = 0; i < numSteps; i++) {
30 tmts[i] = 2 * t * (1 - t);
35 constexpr auto t2s = precomputed.t2s;
36 constexpr auto tmts = precomputed.tmts;
37 constexpr auto numSteps = t2s.size();
39 const QPointF midPoint = b.midPoint();
40 auto distForIndex = [&](
int i) -> qreal {
41 QPointF qp = (t2s[numSteps - 1 - i] * b.pt1()) + (tmts[i] * qcp) + (t2s[i] * b.pt4());
42 QPointF d = midPoint - qp;
43 return QPointF::dotProduct(d, d);
46 const int halfSteps = (numSteps - 1) / 2;
48 const qreal centerDist = distForIndex(halfSteps);
49 qreal minDist = centerDist;
51 for (
int i = 0; i < halfSteps; i++) {
52 qreal tDist = distForIndex(halfSteps + 1 + i);
53 if (tDist < minDist) {
63 for (
int i = 0; i < halfSteps; i++) {
64 qreal tDist = distForIndex(halfSteps - 1 - i);
65 if (tDist < minDist) {
73 return foundIt ? minDist : centerDist;
78 const QLineF st = b.startTangent();
79 const QLineF et = b.endTangent();
80 const QPointF midPoint = b.midPoint();
82 QPointF quadControlPoint;
83 if (st.intersects(et, &quadControlPoint) == QLineF::NoIntersection) {
87 const QPointF bl = b.pt4() - b.pt1();
88 const QPointF ml = midPoint - b.pt1();
89 const QPointF ql = quadControlPoint - b.pt1();
90 qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
91 qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
92 valid = (std::signbit(cx1) == std::signbit(cx2));
94 return valid ? quadControlPoint : midPoint;
99 auto isValidRoot = [](qreal r) {
100 return qIsFinite(r) && (r > 0) && (!qFuzzyIsNull(
float(r))) && (r < 1)
101 && (!qFuzzyIsNull(
float(r - 1)));
106 const QLineF l(orig.pt1(), orig.pt4());
107 xf.rotate(l.angle());
108 xf.translate(-orig.pt1().x(), -orig.pt1().y());
109 const QBezier n = orig.mapBy(xf);
111 const qreal x2 = n.pt2().x();
112 const qreal x3 = n.pt3().x();
113 const qreal x4 = n.pt4().x();
114 const qreal y2 = n.pt2().y();
115 const qreal y3 = n.pt3().y();
117 const qreal p = x3 * y2;
118 const qreal q = x4 * y2;
119 const qreal r = x2 * y3;
120 const qreal s = x4 * y3;
122 const qreal a = 18 * ((-3 * p) + (2 * q) + (3 * r) - s);
123 if (qFuzzyIsNull(
float(a))) {
124 if (std::signbit(y2) != std::signbit(y3) && qFuzzyCompare(
float(x4 - x3),
float(x2))) {
131 const qreal b = 18 * (((3 * p) - q) - (3 * r));
132 const qreal c = 18 * (r - p);
133 const qreal rad = (b * b) - (4 * a * c);
136 const qreal sqr = qSqrt(rad);
137 const qreal root1 = (-b + sqr) / (2 * a);
138 const qreal root2 = (-b - sqr) / (2 * a);
141 if (isValidRoot(root1))
142 tpoints[res++] = root1;
143 if (root2 != root1 && isValidRoot(root2))
144 tpoints[res++] = root2;
146 if (res == 2 && tpoints[0] > tpoints[1])
147 qSwap(tpoints[0], tpoints[1]);
154 QPointF qcp = qt_quadraticForCubic(b);
155 if (maxSplits <= 0 || qt_scoreQuadratic(b, qcp) < maxDiff) {
161 rhs.parameterSplitLeft(0.5, &lhs);
162 qt_addToQuadratics(lhs, p, maxSplits - 1, maxDiff);
163 qt_addToQuadratics(rhs, p, maxSplits - 1, maxDiff);
170 out->append(b.pt1());
174 const qreal f = 3.0 / 2.0;
175 const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
176 const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
179 out->append(b.pt4());
184 const QRectF cpr = b.bounds();
185 const QPointF dim = cpr.bottomRight() - cpr.topLeft();
186 qreal maxDiff = QPointF::dotProduct(dim, dim) * errorLimit * errorLimit;
189 int numInfPoints = qt_getInflectionPoints(b, infPoints);
190 const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
193 for (
int i = 0; i < numInfPoints + 1; i++) {
194 qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
195 QBezier segment = b.bezierOnInterval(t0, t1);
196 qt_addToQuadratics(segment, out, maxSubSplits, maxDiff);
201QVector2D QQuadPath::Element::pointAtFraction(
float t)
const
204 return sp + t * (ep - sp);
206 const float r = 1 - t;
207 return (r * r * sp) + (2 * t * r * cp) + (t * t * ep);
211QQuadPath::Element QQuadPath::Element::segmentFromTo(
float t0,
float t1)
const
213 if (t0 <= 0 && t1 >= 1)
217 part.sp = pointAtFraction(t0);
218 part.ep = pointAtFraction(t1);
221 part.cp = 0.5f * (part.sp + part.ep);
222 part.m_isLine =
true;
225 const QVector2D rcp = (1 - t0) * controlPoint() + t0 * endPoint();
227 float segmentT = (t1 - t0) / (1 - t0);
228 part.cp = (1 - segmentT) * part.sp + segmentT * rcp;
233QQuadPath::Element QQuadPath::Element::reversed()
const {
234 Element swappedElement;
235 swappedElement.ep = sp;
236 swappedElement.cp = cp;
237 swappedElement.sp = ep;
238 swappedElement.m_isLine = m_isLine;
239 return swappedElement;
242float QQuadPath::Element::extent()
const
245 QVector2D min(qMin(sp.x(), ep.x()), qMin(sp.y(), ep.y()));
246 QVector2D max(qMax(sp.x(), ep.x()), qMax(sp.y(), ep.y()));
248 min = QVector2D(qMin(min.x(), cp.x()), qMin(min.y(), cp.y()));
249 max = QVector2D(qMax(max.x(), cp.x()), qMax(max.y(), cp.y()));
251 return (max - min).length();
256int QQuadPath::Element::intersectionsAtY(
float y,
float *fractions,
bool swapXY)
const
260 auto getY = [=](QVector2D p) ->
float {
return swapXY ? -p.x() : p.y(); };
262 const float y0 = getY(startPoint()) - y;
263 const float y1 = getY(controlPoint()) - y;
264 const float y2 = getY(endPoint()) - y;
267 const float a = y0 - (2 * y1) + y2;
269 const float b = (y1 * y1) - (y0 * y2);
271 const float sqr = qSqrt(b);
272 const float root1 = -(-y0 + y1 + sqr) / a;
273 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
274 fractions[numRoots++] = root1;
275 const float root2 = (y0 - y1 + sqr) / a;
276 if (qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
277 fractions[numRoots++] = root2;
279 }
else if (y1 != y2) {
280 const float root1 = (y2 - (2 * y1)) / (2 * (y2 - y1));
281 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
282 fractions[numRoots++] = root1;
288static float crossProduct(
const QVector2D &sp,
const QVector2D &p,
const QVector2D &ep)
290 QVector2D v1 = ep - sp;
291 QVector2D v2 = p - sp;
292 return (v2.x() * v1.y()) - (v2.y() * v1.x());
295bool QQuadPath::isPointOnLeft(
const QVector2D &p,
const QVector2D &sp,
const QVector2D &ep)
298 return crossProduct(sp, p, ep) >= 0.0f;
301bool QQuadPath::isPointOnLine(
const QVector2D &p,
const QVector2D &sp,
const QVector2D &ep)
303 return qFuzzyIsNull(crossProduct(sp, p, ep));
307bool QQuadPath::isPointNearLine(
const QVector2D &p,
const QVector2D &sp,
const QVector2D &ep)
311 constexpr float epsilon = 0.01f;
312 QVector2D bv = ep - sp;
313 float bl2 = QVector2D::dotProduct(bv, bv);
314 float t = QVector2D::dotProduct(p - sp, bv) / bl2;
315 QVector2D pv = p - (sp + t * bv);
316 return (QVector2D::dotProduct(pv, pv) / bl2) < (epsilon * epsilon);
319QVector2D QQuadPath::closestPointOnLine(
const QVector2D &p,
const QVector2D &sp,
const QVector2D &ep)
321 QVector2D line = ep - sp;
322 float t = QVector2D::dotProduct(p - sp, line) / QVector2D::dotProduct(line, line);
323 return sp + qBound(0.0f, t, 1.0f) * line;
327bool QQuadPath::contains(
const QVector2D &point)
const
329 return contains(point, 0, elementCount() - 1);
332bool QQuadPath::contains(
const QVector2D &point,
int fromIndex,
int toIndex)
const
337 int winding_number = 0;
338 for (
int ei = fromIndex; ei <= toIndex; ei++) {
339 const Element &e = m_elements.at(ei);
341 float y1 = e.startPoint().y();
342 float y2 = e.endPoint().y();
348 if (point.y() < y1 || point.y() >= y2 || y1 == y2)
350 const float t = (point.y() - e.startPoint().y()) / (e.endPoint().y() - e.startPoint().y());
351 const float x = e.startPoint().x() + t * (e.endPoint().x() - e.startPoint().x());
353 winding_number += dir;
355 y1 = qMin(y1, e.controlPoint().y());
356 y2 = qMax(y2, e.controlPoint().y());
357 if (point.y() < y1 || point.y() >= y2)
360 const int numRoots = e.intersectionsAtY(point.y(), ts);
364 for (
int i = 0; i < numRoots; i++) {
365 if (e.pointAtFraction(ts[i]).x() <= point.x()) {
371 dir = e.tangentAtFraction(tForHit).y() < 0 ? -1 : 1;
372 winding_number += dir;
377 return (fillRule() == Qt::WindingFill ? (winding_number != 0) : ((winding_number % 2) != 0));
383QQuadPath::Element::FillSide QQuadPath::fillSideOf(
int elementIdx,
float elementT)
const
385 constexpr float toleranceT = 1e-3f;
386 const QVector2D point = m_elements.at(elementIdx).pointAtFraction(elementT);
387 const QVector2D tangent = m_elements.at(elementIdx).tangentAtFraction(elementT);
389 const bool swapXY = qAbs(tangent.x()) > qAbs(tangent.y());
390 auto getX = [=](QVector2D p) ->
float {
return swapXY ? p.y() : p.x(); };
391 auto getY = [=](QVector2D p) ->
float {
return swapXY ? -p.x() : p.y(); };
393 int winding_number = 0;
394 for (
int i = 0; i < elementCount(); i++) {
395 const Element &e = m_elements.at(i);
397 float y1 = getY(e.startPoint());
398 float y2 = getY(e.endPoint());
404 if (getY(point) < y1 || getY(point) >= y2 || y1 == y2)
406 const float t = (getY(point) - getY(e.startPoint())) / (getY(e.endPoint()) - getY(e.startPoint()));
407 const float x = getX(e.startPoint()) + t * (getX(e.endPoint()) - getX(e.startPoint()));
408 if (x <= getX(point) && (i != elementIdx || qAbs(t - elementT) > toleranceT))
409 winding_number += dir;
411 y1 = qMin(y1, getY(e.controlPoint()));
412 y2 = qMax(y2, getY(e.controlPoint()));
413 if (getY(point) < y1 || getY(point) >= y2)
416 const int numRoots = e.intersectionsAtY(getY(point), ts, swapXY);
420 for (
int j = 0; j < numRoots; j++) {
421 const float x = getX(e.pointAtFraction(ts[j]));
422 if (x <= getX(point) && (i != elementIdx || qAbs(ts[j] - elementT) > toleranceT)) {
428 dir = getY(e.tangentAtFraction(tForHit)) < 0 ? -1 : 1;
429 winding_number += dir;
434 int left_winding_number = winding_number;
435 int right_winding_number = winding_number;
437 int dir = getY(tangent) < 0 ? -1 : 1;
440 left_winding_number += dir;
442 right_winding_number += dir;
444 bool leftInside = (fillRule() == Qt::WindingFill ? (left_winding_number != 0) : ((left_winding_number % 2) != 0));
445 bool rightInside = (fillRule() == Qt::WindingFill ? (right_winding_number != 0) : ((right_winding_number % 2) != 0));
447 if (leftInside && rightInside)
448 return QQuadPath::Element::FillSideBoth;
450 return QQuadPath::Element::FillSideLeft;
451 else if (rightInside)
452 return QQuadPath::Element::FillSideRight;
454 return QQuadPath::Element::FillSideUndetermined;
457void QQuadPath::addElement(
const QVector2D &control,
const QVector2D &endPoint,
bool isLine)
459 if (qFuzzyCompare(m_currentPoint, endPoint))
462 isLine = isLine || isPointNearLine(control, m_currentPoint, endPoint);
464 if (!m_subPathToStart) {
465 Q_ASSERT(!m_elements.isEmpty());
466 m_elements.last().m_isSubpathEnd =
false;
468 m_elements.resize(m_elements.size() + 1);
469 Element &elem = m_elements.last();
470 elem.sp = m_currentPoint;
471 elem.cp = isLine ? (0.5f * (m_currentPoint + endPoint)) : control;
473 elem.m_isLine = isLine;
474 elem.m_isSubpathStart = m_subPathToStart;
475 m_subPathToStart =
false;
476 elem.m_isSubpathEnd =
true;
477 m_currentPoint = endPoint;
480void QQuadPath::addElement(
const Element &e)
482 m_subPathToStart =
false;
483 m_currentPoint = e.endPoint();
484 m_elements.append(e);
487#if !defined(QQUADPATH_CONVEX_CHECK_ERROR_MARGIN)
488# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f
/ 32.0f
)
491QQuadPath::Element::FillSide QQuadPath::coordinateOrderOfElement(
const QQuadPath::Element &element)
const
493 QVector2D baseLine = element.endPoint() - element.startPoint();
494 QVector2D midPoint = element.midPoint();
496 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
498 QVector2D offset = (normal * delta);
499 bool pathContainsPointToRight = contains(midPoint + offset);
500 bool pathContainsPointToLeft = contains(midPoint - offset);
501 Element::FillSide res = Element::FillSideUndetermined;
502 if (pathContainsPointToRight)
503 res = (pathContainsPointToLeft ? Element::FillSideBoth : Element::FillSideRight);
504 else if (pathContainsPointToLeft)
505 res = Element::FillSideLeft;
509QQuadPath QQuadPath::fromPainterPath(
const QPainterPath &path, PathHints hints)
512 res.reserve(path.elementCount());
513 res.setFillRule(path.fillRule());
515 const bool isQuadratic = hints & PathQuadratic;
519 for (
int i = 0; i < path.elementCount(); ++i) {
520 QPainterPath::Element element = path.elementAt(i);
523 switch (element.type) {
524 case QPainterPath::MoveToElement:
525 res.moveTo(QVector2D(ep));
527 case QPainterPath::LineToElement:
528 res.lineTo(QVector2D(ep));
530 case QPainterPath::CurveToElement: {
532 QPointF cp2(path.elementAt(++i));
533 ep = path.elementAt(++i);
535 const qreal f = 3.0 / 2.0;
536 const QPointF cp = sp + f * (cp1 - sp);
537 res.quadTo(QVector2D(cp), QVector2D(ep));
539 QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
540 qt_toQuadratics(b, &quads);
541 for (
int i = 1; i < quads.size(); i += 2) {
542 QVector2D cp(quads[i]);
543 QVector2D ep(quads[i + 1]);
556 res.setPathHints(hints | PathQuadratic);
562 QVarLengthArray<
int, 16> lines;
563 for (
int i = 0; i < path.elementCount(); i++) {
564 const QQuadPath::Element &e = path.elementAt(i);
566 const QVector2D tangent = e.tangentAtFraction(0);
567 const bool isHorizontal = qAbs(tangent.x()) > qAbs(tangent.y());
568 float e1 = isHorizontal ? e.startPoint().x() : e.startPoint().y();
569 float e2 = isHorizontal ? e.endPoint().x() : e.endPoint().y();
572 for (
int j = 0; j < lines.size(); j++) {
573 const QQuadPath::Element &line = path.elementAt(j);
574 if (QQuadPath::isPointOnLine(e.controlPoint(), line.startPoint(), line.endPoint())) {
576 float l1 = isHorizontal ? line.startPoint().x() : line.startPoint().y();
577 float l2 = isHorizontal ? line.endPoint().x() : line.endPoint().y();
580 if (qMax(l1, e1) <= qMin(l2, e2))
590void QQuadPath::addCurvatureData()
600 auto isSingleSided = [](Element::FillSide fillSide) {
601 return fillSide == Element::FillSideLeft || fillSide == Element::FillSideRight;
604 auto flagFromFillSide = [](Element::FillSide fillSide) {
605 if (fillSide == Element::FillSideRight || fillSide == Element::FillSideBoth)
606 return Element::FillOnRight;
608 return Element::CurvatureUndetermined;
611 static bool checkAnomalyEnv = qEnvironmentVariableIntValue(
"QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
612 constexpr int overlapTestLimit = 64;
614 const bool checkAnomaly =
615 checkAnomalyEnv || (lineCount() <= overlapTestLimit && hasOverlappingLines(*
this));
616 const bool pathHasFillOnRight = testHint(PathFillOnRight);
618 Element::CurvatureFlags flags = Element::CurvatureUndetermined;
619 for (
int i = 0; i < m_elements.size(); i++) {
620 QQuadPath::Element &element = m_elements[i];
621 Q_ASSERT(element.childCount() == 0);
622 if (element.isSubpathStart()) {
623 if (pathHasFillOnRight && !checkAnomaly) {
624 flags = Element::FillOnRight;
626 Element::FillSide fillSide = Element::FillSideUndetermined;
627 for (
int j = i; !isSingleSided(fillSide) && j < m_elements.size(); j++) {
628 const QQuadPath::Element &subElem = m_elements.at(j);
629 if (j > i && subElem.isSubpathStart())
631 fillSide = coordinateOrderOfElement(subElem);
633 flags = flagFromFillSide(fillSide);
635 }
else if (checkAnomaly) {
636 Element::FillSide fillSide = coordinateOrderOfElement(element);
637 if (isSingleSided(fillSide)) {
638 Element::CurvatureFlags newFlags = flagFromFillSide(fillSide);
639 if (flags != newFlags) {
640 qCDebug(lcSGCurveProcessor)
641 <<
"Curvature anomaly detected:" << element
642 <<
"Subpath fill on right:" << (flags & Element::FillOnRight)
643 <<
"Element fill on right:" << (newFlags & Element::FillOnRight);
649 if (element.isLine()) {
650 element.m_curvatureFlags = flags;
652 bool controlPointOnLeft = element.isControlPointOnLeft();
653 bool isFillOnRight = flags & Element::FillOnRight;
654 bool isConvex = controlPointOnLeft == isFillOnRight;
657 element.m_curvatureFlags = Element::CurvatureFlags(flags | Element::Convex);
659 element.m_curvatureFlags = flags;
664QRectF QQuadPath::controlPointRect()
const
667 if (elementCount()) {
669 min = max = m_elements.constFirst().sp;
671 for (
const QQuadPath::Element &e : std::as_const(m_elements)) {
672 min.setX(std::min({ min.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
673 min.setY(std::min({ min.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
674 max.setX(std::max({ max.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
675 max.setY(std::max({ max.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
677 res = QRectF(min.toPointF(), max.toPointF());
683int QQuadPath::elementCountRecursive()
const
686 iterateElements([&](
const QQuadPath::Element &,
int) { count++; });
690QPainterPath QQuadPath::toPainterPath()
const
694 res.reserve(elementCount());
695 res.setFillRule(fillRule());
696 for (
const Element &element : m_elements) {
697 if (element.m_isSubpathStart)
698 res.moveTo(element.startPoint().toPointF());
699 if (element.m_isLine)
700 res.lineTo(element.endPoint().toPointF());
702 res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
707QString QQuadPath::asSvgString()
const
710 QTextStream str(&res);
711 for (
const Element &element : m_elements) {
712 if (element.isSubpathStart())
713 str <<
"M " << element.startPoint().x() <<
" " << element.startPoint().y() <<
" ";
714 if (element.isLine())
715 str <<
"L " << element.endPoint().x() <<
" " << element.endPoint().y() <<
" ";
717 str <<
"Q " << element.controlPoint().x() <<
" " << element.controlPoint().y() <<
" "
718 << element.endPoint().x() <<
" " << element.endPoint().y() <<
" ";
727QQuadPath QQuadPath::subPathsClosed(
bool *didClose)
const
729 Q_ASSERT(m_childElements.isEmpty());
731 QQuadPath res = *
this;
732 res.m_subPathToStart =
false;
734 res.m_elements.reserve(elementCount());
736 int prevElement = -1;
737 for (
int i = 0; i < elementCount(); i++) {
738 const auto &element = m_elements.at(i);
739 if (element.m_isSubpathStart) {
740 if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
741 res.m_currentPoint = m_elements[i - 1].ep;
742 res.lineTo(m_elements[subStart].sp);
744 auto &endElement = res.m_elements.last();
745 endElement.m_isSubpathEnd =
true;
749 endElement.ep = m_elements[subStart].sp;
750 }
else if (prevElement >= 0) {
751 res.m_elements[prevElement].m_isSubpathEnd =
true;
755 res.m_elements.append(element);
756 prevElement = res.m_elements.size() - 1;
759 if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
760 res.m_currentPoint = m_elements.last().ep;
761 res.lineTo(m_elements[subStart].sp);
764 if (!res.m_elements.isEmpty()) {
765 auto &endElement = res.m_elements.last();
766 endElement.m_isSubpathEnd =
true;
767 endElement.ep = m_elements[subStart].sp;
775QQuadPath QQuadPath::flattened()
const
778 res.reserve(elementCountRecursive());
779 iterateElements([&](
const QQuadPath::Element &elem,
int) { res.m_elements.append(elem); });
780 res.setPathHints(pathHints());
781 res.setFillRule(fillRule());
791 m_currentPoint = m_element.startPoint();
792 if (m_element.isLine())
793 m_lineLength = (m_element.endPoint() - m_element.startPoint()).length();
800 m_lastT = m_currentT;
801 m_lastPoint = m_currentPoint;
802 float nextCut = m_consumed + length;
803 float cutT = m_element.isLine() ? nextCut / m_lineLength : tForLength(nextCut);
806 m_currentPoint = m_element.pointAtFraction(m_currentT);
807 m_consumed = nextCut;
811 m_currentPoint = m_element.endPoint();
818 return m_currentPoint;
823 Q_ASSERT(!m_element.isLine());
825 QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
827 float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
828 QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
834 float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
835 return elemLength - m_consumed;
841 Q_ASSERT(!m_element.isLine());
842 QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
843 QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
844 float A = 4 * QVector2D::dotProduct(ap, ap);
845 float B = 4 * QVector2D::dotProduct(ap, bp);
846 float C = QVector2D::dotProduct(bp, bp);
847 float b = B / (2 * A);
849 float k = c - (b * b);
850 float l2 = b * std::sqrt(b * b + k);
851 float lnom = b + std::sqrt(b * b + k);
852 float l0 = 0.5f * std::sqrt(A);
854 m_lut.resize(LUTSize, 0);
855 for (
int i = 1; i < LUTSize; i++) {
856 float t =
float(i) / (LUTSize - 1);
858 float w = std::sqrt(u * u + k);
861 float l3 = k * std::log(std::fabs(lden / lnom));
862 float res = l0 * (l1 - l2 + l3);
867 float tForLength(
float length)
869 Q_ASSERT(!m_element.isLine());
870 Q_ASSERT(!m_lut.isEmpty());
873 auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(), length);
874 if (it != m_lut.cend()) {
875 float nextLength = *it--;
876 float prevLength = *it;
877 int prevIndex = std::distance(m_lut.cbegin(), it);
878 float fraction = (length - prevLength) / (nextLength - prevLength);
879 res = (prevIndex + fraction) / (LUTSize - 1);
886 float m_currentT = 0;
887 QVector2D m_lastPoint;
888 QVector2D m_currentPoint;
889 float m_consumed = 0;
893 static constexpr int LUTSize = 21;
894 QVarLengthArray<
float, LUTSize> m_lut;
897QQuadPath QQuadPath::dashed(qreal lineWidth,
const QList<qreal> &dashPattern, qreal dashOffset)
const
899 QVarLengthArray<
float, 16> pattern;
900 float patternLength = 0;
901 for (
int i = 0; i < 2 * (dashPattern.length() / 2); i++) {
902 float dashLength = qMax(lineWidth * dashPattern[i], qreal(0));
903 pattern.append(dashLength);
904 patternLength += dashLength;
906 if (patternLength == 0)
910 float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
912 startOffset += patternLength;
913 for (
float dashLength : pattern) {
914 if (dashLength > startOffset)
916 startIndex = (startIndex + 1) % pattern.size();
917 startOffset -= dashLength;
920 int dashIndex = startIndex;
921 float offset = startOffset;
923 for (
int i = 0; i < elementCount(); i++) {
924 const Element &element = elementAt(i);
925 if (element.isSubpathStart()) {
926 res.moveTo(element.startPoint());
927 dashIndex = startIndex;
928 offset = startOffset;
930 ElementCutter cutter(element);
932 bool gotAll = cutter.consume(pattern.at(dashIndex) - offset);
933 QVector2D nextPoint = cutter.currentCutPoint();
935 res.moveTo(nextPoint);
936 else if (element.isLine())
937 res.lineTo(nextPoint);
939 res.quadTo(cutter.currentControlPoint(), nextPoint);
942 dashIndex = (dashIndex + 1) % pattern.size();
944 offset += cutter.lastLength();
949 res.setFillRule(fillRule());
950 res.setPathHints(pathHints());
954void QQuadPath::splitElementAt(
int index)
956 const int newChildIndex = m_childElements.size();
957 m_childElements.resize(newChildIndex + 2);
958 Element &parent = elementAt(index);
959 parent.m_numChildren = 2;
960 parent.m_firstChildIndex = newChildIndex;
962 Element &quad1 = m_childElements[newChildIndex];
963 const QVector2D mp = parent.midPoint();
964 quad1.sp = parent.sp;
965 quad1.cp = 0.5f * (parent.sp + parent.cp);
967 quad1.m_isSubpathStart = parent.m_isSubpathStart;
968 quad1.m_isSubpathEnd =
false;
969 quad1.m_curvatureFlags = parent.m_curvatureFlags;
970 quad1.m_isLine = parent.m_isLine;
972 Element &quad2 = m_childElements[newChildIndex + 1];
974 quad2.cp = 0.5f * (parent.ep + parent.cp);
975 quad2.ep = parent.ep;
976 quad2.m_isSubpathStart =
false;
977 quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
978 quad2.m_curvatureFlags = parent.m_curvatureFlags;
979 quad2.m_isLine = parent.m_isLine;
982 if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
983 qCDebug(lcSGCurveProcessor) <<
"Splitting has resulted in ~null quad";
987static void printElement(QDebug stream,
const QQuadPath::Element &element)
989 auto printPoint = [&](QVector2D p) { stream <<
"(" << p.x() <<
", " << p.y() <<
") "; };
991 printPoint(element.startPoint());
992 printPoint(element.controlPoint());
993 printPoint(element.endPoint());
994 stream <<
"} " << (element.isLine() ?
"L " :
"C ") << (element.isConvex() ?
"X " :
"O ")
995 << (element.isFillOnRight() ?
"R " :
"L ")
996 << (element.isSubpathStart() ?
"S" : element.isSubpathEnd() ?
"E" :
"");
999#ifndef QT_NO_DEBUG_STREAM
1002 QDebugStateSaver saver(stream);
1004 stream <<
"QuadPath::Element( ";
1005 printElement(stream, element);
1012 QDebugStateSaver saver(stream);
1014 stream <<
"QuadPath(" << path.elementCount() <<
" main elements, "
1015 << path.elementCountRecursive() <<
" leaf elements, "
1016 << (path.fillRule() == Qt::OddEvenFill ?
"OddEven" :
"Winding")
1017 <<
", hints: " << path.pathHints() << Qt::endl;
1019 path.iterateElements([&](
const QQuadPath::Element &e,
int) {
1020 stream <<
" " << count++ << (e.isSubpathStart() ?
">" : e.isSubpathEnd() ?
"<" :
" ");
1021 printElement(stream, e);
bool consume(float length)
QVector2D currentControlPoint()
QVector2D currentCutPoint()
ElementCutter(const QQuadPath::Element &element)
Combined button and popup list for selecting options.
QDebug operator<<(QDebug dbg, const QFileInfo &fi)
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)
static bool hasOverlappingLines(const QQuadPath &path)
#define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN
static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)