1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtOpenGL module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qtriangulatingstroker_p.h"
47 #define CURVE_FLATNESS Q_PI / 8
52 void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur,
53 bool implicitClose, bool endsAtStart)
57 } else if (implicitClose) {
64 int count = m_vertices.size();
66 // Copy the (x, y) values because QDataBuffer::add(const float& t)
67 // may resize the buffer, which will leave t pointing at the
68 // previous buffer's memory region if we don't copy first.
69 float x = m_vertices.at(count-2);
70 float y = m_vertices.at(count-1);
76 void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &)
78 const qreal *pts = path.points();
79 const QPainterPath::ElementType *types = path.elements();
80 int count = path.elementCount();
84 float realWidth = qpen_widthf(pen);
88 m_width = realWidth / 2;
90 bool cosmetic = pen.isCosmetic();
92 m_width = m_width * m_inv_scale;
95 m_join_style = qpen_joinStyle(pen);
96 m_cap_style = qpen_capStyle(pen);
98 m_miter_limit = pen.miterLimit() * qpen_widthf(pen);
100 // The curvyness is based on the notion that I originally wanted
101 // roughly one line segment pr 4 pixels. This may seem little, but
102 // because we sample at constantly incrementing B(t) E [0<t<1], we
103 // will get longer segments where the curvature is small and smaller
104 // segments when the curvature is high.
106 // To get a rough idea of the length of each curve, I pretend that
107 // the curve is a 90 degree arc, whose radius is
108 // qMax(curveBounds.width, curveBounds.height). Based on this
109 // logic we can estimate the length of the outline edges based on
110 // the radius + a pen width and adjusting for scale factors
111 // depending on if the pen is cosmetic or not.
113 // The curvyness value of PI/14 was based on,
114 // arcLength = 2*PI*r/4 = PI*r/2 and splitting length into somewhere
115 // between 3 and 8 where 5 seemed to be give pretty good results
116 // hence: Q_PI/14. Lower divisors will give more detail at the
117 // direct cost of performance.
119 // simplfy pens that are thin in device size (2px wide or less)
120 if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) {
121 if (m_cap_style == Qt::RoundCap)
122 m_cap_style = Qt::SquareCap;
123 if (m_join_style == Qt::RoundJoin)
124 m_join_style = Qt::MiterJoin;
125 m_curvyness_add = 0.5;
126 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
128 } else if (cosmetic) {
129 m_curvyness_add = realWidth / 2;
130 m_curvyness_mul = CURVE_FLATNESS;
131 m_roundness = qMax<int>(4, realWidth * CURVE_FLATNESS);
133 m_curvyness_add = m_width;
134 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
135 m_roundness = qMax<int>(4, realWidth * m_curvyness_mul);
138 // Over this level of segmentation, there doesn't seem to be any
139 // benefit, even for huge penWidth
140 if (m_roundness > 24)
143 m_sin_theta = qFastSin(Q_PI / m_roundness);
144 m_cos_theta = qFastCos(Q_PI / m_roundness);
146 const qreal *endPts = pts + (count<<1);
147 const qreal *startPts = 0;
149 Qt::PenCapStyle cap = m_cap_style;
152 // skip duplicate points
153 while((pts + 2) < endPts && pts[0] == pts[2] && pts[1] == pts[3])
155 if ((pts + 2) == endPts)
160 bool endsAtStart = startPts[0] == *(endPts-2) && startPts[1] == *(endPts-1);
162 if (endsAtStart || path.hasImplicitClose())
163 m_cap_style = Qt::FlatCap;
169 while (pts < endPts) {
170 if (m_cx != pts[0] || m_cy != pts[1]) {
177 endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
180 bool endsAtStart = false;
181 while (pts < endPts) {
183 case QPainterPath::MoveToElement: {
184 if (pts != path.points())
185 endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
188 int end = (endPts - pts) / 2;
189 int i = 2; // Start looking to ahead since we never have two moveto's in a row
190 while (i<end && types[i] != QPainterPath::MoveToElement) {
193 endsAtStart = startPts[0] == pts[i*2 - 2] && startPts[1] == pts[i*2 - 1];
194 if (endsAtStart || path.hasImplicitClose())
195 m_cap_style = Qt::FlatCap;
202 case QPainterPath::LineToElement:
203 if (*(types - 1) != QPainterPath::MoveToElement)
209 case QPainterPath::CurveToElement:
210 if (*(types - 1) != QPainterPath::MoveToElement)
222 endCapOrJoinClosed(startPts, pts-2, path.hasImplicitClose(), endsAtStart);
226 void QTriangulatingStroker::moveTo(const qreal *pts)
233 normalVector(m_cx, m_cy, x2, y2, &m_nvx, &m_nvy);
236 // To acheive jumps we insert zero-area tringles. This is done by
237 // adding two identical points in both the end of previous strip
238 // and beginning of next strip
239 bool invisibleJump = m_vertices.size();
241 switch (m_cap_style) {
244 m_vertices.add(m_cx + m_nvx);
245 m_vertices.add(m_cy + m_nvy);
248 case Qt::SquareCap: {
249 float sx = m_cx - m_nvy;
250 float sy = m_cy + m_nvx;
252 m_vertices.add(sx + m_nvx);
253 m_vertices.add(sy + m_nvy);
255 emitLineSegment(sx, sy, m_nvx, m_nvy);
258 QVarLengthArray<float> points;
259 arcPoints(m_cx, m_cy, m_cx + m_nvx, m_cy + m_nvy, m_cx - m_nvx, m_cy - m_nvy, points);
260 m_vertices.resize(m_vertices.size() + points.size() + 2 * int(invisibleJump));
261 int count = m_vertices.size();
263 int end = points.size() / 2;
264 while (front != end) {
265 m_vertices.at(--count) = points[2 * end - 1];
266 m_vertices.at(--count) = points[2 * end - 2];
270 m_vertices.at(--count) = points[2 * front + 1];
271 m_vertices.at(--count) = points[2 * front + 0];
276 m_vertices.at(count - 1) = m_vertices.at(count + 1);
277 m_vertices.at(count - 2) = m_vertices.at(count + 0);
280 default: break; // ssssh gcc...
282 emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
285 void QTriangulatingStroker::cubicTo(const qreal *pts)
287 const QPointF *p = (const QPointF *) pts;
288 QBezier bezier = QBezier::fromPoints(*(p - 1), p[0], p[1], p[2]);
290 QRectF bounds = bezier.bounds();
291 float rad = qMax(bounds.width(), bounds.height());
292 int threshold = qMin<float>(64, (rad + m_curvyness_add) * m_curvyness_mul);
295 qreal threshold_minus_1 = threshold - 1;
298 float cx = m_cx, cy = m_cy;
301 for (int i=1; i<threshold; ++i) {
302 qreal t = qreal(i) / threshold_minus_1;
303 QPointF p = bezier.pointAt(t);
307 normalVector(cx, cy, x, y, &vx, &vy);
309 emitLineSegment(x, y, vx, vy);
322 void QTriangulatingStroker::join(const qreal *pts)
324 // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
325 normalVector(m_cx, m_cy, pts[0], pts[1], &m_nvx, &m_nvy);
327 switch (m_join_style) {
330 case Qt::SvgMiterJoin:
331 case Qt::MiterJoin: {
332 // Find out on which side the join should be.
333 int count = m_vertices.size();
334 float prevNvx = m_vertices.at(count - 2) - m_cx;
335 float prevNvy = m_vertices.at(count - 1) - m_cy;
336 float xprod = prevNvx * m_nvy - prevNvy * m_nvx;
337 float px, py, qx, qy;
339 // If the segments are parallel, use bevel join.
340 if (qFuzzyIsNull(xprod))
343 // Find the corners of the previous and next segment to join.
345 px = m_vertices.at(count - 2);
346 py = m_vertices.at(count - 1);
350 px = m_vertices.at(count - 4);
351 py = m_vertices.at(count - 3);
356 // Find intersection point.
357 float pu = px * prevNvx + py * prevNvy;
358 float qv = qx * m_nvx + qy * m_nvy;
359 float ix = (m_nvy * pu - prevNvy * qv) / xprod;
360 float iy = (prevNvx * qv - m_nvx * pu) / xprod;
362 // Check that the distance to the intersection point is less than the miter limit.
363 if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) {
370 // Do a plain bevel join if the miter limit is exceeded or if
371 // the lines are parallel. This is not what the raster
372 // engine's stroker does, but it is both faster and similar to
373 // what some other graphics API's do.
376 case Qt::RoundJoin: {
377 QVarLengthArray<float> points;
378 int count = m_vertices.size();
379 float prevNvx = m_vertices.at(count - 2) - m_cx;
380 float prevNvy = m_vertices.at(count - 1) - m_cy;
381 if (m_nvx * prevNvy - m_nvy * prevNvx < 0) {
382 arcPoints(0, 0, m_nvx, m_nvy, -prevNvx, -prevNvy, points);
383 for (int i = points.size() / 2; i > 0; --i)
384 emitLineSegment(m_cx, m_cy, points[2 * i - 2], points[2 * i - 1]);
386 arcPoints(0, 0, -prevNvx, -prevNvy, m_nvx, m_nvy, points);
387 for (int i = 0; i < points.size() / 2; ++i)
388 emitLineSegment(m_cx, m_cy, points[2 * i + 0], points[2 * i + 1]);
391 default: break; // gcc warn--
394 emitLineSegment(m_cx, m_cy, m_nvx, m_nvy);
397 void QTriangulatingStroker::endCap(const qreal *)
399 switch (m_cap_style) {
403 emitLineSegment(m_cx + m_nvy, m_cy - m_nvx, m_nvx, m_nvy);
406 QVarLengthArray<float> points;
407 int count = m_vertices.size();
408 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);
410 int end = points.size() / 2;
411 while (front != end) {
412 m_vertices.add(points[2 * end - 2]);
413 m_vertices.add(points[2 * end - 1]);
417 m_vertices.add(points[2 * front + 0]);
418 m_vertices.add(points[2 * front + 1]);
422 default: break; // to shut gcc up...
426 void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points)
428 float dx1 = fromX - cx;
429 float dy1 = fromY - cy;
430 float dx2 = toX - cx;
431 float dy2 = toY - cy;
433 // while more than 180 degrees left:
434 while (dx1 * dy2 - dx2 * dy1 < 0) {
435 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
436 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
439 points.append(cx + dx1);
440 points.append(cy + dy1);
443 // while more than 90 degrees left:
444 while (dx1 * dx2 + dy1 * dy2 < 0) {
445 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
446 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
449 points.append(cx + dx1);
450 points.append(cy + dy1);
453 // while more than 0 degrees left:
454 while (dx1 * dy2 - dx2 * dy1 > 0) {
455 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
456 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
459 points.append(cx + dx1);
460 points.append(cy + dy1);
463 // remove last point which was rotated beyond [toX, toY].
464 if (!points.isEmpty())
465 points.resize(points.size() - 2);
468 static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
470 ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::MoveToElement, x, y);
473 static void qdashprocessor_lineTo(qreal x, qreal y, void *data)
475 ((QDashedStrokeProcessor *) data)->addElement(QPainterPath::LineToElement, x, y);
478 static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *)
480 Q_ASSERT(0); // The dasher should not produce curves...
483 QDashedStrokeProcessor::QDashedStrokeProcessor()
484 : m_points(0), m_types(0),
485 m_dash_stroker(0), m_inv_scale(1)
487 m_dash_stroker.setMoveToHook(qdashprocessor_moveTo);
488 m_dash_stroker.setLineToHook(qdashprocessor_lineTo);
489 m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo);
492 void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip)
495 const qreal *pts = path.points();
496 const QPainterPath::ElementType *types = path.elements();
497 int count = path.elementCount();
499 bool cosmetic = pen.isCosmetic();
503 m_points.reserve(path.elementCount());
504 m_types.reserve(path.elementCount());
506 qreal width = qpen_widthf(pen);
510 m_dash_stroker.setDashPattern(pen.dashPattern());
511 m_dash_stroker.setStrokeWidth(cosmetic ? width * m_inv_scale : width);
512 m_dash_stroker.setDashOffset(pen.dashOffset());
513 m_dash_stroker.setMiterLimit(pen.miterLimit());
514 m_dash_stroker.setClipRect(clip);
516 float curvynessAdd, curvynessMul;
518 // simplfy pens that are thin in device size (2px wide or less)
519 if (width < 2.5 && (cosmetic || m_inv_scale == 1)) {
521 curvynessMul = CURVE_FLATNESS / m_inv_scale;
522 } else if (cosmetic) {
523 curvynessAdd= width / 2;
524 curvynessMul= CURVE_FLATNESS;
526 curvynessAdd = width * m_inv_scale;
527 curvynessMul = CURVE_FLATNESS / m_inv_scale;
533 const qreal *endPts = pts + (count<<1);
535 m_dash_stroker.begin(this);
538 m_dash_stroker.moveTo(pts[0], pts[1]);
540 while (pts < endPts) {
541 m_dash_stroker.lineTo(pts[0], pts[1]);
545 while (pts < endPts) {
547 case QPainterPath::MoveToElement:
548 m_dash_stroker.moveTo(pts[0], pts[1]);
552 case QPainterPath::LineToElement:
553 m_dash_stroker.lineTo(pts[0], pts[1]);
557 case QPainterPath::CurveToElement: {
558 QBezier b = QBezier::fromPoints(*(((const QPointF *) pts) - 1),
559 *(((const QPointF *) pts)),
560 *(((const QPointF *) pts) + 1),
561 *(((const QPointF *) pts) + 2));
562 QRectF bounds = b.bounds();
563 float rad = qMax(bounds.width(), bounds.height());
564 int threshold = qMin<float>(64, (rad + curvynessAdd) * curvynessMul);
568 qreal threshold_minus_1 = threshold - 1;
569 for (int i=0; i<threshold; ++i) {
570 QPointF pt = b.pointAt(i / threshold_minus_1);
571 m_dash_stroker.lineTo(pt.x(), pt.y());
581 m_dash_stroker.end();