Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtriangulatingstroker.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:significant reason:default
4
6#include <qmath.h>
7
9
10#define CURVE_FLATNESS Q_PI / 8
11
12
13
14
15void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur,
16 bool implicitClose, bool endsAtStart)
17{
18 Q_ASSERT(start);
19 if (endsAtStart) {
20 join(start + 2);
21 } else if (implicitClose) {
22 join(start);
23 lineTo(start);
24 join(start+2);
25 } else {
26 endCap(cur);
27 }
28 int count = m_vertices.size();
29
30 // Copy the (x, y) values because QDataBuffer::add(const float& t)
31 // may resize the buffer, which will leave t pointing at the
32 // previous buffer's memory region if we don't copy first.
33 float x = m_vertices.at(count-2);
34 float y = m_vertices.at(count-1);
35 m_vertices.add(x);
36 m_vertices.add(y);
37}
38
39static inline void skipDuplicatePoints(const qreal **pts, const qreal *endPts)
40{
41 while ((*pts + 2) < endPts && float((*pts)[0]) == float((*pts)[2])
42 && float((*pts)[1]) == float((*pts)[3]))
43 {
44 *pts += 2;
45 }
46}
47
48void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &, QPainter::RenderHints)
49{
50 const qreal *pts = path.points();
51 const QPainterPath::ElementType *types = path.elements();
52 int count = path.elementCount();
53 m_vertices.reset();
54 if (count < 2)
55 return;
56
57 float realWidth = qpen_widthf(pen);
58 if (realWidth == 0)
59 realWidth = 1;
60
61 m_width = realWidth / 2;
62
63 bool cosmetic = pen.isCosmetic();
64 if (cosmetic) {
65 m_width = m_width * m_inv_scale;
66 }
67
68 m_join_style = qpen_joinStyle(pen);
69 m_cap_style = qpen_capStyle(pen);
70 m_miter_limit = pen.miterLimit() * qpen_widthf(pen);
71
72 // The curvyness is based on the notion that I originally wanted
73 // roughly one line segment pr 4 pixels. This may seem little, but
74 // because we sample at constantly incrementing B(t) E [0<t<1], we
75 // will get longer segments where the curvature is small and smaller
76 // segments when the curvature is high.
77 //
78 // To get a rough idea of the length of each curve, I pretend that
79 // the curve is a 90 degree arc, whose radius is
80 // qMax(curveBounds.width, curveBounds.height). Based on this
81 // logic we can estimate the length of the outline edges based on
82 // the radius + a pen width and adjusting for scale factors
83 // depending on if the pen is cosmetic or not.
84 //
85 // The curvyness value of PI/14 was based on,
86 // arcLength = 2*PI*r/4 = PI*r/2 and splitting length into somewhere
87 // between 3 and 8 where 5 seemed to be give pretty good results
88 // hence: Q_PI/14. Lower divisors will give more detail at the
89 // direct cost of performance.
90
91 // simplfy pens that are thin in device size (2px wide or less)
92 if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) {
93 if (m_cap_style == Qt::RoundCap)
94 m_cap_style = Qt::SquareCap;
95 if (m_join_style == Qt::RoundJoin)
96 m_join_style = Qt::MiterJoin;
97 m_curvyness_add = 0.5;
98 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
99 m_roundness = 1;
100 } else if (cosmetic) {
101 m_curvyness_add = realWidth / 2;
102 m_curvyness_mul = float(CURVE_FLATNESS);
103 m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS);
104 } else {
105 m_curvyness_add = m_width;
106 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
107 m_roundness = qMax<int>(4, realWidth * m_curvyness_mul);
108 }
109
110 // Over this level of segmentation, there doesn't seem to be any
111 // benefit, even for huge penWidth
112 if (m_roundness > 24)
113 m_roundness = 24;
114
115 m_sin_theta = qFastSin(Q_PI / m_roundness);
116 m_cos_theta = qFastCos(Q_PI / m_roundness);
117
118 const qreal *endPts = pts + (count<<1);
119 const qreal *startPts = nullptr;
120
121 Qt::PenCapStyle cap = m_cap_style;
122
123 if (!types) {
124 skipDuplicatePoints(&pts, endPts);
125 if ((pts + 2) == endPts)
126 return;
127
128 startPts = pts;
129
130 bool endsAtStart = float(startPts[0]) == float(endPts[-2])
131 && float(startPts[1]) == float(endPts[-1]);
132
133 if (endsAtStart || path.hasImplicitClose())
134 m_cap_style = Qt::FlatCap;
135 moveTo(pts);
136 m_cap_style = cap;
137 pts += 2;
138 skipDuplicatePoints(&pts, endPts);
139 lineTo(pts);
140 pts += 2;
141 skipDuplicatePoints(&pts, endPts);
142 while (pts < endPts) {
143 join(pts);
144 lineTo(pts);
145 pts += 2;
146 skipDuplicatePoints(&pts, endPts);
147 }
148 endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
149
150 } else {
151 bool endsAtStart = false;
152 QPainterPath::ElementType previousType = QPainterPath::MoveToElement;
153 const qreal *previousPts = pts;
154 while (pts < endPts) {
155 switch (*types) {
156 case QPainterPath::MoveToElement: {
157 int end = (endPts - pts) / 2;
158 int nextMoveElement = 1;
159 bool hasValidLineSegments = false;
160 while (nextMoveElement < end && types[nextMoveElement] != QPainterPath::MoveToElement) {
161 if (!hasValidLineSegments) {
162 hasValidLineSegments =
163 float(pts[0]) != float(pts[nextMoveElement * 2]) ||
164 float(pts[1]) != float(pts[nextMoveElement * 2 + 1]);
165 }
166 ++nextMoveElement;
167 }
168
169 /**
170 * 'LineToElement' may be skipped if it doesn't move the center point
171 * of the line. We should make sure that we don't end up with a lost
172 * 'MoveToElement' in the vertex buffer, not connected to anything. Since
173 * the buffer uses degenerate triangles trick to split the primitives,
174 * this spurious MoveToElement will create artifacts when rendering.
175 */
176 if (!hasValidLineSegments) {
177 pts += 2 * nextMoveElement;
178 types += nextMoveElement;
179 continue;
180 }
181
182 if (previousType != QPainterPath::MoveToElement)
183 endCapOrJoinClosed(startPts, previousPts, path.hasImplicitClose(), endsAtStart);
184
185 startPts = pts;
186 skipDuplicatePoints(&startPts, endPts); // Skip duplicates to find correct normal.
187 if (startPts + 2 >= endPts)
188 return; // Nothing to see here...
189
190 endsAtStart = float(startPts[0]) == float(pts[nextMoveElement * 2 - 2])
191 && float(startPts[1]) == float(pts[nextMoveElement * 2 - 1]);
192 if (endsAtStart || path.hasImplicitClose())
193 m_cap_style = Qt::FlatCap;
194
195 moveTo(startPts);
196 m_cap_style = cap;
197 previousType = QPainterPath::MoveToElement;
198 previousPts = pts;
199 pts+=2;
200 ++types;
201 break; }
202 case QPainterPath::LineToElement:
203 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])) {
204 if (previousType != QPainterPath::MoveToElement)
205 join(pts);
206 lineTo(pts);
207 previousType = QPainterPath::LineToElement;
208 previousPts = pts;
209 }
210 pts+=2;
211 ++types;
212 break;
213 case QPainterPath::CurveToElement:
214 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])
215 || float(pts[0]) != float(pts[2]) || float(pts[1]) != float(pts[3])
216 || float(pts[2]) != float(pts[4]) || float(pts[3]) != float(pts[5]))
217 {
218 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])) {
219 if (previousType != QPainterPath::MoveToElement)
220 join(pts);
221 }
222 cubicTo(pts);
223 previousType = QPainterPath::CurveToElement;
224 previousPts = pts + 4;
225 }
226 pts+=6;
227 types+=3;
228 break;
229 default:
230 Q_ASSERT(false);
231 break;
232 }
233 }
234
235 if (previousType != QPainterPath::MoveToElement)
236 endCapOrJoinClosed(startPts, previousPts, path.hasImplicitClose(), endsAtStart);
237 }
238}
239
240void QTriangulatingStroker::moveTo(const qreal *pts)
241{
242 m_cx = pts[0];
243 m_cy = pts[1];
244
245 float x2 = pts[2];
246 float y2 = pts[3];
247 normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy);
248
249
250 // To achieve jumps we insert zero-area tringles. This is done by
251 // adding two identical points in both the end of previous strip
252 // and beginning of next strip
253 bool invisibleJump = m_vertices.size();
254
255 switch (m_cap_style) {
256 case Qt::FlatCap:
257 if (invisibleJump) {
258 m_vertices.add(m_cx + m_nvx);
259 m_vertices.add(m_cy + m_nvy);
260 }
261 break;
262 case Qt::SquareCap: {
263 float sx = m_cx - m_nvy;
264 float sy = m_cy + m_nvx;
265 if (invisibleJump) {
266 m_vertices.add(sx + m_nvx);
267 m_vertices.add(sy + m_nvy);
268 }
269 emitLineSegment(sx, sy, m_nvx, m_nvy);
270 break; }
271 case Qt::RoundCap: {
272 QVarLengthArray<float> points;
273 arcPoints(m_cx, m_cy, m_cx + m_nvx, m_cy + m_nvy, m_cx - m_nvx, m_cy - m_nvy, points);
274 m_vertices.resize(m_vertices.size() + points.size() + 2 * int(invisibleJump));
275 int count = m_vertices.size();
276 int front = 0;
277 int end = points.size() / 2;
278 while (front != end) {
279 m_vertices.at(--count) = points[2 * end - 1];
280 m_vertices.at(--count) = points[2 * end - 2];
281 --end;
282 if (front == end)
283 break;
284 m_vertices.at(--count) = points[2 * front + 1];
285 m_vertices.at(--count) = points[2 * front + 0];
286 ++front;
287 }
288
289 if (invisibleJump) {
290 m_vertices.at(count - 1) = m_vertices.at(count + 1);
291 m_vertices.at(count - 2) = m_vertices.at(count + 0);
292 }
293 break; }
294 default: break; // ssssh gcc...
295 }
296 emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
297}
298
299void QTriangulatingStroker::cubicTo(const qreal *pts)
300{
301 const QPointF *p = (const QPointF *) pts;
302 QBezier bezier = QBezier::fromPoints(*(p - 1), p[0], p[1], p[2]);
303
304 QRectF bounds = bezier.bounds();
305 float rad = qMax(bounds.width(), bounds.height());
306 int threshold = qMin<float>(64, (rad + m_curvyness_add) * m_curvyness_mul);
307 if (threshold < 4)
308 threshold = 4;
309 qreal threshold_minus_1 = threshold - 1;
310 float vx = 0, vy = 0;
311
312 float cx = m_cx, cy = m_cy;
313 float x, y;
314
315 for (int i=1; i<threshold; ++i) {
316 qreal t = qreal(i) / threshold_minus_1;
317 QPointF p = bezier.pointAt(t);
318 x = p.x();
319 y = p.y();
320
321 normalVector(cx, cy, x, y, &vx, &vy);
322
323 emitLineSegment(x, y, vx, vy);
324
325 cx = x;
326 cy = y;
327 }
328
329 m_cx = cx;
330 m_cy = cy;
331
332 m_nvx = vx;
333 m_nvy = vy;
334}
335
336void QTriangulatingStroker::join(const qreal *pts)
337{
338 // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
339 normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy);
340
341 switch (m_join_style) {
342 case Qt::BevelJoin:
343 break;
344 case Qt::SvgMiterJoin:
345 case Qt::MiterJoin: {
346 // Find out on which side the join should be.
347 int count = m_vertices.size();
348 float prevNvx = m_vertices.at(count - 2) - m_cx;
349 float prevNvy = m_vertices.at(count - 1) - m_cy;
350 float xprod = prevNvx * m_nvy - prevNvy * m_nvx;
351 float px, py, qx, qy;
352
353 // If the segments are parallel, use bevel join.
354 if (qFuzzyIsNull(xprod))
355 break;
356
357 // Find the corners of the previous and next segment to join.
358 if (xprod < 0) {
359 px = m_vertices.at(count - 2);
360 py = m_vertices.at(count - 1);
361 qx = m_cx - m_nvx;
362 qy = m_cy - m_nvy;
363 } else {
364 px = m_vertices.at(count - 4);
365 py = m_vertices.at(count - 3);
366 qx = m_cx + m_nvx;
367 qy = m_cy + m_nvy;
368 }
369
370 // Find intersection point.
371 float pu = px * prevNvx + py * prevNvy;
372 float qv = qx * m_nvx + qy * m_nvy;
373 float ix = (m_nvy * pu - prevNvy * qv) / xprod;
374 float iy = (prevNvx * qv - m_nvx * pu) / xprod;
375
376 // Check that the distance to the intersection point is less than the miter limit.
377 if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) {
378 m_vertices.add(ix);
379 m_vertices.add(iy);
380 m_vertices.add(ix);
381 m_vertices.add(iy);
382 }
383 // else
384 // Do a plain bevel join if the miter limit is exceeded or if
385 // the lines are parallel. This is not what the raster
386 // engine's stroker does, but it is both faster and similar to
387 // what some other graphics API's do.
388
389 break; }
390 case Qt::RoundJoin: {
391 QVarLengthArray<float> points;
392 int count = m_vertices.size();
393 float prevNvx = m_vertices.at(count - 2) - m_cx;
394 float prevNvy = m_vertices.at(count - 1) - m_cy;
395 if (m_nvx * prevNvy - m_nvy * prevNvx < 0) {
396 arcPoints(0, 0, m_nvx, m_nvy, -prevNvx, -prevNvy, points);
397 for (int i = points.size() / 2; i > 0; --i)
398 emitLineSegment(m_cx, m_cy, points[2 * i - 2], points[2 * i - 1]);
399 } else {
400 arcPoints(0, 0, -prevNvx, -prevNvy, m_nvx, m_nvy, points);
401 for (int i = 0; i < points.size() / 2; ++i)
402 emitLineSegment(m_cx, m_cy, points[2 * i + 0], points[2 * i + 1]);
403 }
404 break; }
405 default: break; // gcc warn--
406 }
407
408 emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
409}
410
411void QTriangulatingStroker::endCap(const qreal *)
412{
413 switch (m_cap_style) {
414 case Qt::FlatCap:
415 break;
416 case Qt::SquareCap:
417 emitLineSegment(m_cx + m_nvy, m_cy - m_nvx, m_nvx, m_nvy);
418 break;
419 case Qt::RoundCap: {
420 QVarLengthArray<float> points;
421 int count = m_vertices.size();
422 arcPoints(m_cx, m_cy, m_vertices.at(count - 2), m_vertices.at(count - 1), m_vertices.at(count - 4), m_vertices.at(count - 3), points);
423 int front = 0;
424 int end = points.size() / 2;
425 while (front != end) {
426 m_vertices.add(points[2 * end - 2]);
427 m_vertices.add(points[2 * end - 1]);
428 --end;
429 if (front == end)
430 break;
431 m_vertices.add(points[2 * front + 0]);
432 m_vertices.add(points[2 * front + 1]);
433 ++front;
434 }
435 break; }
436 default: break; // to shut gcc up...
437 }
438}
439
440void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points)
441{
442 float dx1 = fromX - cx;
443 float dy1 = fromY - cy;
444 float dx2 = toX - cx;
445 float dy2 = toY - cy;
446
447 // while more than 180 degrees left:
448 while (dx1 * dy2 - dx2 * dy1 < 0) {
449 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
450 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
451 dx1 = tmpx;
452 dy1 = tmpy;
453 points.append(cx + dx1);
454 points.append(cy + dy1);
455 }
456
457 // while more than 90 degrees left:
458 while (dx1 * dx2 + dy1 * dy2 < 0) {
459 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
460 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
461 dx1 = tmpx;
462 dy1 = tmpy;
463 points.append(cx + dx1);
464 points.append(cy + dy1);
465 }
466
467 // while more than 0 degrees left:
468 while (dx1 * dy2 - dx2 * dy1 > 0) {
469 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
470 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
471 dx1 = tmpx;
472 dy1 = tmpy;
473 points.append(cx + dx1);
474 points.append(cy + dy1);
475 }
476
477 // remove last point which was rotated beyond [toX, toY].
478 if (!points.isEmpty())
479 points.resize(points.size() - 2);
480}
481
482static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
483{
484 ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::MoveToElement, x, y);
485}
486
487static void qdashprocessor_lineTo(qreal x, qreal y, void *data)
488{
489 ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::LineToElement, x, y);
490}
491
493{
494 Q_ASSERT(0); // The dasher should not produce curves...
495}
496
497QDashedStrokeProcessor::QDashedStrokeProcessor()
498 : m_points(0), m_types(0),
499 m_dash_stroker(nullptr), m_inv_scale(1)
500{
501 m_dash_stroker.setMoveToHook(qdashprocessor_moveTo);
502 m_dash_stroker.setLineToHook(qdashprocessor_lineTo);
503 m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo);
504}
505
506void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints)
507{
508
509 const qreal *pts = path.points();
510 const QPainterPath::ElementType *types = path.elements();
511 int count = path.elementCount();
512
513 bool cosmetic = pen.isCosmetic();
514 bool implicitClose = path.hasImplicitClose();
515
516 m_points.reset();
517 m_types.reset();
518 m_points.reserve(path.elementCount());
519 m_types.reserve(path.elementCount());
520
521 qreal width = qpen_widthf(pen);
522 if (width == 0)
523 width = 1;
524
525 m_dash_stroker.setDashPattern(pen.dashPattern());
526 m_dash_stroker.setStrokeWidth(cosmetic ? width * m_inv_scale : width);
527 m_dash_stroker.setDashOffset(pen.dashOffset());
528 m_dash_stroker.setMiterLimit(pen.miterLimit());
529 m_dash_stroker.setClipRect(clip);
530
531 float curvynessAdd, curvynessMul;
532
533 // simplify pens that are thin in device size (2px wide or less)
534 if (width < 2.5 && (cosmetic || m_inv_scale == 1)) {
535 curvynessAdd = 0.5;
536 curvynessMul = CURVE_FLATNESS / m_inv_scale;
537 } else if (cosmetic) {
538 curvynessAdd= width / 2;
539 curvynessMul= float(CURVE_FLATNESS);
540 } else {
541 curvynessAdd = width * m_inv_scale;
542 curvynessMul = CURVE_FLATNESS / m_inv_scale;
543 }
544
545 if (count < 2)
546 return;
547
548 bool needsClose = false;
549 if (implicitClose) {
550 if (pts[0] != pts[count * 2 - 2] || pts[1] != pts[count * 2 - 1])
551 needsClose = true;
552 }
553
554 const qreal *firstPts = pts;
555 const qreal *endPts = pts + (count<<1);
556 m_dash_stroker.begin(this);
557
558 if (!types) {
559 m_dash_stroker.moveTo(pts[0], pts[1]);
560 pts += 2;
561 while (pts < endPts) {
562 m_dash_stroker.lineTo(pts[0], pts[1]);
563 pts += 2;
564 }
565 } else {
566 while (pts < endPts) {
567 switch (*types) {
568 case QPainterPath::MoveToElement:
569 m_dash_stroker.moveTo(pts[0], pts[1]);
570 pts += 2;
571 ++types;
572 break;
573 case QPainterPath::LineToElement:
574 m_dash_stroker.lineTo(pts[0], pts[1]);
575 pts += 2;
576 ++types;
577 break;
578 case QPainterPath::CurveToElement: {
579 QBezier b = QBezier::fromPoints(*(((const QPointF *) pts) - 1),
580 *(((const QPointF *) pts)),
581 *(((const QPointF *) pts) + 1),
582 *(((const QPointF *) pts) + 2));
583 QRectF bounds = b.bounds();
584 float rad = qMax(bounds.width(), bounds.height());
585 int threshold = qMin<float>(64, (rad + curvynessAdd) * curvynessMul);
586 if (threshold < 4)
587 threshold = 4;
588
589 qreal threshold_minus_1 = threshold - 1;
590 for (int i=0; i<threshold; ++i) {
591 QPointF pt = b.pointAt(i / threshold_minus_1);
592 m_dash_stroker.lineTo(pt.x(), pt.y());
593 }
594 pts += 6;
595 types += 3;
596 break; }
597 default: break;
598 }
599 }
600 }
601 if (needsClose)
602 m_dash_stroker.lineTo(firstPts[0], firstPts[1]);
603
604 m_dash_stroker.end();
605}
606
607QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define CURVE_FLATNESS
static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *)
static void skipDuplicatePoints(const qreal **pts, const qreal *endPts)
static void qdashprocessor_lineTo(qreal x, qreal y, void *data)