Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquadpath.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquadpath_p.h"
5#include <QtGui/private/qbezier_p.h>
6#include <QtMath>
7#include <QtCore/QLoggingCategory>
8#include <QtCore/QVarLengthArray>
9
11
12Q_DECLARE_LOGGING_CATEGORY(lcSGCurveProcessor);
13
15{
16 static bool init = false;
17 const int numSteps = 21;
18 Q_STATIC_ASSERT(numSteps % 2 == 1); // numTries must be odd
19 static qreal t2s[numSteps];
20 static qreal tmts[numSteps];
21 if (!init) {
22 // Precompute bezier factors
23 qreal t = 0.20;
24 const qreal step = (1 - (2 * t)) / (numSteps - 1);
25 for (int i = 0; i < numSteps; i++) {
26 t2s[i] = t * t;
27 tmts[i] = 2 * t * (1 - t);
28 t += step;
29 }
30 init = true;
31 }
32
33 const QPointF midPoint = b.midPoint();
34 auto distForIndex = [&](int i) -> qreal {
35 QPointF qp = (t2s[numSteps - 1 - i] * b.pt1()) + (tmts[i] * qcp) + (t2s[i] * b.pt4());
36 QPointF d = midPoint - qp;
37 return QPointF::dotProduct(d, d);
38 };
39
40 const int halfSteps = (numSteps - 1) / 2;
41 bool foundIt = false;
42 const qreal centerDist = distForIndex(halfSteps);
43 qreal minDist = centerDist;
44 // Search for the minimum in right half
45 for (int i = 0; i < halfSteps; i++) {
46 qreal tDist = distForIndex(halfSteps + 1 + i);
47 if (tDist < minDist) {
48 minDist = tDist;
49 } else {
50 foundIt = (i > 0);
51 break;
52 }
53 }
54 if (!foundIt) {
55 // Search in left half
56 minDist = centerDist;
57 for (int i = 0; i < halfSteps; i++) {
58 qreal tDist = distForIndex(halfSteps - 1 - i);
59 if (tDist < minDist) {
60 minDist = tDist;
61 } else {
62 foundIt = (i > 0);
63 break;
64 }
65 }
66 }
67 return foundIt ? minDist : centerDist;
68}
69
71{
72 const QLineF st = b.startTangent();
73 const QLineF et = b.endTangent();
74 const QPointF midPoint = b.midPoint();
75 bool valid = true;
76 QPointF quadControlPoint;
77 if (st.intersects(et, &quadControlPoint) == QLineF::NoIntersection) {
78 valid = false;
79 } else {
80 // Check if intersection is on wrong side
81 const QPointF bl = b.pt4() - b.pt1();
82 const QPointF ml = midPoint - b.pt1();
83 const QPointF ql = quadControlPoint - b.pt1();
84 qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
85 qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
86 valid = (std::signbit(cx1) == std::signbit(cx2));
87 }
88 return valid ? quadControlPoint : midPoint;
89}
90
91static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
92{
93 auto isValidRoot = [](qreal r) {
94 return qIsFinite(r) && (r > 0) && (!qFuzzyIsNull(float(r))) && (r < 1)
95 && (!qFuzzyIsNull(float(r - 1)));
96 };
97
98 // normalize so pt1.x,pt1.y,pt4.y == 0
99 QTransform xf;
100 const QLineF l(orig.pt1(), orig.pt4());
101 xf.rotate(l.angle());
102 xf.translate(-orig.pt1().x(), -orig.pt1().y());
103 const QBezier n = orig.mapBy(xf);
104
105 const qreal x2 = n.pt2().x();
106 const qreal x3 = n.pt3().x();
107 const qreal x4 = n.pt4().x();
108 const qreal y2 = n.pt2().y();
109 const qreal y3 = n.pt3().y();
110
111 const qreal p = x3 * y2;
112 const qreal q = x4 * y2;
113 const qreal r = x2 * y3;
114 const qreal s = x4 * y3;
115
116 const qreal a = 18 * ((-3 * p) + (2 * q) + (3 * r) - s);
117 if (qFuzzyIsNull(float(a))) {
118 if (std::signbit(y2) != std::signbit(y3) && qFuzzyCompare(float(x4 - x3), float(x2))) {
119 tpoints[0] = 0.5; // approx
120 return 1;
121 } else if (!a) {
122 return 0;
123 }
124 }
125 const qreal b = 18 * (((3 * p) - q) - (3 * r));
126 const qreal c = 18 * (r - p);
127 const qreal rad = (b * b) - (4 * a * c);
128 if (rad < 0)
129 return 0;
130 const qreal sqr = qSqrt(rad);
131 const qreal root1 = (-b + sqr) / (2 * a);
132 const qreal root2 = (-b - sqr) / (2 * a);
133
134 int res = 0;
135 if (isValidRoot(root1))
136 tpoints[res++] = root1;
137 if (root2 != root1 && isValidRoot(root2))
138 tpoints[res++] = root2;
139
140 if (res == 2 && tpoints[0] > tpoints[1])
141 qSwap(tpoints[0], tpoints[1]);
142
143 return res;
144}
145
146static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
147{
149 if (maxSplits <= 0 || qt_scoreQuadratic(b, qcp) < maxDiff) {
150 p->append(qcp);
151 p->append(b.pt4());
152 } else {
153 QBezier rhs = b;
154 QBezier lhs;
155 rhs.parameterSplitLeft(0.5, &lhs);
156 qt_addToQuadratics(lhs, p, maxSplits - 1, maxDiff);
157 qt_addToQuadratics(rhs, p, maxSplits - 1, maxDiff);
158 }
159}
160
161static void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit = 0.01)
162{
163 out->resize(0);
164 out->append(b.pt1());
165
166 {
167 // Shortcut if the cubic is really a quadratic
168 const qreal f = 3.0 / 2.0;
169 const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
170 const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
171 if (c1 == c2) {
172 out->append(c1);
173 out->append(b.pt4());
174 return;
175 }
176 }
177
178 const QRectF cpr = b.bounds();
179 const QPointF dim = cpr.bottomRight() - cpr.topLeft();
180 qreal maxDiff = QPointF::dotProduct(dim, dim) * errorLimit * errorLimit; // maxdistance^2
181
182 qreal infPoints[2];
183 int numInfPoints = qt_getInflectionPoints(b, infPoints);
184 const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
186 // number of main segments == #inflectionpoints + 1
187 for (int i = 0; i < numInfPoints + 1; i++) {
188 qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
190 qt_addToQuadratics(segment, out, maxSubSplits, maxDiff);
191 t0 = t1;
192 }
193}
194
196{
197 if (isLine()) {
198 return sp + t * (ep - sp);
199 } else {
200 const float r = 1 - t;
201 return (r * r * sp) + (2 * t * r * cp) + (t * t * ep);
202 }
203}
204
206{
207 if (t0 <= 0 && t1 >= 1)
208 return *this;
209
210 Element part;
211 part.sp = pointAtFraction(t0);
212 part.ep = pointAtFraction(t1);
213
214 if (isLine()) {
215 part.cp = 0.5f * (part.sp + part.ep);
216 part.m_isLine = true;
217 } else {
218 // Split curve right at t0, yields { t0, rcp, endPoint } quad segment
219 const QVector2D rcp = (1 - t0) * controlPoint() + t0 * endPoint();
220 // Split that left at t1, yields { t0, lcp, t1 } quad segment
221 float segmentT = (t1 - t0) / (1 - t0);
222 part.cp = (1 - segmentT) * part.sp + segmentT * rcp;
223 }
224 return part;
225}
226
228 Element swappedElement;
229 swappedElement.ep = sp;
230 swappedElement.cp = cp;
231 swappedElement.sp = ep;
232 swappedElement.m_isLine = m_isLine;
233 return swappedElement;
234}
235
237{
238 // TBD: cache this value if we start using it a lot
239 QVector2D min(qMin(sp.x(), ep.x()), qMin(sp.y(), ep.y()));
240 QVector2D max(qMax(sp.x(), ep.x()), qMax(sp.y(), ep.y()));
241 if (!isLine()) {
242 min = QVector2D(qMin(min.x(), cp.x()), qMin(min.y(), cp.y()));
243 max = QVector2D(qMax(max.x(), cp.x()), qMax(max.y(), cp.y()));
244 }
245 return (max - min).length();
246}
247
248// Returns the number of intersections between element and a horizontal line at y.
249// The t values of max 2 intersection(s) are stored in the fractions array
250int QQuadPath::Element::intersectionsAtY(float y, float *fractions, bool swapXY) const
251{
252 Q_ASSERT(!isLine());
253
254 auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
255
256 const float y0 = getY(startPoint()) - y;
257 const float y1 = getY(controlPoint()) - y;
258 const float y2 = getY(endPoint()) - y;
259
260 int numRoots = 0;
261 const float a = y0 - (2 * y1) + y2;
262 if (a) {
263 const float b = (y1 * y1) - (y0 * y2);
264 if (b >= 0) {
265 const float sqr = qSqrt(b);
266 const float root1 = -(-y0 + y1 + sqr) / a;
267 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
268 fractions[numRoots++] = root1;
269 const float root2 = (y0 - y1 + sqr) / a;
270 if (qIsFinite(root2) && root2 != root1 && root2 >= 0 && root2 <= 1)
271 fractions[numRoots++] = root2;
272 }
273 } else if (y1 != y2) {
274 const float root1 = (y2 - (2 * y1)) / (2 * (y2 - y1));
275 if (qIsFinite(root1) && root1 >= 0 && root1 <= 1)
276 fractions[numRoots++] = root1;
277 }
278
279 return numRoots;
280}
281
282static float crossProduct(const QVector2D &sp, const QVector2D &p, const QVector2D &ep)
283{
284 QVector2D v1 = ep - sp;
285 QVector2D v2 = p - sp;
286 return (v2.x() * v1.y()) - (v2.y() * v1.x());
287}
288
290{
291 // Use cross product to compare directions of base vector and vector from start to p
292 return crossProduct(sp, p, ep) >= 0.0f;
293}
294
296{
297 return qFuzzyIsNull(crossProduct(sp, p, ep));
298}
299
300// Assumes sp != ep
302{
303 // epsilon is max length of p-to-baseline relative to length of baseline. So 0.01 means that
304 // the distance from p to the baseline must be less than 1% of the length of the baseline.
305 constexpr float epsilon = 0.01f;
306 QVector2D bv = ep - sp;
307 float bl2 = QVector2D::dotProduct(bv, bv);
308 float t = QVector2D::dotProduct(p - sp, bv) / bl2;
309 QVector2D pv = p - (sp + t * bv);
310 return (QVector2D::dotProduct(pv, pv) / bl2) < (epsilon * epsilon);
311}
312
314{
315 QVector2D line = ep - sp;
317 return sp + qBound(0.0f, t, 1.0f) * line;
318}
319
320// NOTE: it is assumed that subpaths are closed
321bool QQuadPath::contains(const QVector2D &point) const
323 return contains(point, 0, elementCount() - 1);
324}
325
326bool QQuadPath::contains(const QVector2D &point, int fromIndex, int toIndex) const
327{
328 // if (!controlPointRect().contains(pt) : good opt when we add cpr caching
329 // return false;
330
331 int winding_number = 0;
332 for (int ei = fromIndex; ei <= toIndex; ei++) {
333 const Element &e = m_elements.at(ei);
334 int dir = 1;
335 float y1 = e.startPoint().y();
336 float y2 = e.endPoint().y();
337 if (y2 < y1) {
338 qSwap(y1, y2);
339 dir = -1;
340 }
341 if (e.m_isLine) {
342 if (point.y() < y1 || point.y() >= y2 || y1 == y2)
343 continue;
344 const float t = (point.y() - e.startPoint().y()) / (e.endPoint().y() - e.startPoint().y());
345 const float x = e.startPoint().x() + t * (e.endPoint().x() - e.startPoint().x());
346 if (x <= point.x())
347 winding_number += dir;
348 } else {
349 y1 = qMin(y1, e.controlPoint().y());
350 y2 = qMax(y2, e.controlPoint().y());
351 if (point.y() < y1 || point.y() >= y2)
352 continue;
353 float ts[2];
354 const int numRoots = e.intersectionsAtY(point.y(), ts);
355 // Count if there is exactly one intersection to the left
356 bool oneHit = false;
357 float tForHit = -1;
358 for (int i = 0; i < numRoots; i++) {
359 if (e.pointAtFraction(ts[i]).x() <= point.x()) {
360 oneHit = !oneHit;
361 tForHit = ts[i];
362 }
363 }
364 if (oneHit) {
365 dir = e.tangentAtFraction(tForHit).y() < 0 ? -1 : 1;
366 winding_number += dir;
367 }
368 }
369 };
370
371 return (fillRule() == Qt::WindingFill ? (winding_number != 0) : ((winding_number % 2) != 0));
372}
373
374// similar as contains. But we treat the element with the index elementIdx in a special way
375// that should be numerically more stable. The result is a contains for a point on the left
376// and for the right side of the element.
377QQuadPath::Element::FillSide QQuadPath::fillSideOf(int elementIdx, float elementT) const
378{
379 constexpr float toleranceT = 1e-3f;
380 const QVector2D point = m_elements.at(elementIdx).pointAtFraction(elementT);
381 const QVector2D tangent = m_elements.at(elementIdx).tangentAtFraction(elementT);
382
383 const bool swapXY = qAbs(tangent.x()) > qAbs(tangent.y());
384 auto getX = [=](QVector2D p) -> float { return swapXY ? p.y() : p.x(); };
385 auto getY = [=](QVector2D p) -> float { return swapXY ? -p.x() : p.y(); };
386
387 int winding_number = 0;
388 for (int i = 0; i < elementCount(); i++) {
389 const Element &e = m_elements.at(i);
390 int dir = 1;
391 float y1 = getY(e.startPoint());
392 float y2 = getY(e.endPoint());
393 if (y2 < y1) {
394 qSwap(y1, y2);
395 dir = -1;
396 }
397 if (e.m_isLine) {
398 if (getY(point) < y1 || getY(point) >= y2 || y1 == y2)
399 continue;
400 const float t = (getY(point) - getY(e.startPoint())) / (getY(e.endPoint()) - getY(e.startPoint()));
401 const float x = getX(e.startPoint()) + t * (getX(e.endPoint()) - getX(e.startPoint()));
402 if (x <= getX(point) && (i != elementIdx || qAbs(t - elementT) > toleranceT))
403 winding_number += dir;
404 } else {
405 y1 = qMin(y1, getY(e.controlPoint()));
406 y2 = qMax(y2, getY(e.controlPoint()));
407 if (getY(point) < y1 || getY(point) >= y2)
408 continue;
409 float ts[2];
410 const int numRoots = e.intersectionsAtY(getY(point), ts, swapXY);
411 // Count if there is exactly one intersection to the left
412 bool oneHit = false;
413 float tForHit = -1;
414 for (int j = 0; j < numRoots; j++) {
415 const float x = getX(e.pointAtFraction(ts[j]));
416 if (x <= getX(point) && (i != elementIdx || qAbs(ts[j] - elementT) > toleranceT)) {
417 oneHit = !oneHit;
418 tForHit = ts[j];
419 }
420 }
421 if (oneHit) {
422 dir = getY(e.tangentAtFraction(tForHit)) < 0 ? -1 : 1;
423 winding_number += dir;
424 }
425 }
426 };
427
428 int left_winding_number = winding_number;
429 int right_winding_number = winding_number;
430
431 int dir = getY(tangent) < 0 ? -1 : 1;
432
433 if (dir > 0)
434 left_winding_number += dir;
435 else
436 right_winding_number += dir;
437
438 bool leftInside = (fillRule() == Qt::WindingFill ? (left_winding_number != 0) : ((left_winding_number % 2) != 0));
439 bool rightInside = (fillRule() == Qt::WindingFill ? (right_winding_number != 0) : ((right_winding_number % 2) != 0));
440
441 if (leftInside && rightInside)
442 return QQuadPath::Element::FillSideBoth;
443 else if (leftInside)
444 return QQuadPath::Element::FillSideLeft;
445 else if (rightInside)
446 return QQuadPath::Element::FillSideRight;
447 else
448 return QQuadPath::Element::FillSideUndetermined; //should not happen except for numerical error.
449}
450
451void QQuadPath::addElement(const QVector2D &control, const QVector2D &endPoint, bool isLine)
452{
453 if (qFuzzyCompare(m_currentPoint, endPoint))
454 return; // 0 length element, skip
455
456 isLine = isLine || isPointNearLine(control, m_currentPoint, endPoint); // Turn flat quad into line
457
458 m_elements.resize(m_elements.size() + 1);
459 Element &elem = m_elements.last();
460 elem.sp = m_currentPoint;
461 elem.cp = isLine ? (0.5f * (m_currentPoint + endPoint)) : control;
462 elem.ep = endPoint;
463 elem.m_isLine = isLine;
464 elem.m_isSubpathStart = m_subPathToStart;
465 m_subPathToStart = false;
466 m_currentPoint = endPoint;
467}
468
469void QQuadPath::addElement(const Element &e)
470{
471 m_subPathToStart = false;
472 m_currentPoint = e.endPoint();
473 m_elements.append(e);
474}
475
476#if !defined(QQUADPATH_CONVEX_CHECK_ERROR_MARGIN)
477# define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN (1.0f / 32.0f)
478#endif
479
480QQuadPath::Element::CurvatureFlags QQuadPath::coordinateOrderOfElement(const QQuadPath::Element &element) const
481{
482 QVector2D baseLine = element.endPoint() - element.startPoint();
483 QVector2D midPoint = element.midPoint();
484 // At the midpoint, the tangent of a quad is parallel to the baseline
485 QVector2D normal = QVector2D(-baseLine.y(), baseLine.x()).normalized();
486 float delta = qMin(element.extent() / 100, QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN);
487 QVector2D justRightOfMid = midPoint + (normal * delta);
488 bool pathContainsPoint = contains(justRightOfMid);
489 return pathContainsPoint ? Element::FillOnRight : Element::CurvatureFlags(0);
490}
491
493{
495 res.reserve(path.elementCount());
496 res.setFillRule(path.fillRule());
497
498 const bool isQuadratic = hints & PathQuadratic;
499
500 QPolygonF quads;
501 QPointF sp;
502 for (int i = 0; i < path.elementCount(); ++i) {
503 QPainterPath::Element element = path.elementAt(i);
504
505 QPointF ep(element);
506 switch (element.type) {
508 res.moveTo(QVector2D(ep));
509 break;
511 res.lineTo(QVector2D(ep));
512 break;
514 QPointF cp1 = ep;
515 QPointF cp2(path.elementAt(++i));
516 ep = path.elementAt(++i);
517 if (isQuadratic) {
518 const qreal f = 3.0 / 2.0;
519 const QPointF cp = sp + f * (cp1 - sp);
520 res.quadTo(QVector2D(cp), QVector2D(ep));
521 } else {
522 QBezier b = QBezier::fromPoints(sp, cp1, cp2, ep);
523 qt_toQuadratics(b, &quads);
524 for (int i = 1; i < quads.size(); i += 2) {
525 QVector2D cp(quads[i]);
526 QVector2D ep(quads[i + 1]);
527 res.quadTo(cp, ep);
528 }
529 }
530 break;
531 }
532 default:
533 Q_UNREACHABLE();
534 break;
535 }
536 sp = ep;
537 }
538
539 res.setPathHints(hints | PathQuadratic);
540 return res;
541}
542
544{
545 // We use the convention that the inside of a curve is on the *right* side of the
546 // direction of the baseline.Thus, as long as this is true: if the control point is
547 // on the left side of the baseline, the curve is convex and otherwise it is
548 // concave. The paths we get can be arbitrary order, but each subpath will have a
549 // consistent order. Therefore, for the first curve element in a subpath, we can
550 // determine if the direction already follows the convention or not, and then we
551 // can easily detect curvature of all subsequent elements in the subpath.
552
553 static bool checkAnomaly = qEnvironmentVariableIntValue("QT_QUICKSHAPES_CHECK_ALL_CURVATURE") != 0;
554 const bool pathHasFillOnRight = testHint(PathFillOnRight);
555
556 Element::CurvatureFlags flags = Element::CurvatureUndetermined;
557 for (QQuadPath::Element &element : m_elements) {
558 Q_ASSERT(element.childCount() == 0);
559 if (element.isSubpathStart()) {
560 if (pathHasFillOnRight && !checkAnomaly)
561 flags = Element::FillOnRight;
562 else
563 flags = coordinateOrderOfElement(element);
564 } else if (checkAnomaly) {
565 Element::CurvatureFlags newFlags = coordinateOrderOfElement(element);
566 if (flags != newFlags) {
567 qDebug() << "Curvature anomaly detected:" << element
568 << "Subpath fill on right:" << (flags & Element::FillOnRight)
569 << "Element fill on right:" << (newFlags & Element::FillOnRight);
570 flags = newFlags;
571 }
572 }
573
574 if (element.isLine()) {
575 element.m_curvatureFlags = flags;
576 } else {
577 bool controlPointOnLeft = element.isControlPointOnLeft();
578 bool isFillOnRight = flags & Element::FillOnRight;
579 bool isConvex = controlPointOnLeft == isFillOnRight;
580
581 if (isConvex)
582 element.m_curvatureFlags = Element::CurvatureFlags(flags | Element::Convex);
583 else
584 element.m_curvatureFlags = flags;
585 }
586 }
587}
588
590{
591 QRectF res;
592 if (elementCount()) {
593 QVector2D min, max;
594 min = max = m_elements.constFirst().sp;
595 // No need to recurse, as split curve's controlpoints are within the parent curve's
596 for (const QQuadPath::Element &e : std::as_const(m_elements)) {
597 min.setX(std::min({ min.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
598 min.setY(std::min({ min.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
599 max.setX(std::max({ max.x(), e.sp.x(), e.cp.x(), e.ep.x() }));
600 max.setY(std::max({ max.y(), e.sp.y(), e.cp.y(), e.ep.y() }));
601 }
602 res = QRectF(min.toPointF(), max.toPointF());
603 }
604 return res;
605}
606
607// Count leaf elements
609{
610 int count = 0;
611 iterateElements([&](const QQuadPath::Element &, int) { count++; });
612 return count;
613}
614
616{
617 // Currently only converts the main, unsplit path; no need for the split path identified
620 res.setFillRule(fillRule());
621 for (const Element &element : m_elements) {
622 if (element.m_isSubpathStart)
623 res.moveTo(element.startPoint().toPointF());
624 if (element.m_isLine)
625 res.lineTo(element.endPoint().toPointF());
626 else
627 res.quadTo(element.controlPoint().toPointF(), element.endPoint().toPointF());
628 };
629 return res;
630}
631
633{
634 QString res;
636 for (const Element &element : m_elements) {
637 if (element.isSubpathStart())
638 str << "M " << element.startPoint().x() << " " << element.startPoint().y() << " ";
639 if (element.isLine())
640 str << "L " << element.endPoint().x() << " " << element.endPoint().y() << " ";
641 else
642 str << "Q " << element.controlPoint().x() << " " << element.controlPoint().y() << " "
643 << element.endPoint().x() << " " << element.endPoint().y() << " ";
644 }
645 return res;
646}
647
648// Returns a new path since doing it inline would probably be less efficient
649// (technically changing it from O(n) to O(n^2))
650// Note that this function should be called before splitting any elements,
651// so we can assume that the structure is a list and not a tree
653{
654 Q_ASSERT(m_childElements.isEmpty());
655 bool closed = false;
656 QQuadPath res = *this;
657 res.m_subPathToStart = false;
658 res.m_elements = {};
659 res.m_elements.reserve(elementCount());
660 int subStart = -1;
661 int prevElement = -1;
662 for (int i = 0; i < elementCount(); i++) {
663 const auto &element = m_elements.at(i);
664 if (element.m_isSubpathStart) {
665 if (subStart >= 0 && m_elements[i - 1].ep != m_elements[subStart].sp) {
666 res.m_currentPoint = m_elements[i - 1].ep;
667 res.lineTo(m_elements[subStart].sp);
668 closed = true;
669 auto &endElement = res.m_elements.last();
670 endElement.m_isSubpathEnd = true;
671 // lineTo() can bail out if the points are too close.
672 // In that case, just change the end point to be equal to the start
673 // (No need to test because the assignment is a no-op otherwise).
674 endElement.ep = m_elements[subStart].sp;
675 } else if (prevElement >= 0) {
676 res.m_elements[prevElement].m_isSubpathEnd = true;
677 }
678 subStart = i;
679 }
680 res.m_elements.append(element);
681 prevElement = res.m_elements.size() - 1;
682 }
683
684 if (subStart >= 0 && m_elements.last().ep != m_elements[subStart].sp) {
685 res.m_currentPoint = m_elements.last().ep;
686 res.lineTo(m_elements[subStart].sp);
687 closed = true;
688 }
689 if (!res.m_elements.isEmpty()) {
690 auto &endElement = res.m_elements.last();
691 endElement.m_isSubpathEnd = true;
692 endElement.ep = m_elements[subStart].sp;
693 }
694
695 if (didClose)
696 *didClose = closed;
697 return res;
698}
699
701{
704 iterateElements([&](const QQuadPath::Element &elem, int) { res.m_elements.append(elem); });
705 res.setPathHints(pathHints());
706 res.setFillRule(fillRule());
707 return res;
708}
709
711{
712public:
714 : m_element(element)
715 {
716 m_currentPoint = m_element.startPoint();
717 if (m_element.isLine())
718 m_lineLength = (m_element.endPoint() - m_element.startPoint()).length();
719 else
720 fillLUT();
721 }
722
723 bool consume(float length)
724 {
725 m_lastT = m_currentT;
726 m_lastPoint = m_currentPoint;
727 float nextCut = m_consumed + length;
728 float cutT = m_element.isLine() ? nextCut / m_lineLength : tForLength(nextCut);
729 if (cutT < 1) {
730 m_currentT = cutT;
731 m_currentPoint = m_element.pointAtFraction(m_currentT);
732 m_consumed = nextCut;
733 return true;
734 } else {
735 m_currentT = 1;
736 m_currentPoint = m_element.endPoint();
737 return false;
738 }
739 }
740
742 {
743 return m_currentPoint;
744 }
745
747 {
748 Q_ASSERT(!m_element.isLine());
749 // Split curve right at lastT, yields { lastPoint, rcp, endPoint } quad segment
750 QVector2D rcp = (1 - m_lastT) * m_element.controlPoint() + m_lastT * m_element.endPoint();
751 // Split that left at currentT, yields { lastPoint, lcp, currentPoint } quad segment
752 float segmentT = (m_currentT - m_lastT) / (1 - m_lastT);
753 QVector2D lcp = (1 - segmentT) * m_lastPoint + segmentT * rcp;
754 return lcp;
755 }
756
758 {
759 float elemLength = m_element.isLine() ? m_lineLength : m_lut.last();
760 return elemLength - m_consumed;
761 }
762
763private:
764 void fillLUT()
765 {
766 Q_ASSERT(!m_element.isLine());
767 QVector2D ap = m_element.startPoint() - 2 * m_element.controlPoint() + m_element.endPoint();
768 QVector2D bp = 2 * m_element.controlPoint() - 2 * m_element.startPoint();
769 float A = 4 * QVector2D::dotProduct(ap, ap);
770 float B = 4 * QVector2D::dotProduct(ap, bp);
771 float C = QVector2D::dotProduct(bp, bp);
772 float b = B / (2 * A);
773 float c = C / A;
774 float k = c - (b * b);
775 float l2 = b * std::sqrt(b * b + k);
776 float lnom = b + std::sqrt(b * b + k);
777 float l0 = 0.5f * std::sqrt(A);
778
779 m_lut.resize(LUTSize, 0);
780 for (int i = 1; i < LUTSize; i++) {
781 float t = float(i) / (LUTSize - 1);
782 float u = t + b;
783 float w = std::sqrt(u * u + k);
784 float l1 = u * w;
785 float lden = u + w;
786 float l3 = k * std::log(std::fabs(lden / lnom));
787 float res = l0 * (l1 - l2 + l3);
788 m_lut[i] = res;
789 }
790 }
791
792 float tForLength(float length)
793 {
794 Q_ASSERT(!m_element.isLine());
795 Q_ASSERT(!m_lut.isEmpty());
796
797 float res = 2; // I.e. invalid, outside [0, 1] range
798 auto it = std::upper_bound(m_lut.cbegin(), m_lut.cend(), length);
799 if (it != m_lut.cend()) {
800 float nextLength = *it--;
801 float prevLength = *it;
802 int prevIndex = std::distance(m_lut.cbegin(), it);
803 float fraction = (length - prevLength) / (nextLength - prevLength);
804 res = (prevIndex + fraction) / (LUTSize - 1);
805 }
806 return res;
807 }
808
809 const QQuadPath::Element &m_element;
810 float m_lastT = 0;
811 float m_currentT = 0;
812 QVector2D m_lastPoint;
813 QVector2D m_currentPoint;
814 float m_consumed = 0;
815 // For line elements:
816 float m_lineLength;
817 // For quadratic curve elements:
818 static constexpr int LUTSize = 21;
819 QVarLengthArray<float, LUTSize> m_lut;
820};
821
822QQuadPath QQuadPath::dashed(qreal lineWidth, const QList<qreal> &dashPattern, qreal dashOffset) const
823{
824 QVarLengthArray<float, 16> pattern;
825 float patternLength = 0;
826 for (int i = 0; i < 2 * (dashPattern.length() / 2); i++) {
827 float dashLength = qMax(lineWidth * dashPattern[i], qreal(0));
828 pattern.append(dashLength);
829 patternLength += dashLength;
830 }
831 if (patternLength == 0)
832 return {};
833
834 int startIndex = 0;
835 float startOffset = std::fmod(lineWidth * dashOffset, patternLength);
836 if (startOffset < 0)
837 startOffset += patternLength;
838 for (float dashLength : pattern) {
839 if (dashLength > startOffset)
840 break;
841 startIndex = (startIndex + 1) % pattern.size(); // The % guards against accuracy issues
842 startOffset -= dashLength;
843 }
844
845 int dashIndex = startIndex;
846 float offset = startOffset;
848 for (int i = 0; i < elementCount(); i++) {
849 const Element &element = elementAt(i);
850 if (element.isSubpathStart()) {
851 res.moveTo(element.startPoint());
852 dashIndex = startIndex;
853 offset = startOffset;
854 }
855 ElementCutter cutter(element);
856 while (true) {
857 bool gotAll = cutter.consume(pattern.at(dashIndex) - offset);
858 QVector2D nextPoint = cutter.currentCutPoint();
859 if (dashIndex & 1)
860 res.moveTo(nextPoint); // gap
861 else if (element.isLine())
862 res.lineTo(nextPoint); // dash in line
863 else
864 res.quadTo(cutter.currentControlPoint(), nextPoint); // dash in curve
865 if (gotAll) {
866 offset = 0;
867 dashIndex = (dashIndex + 1) % pattern.size();
868 } else {
869 offset += cutter.lastLength();
870 break;
871 }
872 }
873 }
874 res.setFillRule(fillRule());
875 res.setPathHints(pathHints());
876 return res;
877}
878
880{
881 const int newChildIndex = m_childElements.size();
882 m_childElements.resize(newChildIndex + 2);
883 Element &parent = elementAt(index);
884 parent.m_numChildren = 2;
885 parent.m_firstChildIndex = newChildIndex;
886
887 Element &quad1 = m_childElements[newChildIndex];
888 const QVector2D mp = parent.midPoint();
889 quad1.sp = parent.sp;
890 quad1.cp = 0.5f * (parent.sp + parent.cp);
891 quad1.ep = mp;
892 quad1.m_isSubpathStart = parent.m_isSubpathStart;
893 quad1.m_isSubpathEnd = false;
894 quad1.m_curvatureFlags = parent.m_curvatureFlags;
895 quad1.m_isLine = parent.m_isLine; //### || isPointNearLine(quad1.cp, quad1.sp, quad1.ep);
896
897 Element &quad2 = m_childElements[newChildIndex + 1];
898 quad2.sp = mp;
899 quad2.cp = 0.5f * (parent.ep + parent.cp);
900 quad2.ep = parent.ep;
901 quad2.m_isSubpathStart = false;
902 quad2.m_isSubpathEnd = parent.m_isSubpathEnd;
903 quad2.m_curvatureFlags = parent.m_curvatureFlags;
904 quad2.m_isLine = parent.m_isLine; //### || isPointNearLine(quad2.cp, quad2.sp, quad2.ep);
905
906#ifndef QT_NO_DEBUG
907 if (qFuzzyCompare(quad1.sp, quad1.ep) || qFuzzyCompare(quad2.sp, quad2.ep))
908 qCDebug(lcSGCurveProcessor) << "Splitting has resulted in ~null quad";
909#endif
910}
911
912static void printElement(QDebug stream, const QQuadPath::Element &element)
913{
914 auto printPoint = [&](QVector2D p) { stream << "(" << p.x() << ", " << p.y() << ") "; };
915 stream << "{ ";
916 printPoint(element.startPoint());
917 printPoint(element.controlPoint());
918 printPoint(element.endPoint());
919 stream << "} " << (element.isLine() ? "L " : "C ") << (element.isConvex() ? "X " : "O ")
920 << (element.isSubpathStart() ? "S" : element.isSubpathEnd() ? "E" : "");
921}
922
924{
926 stream.nospace();
927 stream << "QuadPath::Element( ";
928 printElement(stream, element);
929 stream << " )";
930 return stream;
931}
932
934{
936 stream.nospace();
937 stream << "QuadPath(" << path.elementCount() << " main elements, "
938 << path.elementCountRecursive() << " leaf elements, "
939 << (path.fillRule() == Qt::OddEvenFill ? "OddEven" : "Winding") << Qt::endl;
940 int count = 0;
941 path.iterateElements([&](const QQuadPath::Element &e, int) {
942 stream << " " << count++ << (e.isSubpathStart() ? " >" : " ");
944 stream << Qt::endl;
945 });
946 stream << ")";
947 return stream;
948}
949
bool consume(float length)
float lastLength()
QVector2D currentControlPoint()
QVector2D currentCutPoint()
ElementCutter(const QQuadPath::Element &element)
QPointF pt1() const
Definition qbezier_p.h:58
static QBezier fromPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
Definition qbezier_p.h:33
void parameterSplitLeft(qreal t, QBezier *left)
Definition qbezier_p.h:205
QBezier bezierOnInterval(qreal t0, qreal t1) const
Definition qbezier.cpp:578
QBezier mapBy(const QTransform &transform) const
Definition qbezier.cpp:40
QPointF pt4() const
Definition qbezier_p.h:61
QPointF pt2() const
Definition qbezier_p.h:59
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore\compares equality \compareswith equality QLine \endcompareswith
Definition qline.h:192
qreal angle() const
Definition qline.cpp:564
@ NoIntersection
Definition qline.h:195
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
T & last()
Definition qlist.h:648
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
const T & constFirst() const noexcept
Definition qlist.h:647
void resize(qsizetype size)
Definition qlist.h:403
void append(parameter_type t)
Definition qlist.h:458
\inmodule QtGui
\inmodule QtGui
void reserve(int size)
Reserves a given amount of elements in QPainterPath's internal memory.
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
static constexpr qreal dotProduct(const QPointF &p1, const QPointF &p2)
Definition qpoint.h:242
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
The QPolygonF class provides a list of points using floating point precision.
Definition qpolygon.h:96
bool isSubpathStart() const
Definition qquadpath_p.h:55
Element segmentFromTo(float t0, float t1) const
bool isLine() const
Definition qquadpath_p.h:65
Element reversed() const
QVector2D tangentAtFraction(float t) const
bool isSubpathEnd() const
Definition qquadpath_p.h:60
QVector2D startPoint() const
Definition qquadpath_p.h:75
QVector2D midPoint() const
Definition qquadpath_p.h:90
bool isConvex() const
Definition qquadpath_p.h:70
QVector2D endPoint() const
Definition qquadpath_p.h:85
float extent() const
QVector2D pointAtFraction(float t) const
QVector2D controlPoint() const
Definition qquadpath_p.h:80
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
void reserve(int size)
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
@ PathFillOnRight
Definition qquadpath_p.h:35
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={})
void addCurvatureData()
friend Q_QUICK_EXPORT QDebug operator<<(QDebug, const QQuadPath &)
int elementCount() const
\inmodule QtCore\reentrant
Definition qrect.h:484
const_iterator cend() const noexcept
Definition qset.h:142
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:31
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:502
QVector2D normalized() const noexcept
Returns the normalized unit vector form of this vector.
Definition qvectornd.h:529
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:501
static constexpr float dotProduct(QVector2D v1, QVector2D v2) noexcept
Returns the dot product of v1 and v2.
Definition qvectornd.h:604
constexpr void setY(float y) noexcept
Sets the y coordinate of this point to the given finite y coordinate.
Definition qvectornd.h:505
constexpr QPointF toPointF() const noexcept
Returns the QPointF form of this 2D vector.
Definition qvectornd.h:628
constexpr void setX(float x) noexcept
Sets the x coordinate of this point to the given finite x coordinate.
Definition qvectornd.h:504
The QWidget class is the base class of all user interface objects.
Definition qwidget.h:99
int y
the y coordinate of the widget relative to its parent and including any window frame
Definition qwidget.h:110
int x
the x coordinate of the widget relative to its parent including any window frame
Definition qwidget.h:109
QString str
[2]
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ WindingFill
@ OddEvenFill
QTextStream & endl(QTextStream &stream)
Writes '\n' to the stream and flushes the stream.
#define Q_STATIC_ASSERT(Condition)
Definition qassert.h:108
EGLStreamKHR stream
bool qIsFinite(qfloat16 f) noexcept
Definition qfloat16.h:285
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
qfloat16 qSqrt(qfloat16 f)
Definition qfloat16.h:289
#define qDebug
[1]
Definition qlogging.h:164
#define qCDebug(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
n varying highp vec2 A
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
GLuint index
[2]
GLboolean r
[2]
GLenum GLuint GLenum GLsizei length
GLenum GLenum GLsizei count
GLfloat GLfloat f
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
[4]
GLbitfield flags
GLint GLfloat GLfloat v1
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
GLenum GLuint GLintptr offset
GLfloat n
GLuint GLfloat GLfloat y0
GLint y
GLdouble s
[6]
Definition qopenglext.h:235
GLuint res
const GLubyte * c
GLfixed GLfixed GLfixed y2
GLuint segment
GLfixed GLfixed x2
GLdouble GLdouble t
Definition qopenglext.h:243
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLfloat GLfloat p
[1]
GLubyte * pattern
static bool isLine(const QBezier &bezier)
static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
Definition qquadpath.cpp:14
static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
static QPointF qt_quadraticForCubic(const QBezier &b)
Definition qquadpath.cpp:70
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 void printElement(QDebug stream, const QQuadPath::Element &element)
#define QQUICKSHAPECURVERENDERER_CONVEX_CHECK_ERROR_MARGIN
static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
Definition qquadpath.cpp:91
static const qreal epsilon
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QT_BEGIN_NAMESPACE constexpr void qSwap(T &value1, T &value2) noexcept(std::is_nothrow_swappable_v< T >)
Definition qswap.h:20
#define sp
#define t0
#define t1
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)
double qreal
Definition qtypes.h:187
static uint toIndex(ExecutionEngine *e, const Value &v)
QTextStream out(stdout)
[7]
MyCustomStruct c2
QString dir
[11]