2 * Copyright (C) 2008-2009 Fredrik Höglund <fredrik@kde.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
20 #include "borderarcstroker.h"
24 # define K_ALIGN(n) __attribute((aligned(n)))
28 #elif defined(__INTEL_COMPILER)
29 # define K_ALIGN(n) __declspec(align(n))
36 # include <xmmintrin.h>
43 // This is a helper class used by BorderArcStroker
48 KCubicBezier(const QPointF
&p0
, const QPointF
&p1
, const QPointF
&p2
, const QPointF
&p3
);
50 void split(KCubicBezier
*a
, KCubicBezier
*b
, qreal t
= .5) const;
51 KCubicBezier
section(qreal t1
, qreal t2
) const;
52 KCubicBezier
leftSection(qreal t
) const;
53 KCubicBezier
rightSection(qreal t
) const;
54 KCubicBezier
derivative() const;
55 QPointF
pointAt(qreal t
) const;
56 QPointF
deltaAt(qreal t
) const;
57 QLineF
normalVectorAt(qreal t
) const;
58 qreal
slopeAt(qreal t
) const;
59 qreal
tAtLength(qreal l
) const;
60 qreal
tAtIntersection(const QLineF
&line
) const;
63 qreal
convexHullLength() const;
65 QPointF
p0() const { return QPointF(x0
, y0
); }
66 QPointF
p1() const { return QPointF(x1
, y1
); }
67 QPointF
p2() const { return QPointF(x2
, y2
); }
68 QPointF
p3() const { return QPointF(x3
, y3
); }
71 qreal x0
, y0
, x1
, y1
, x2
, y2
, x3
, y3
;
75 KCubicBezier::KCubicBezier(const QPointF
&p0
, const QPointF
&p1
, const QPointF
&p2
, const QPointF
&p3
)
87 // Splits the curve at t, using the de Casteljau algorithm.
88 // This function is not atomic, so do not pass a pointer to the curve being split.
89 void KCubicBezier::split(KCubicBezier
*a
, KCubicBezier
*b
, qreal t
) const
97 a
->x1
= x0
+ (x1
- x0
) * t
;
98 a
->y1
= y0
+ (y1
- y0
) * t
;
100 b
->x2
= x2
+ (x3
- x2
) * t
;
101 b
->y2
= y2
+ (y3
- y2
) * t
;
103 // The point on the line (p1, p2).
104 const qreal x
= x1
+ (x2
- x1
) * t
;
105 const qreal y
= y1
+ (y2
- y1
) * t
;
107 a
->x2
= a
->x1
+ (x
- a
->x1
) * t
;
108 a
->y2
= a
->y1
+ (y
- a
->y1
) * t
;
110 b
->x1
= x
+ (b
->x2
- x
) * t
;
111 b
->y1
= y
+ (b
->y2
- y
) * t
;
113 a
->x3
= b
->x0
= a
->x2
+ (b
->x1
- a
->x2
) * t
;
114 a
->y3
= b
->y0
= a
->y2
+ (b
->y1
- a
->y2
) * t
;
117 // Returns a new bezier curve that is the interval between t1 and t2 in this curve
118 KCubicBezier
KCubicBezier::section(qreal t1
, qreal t2
) const
120 return leftSection(t2
).rightSection(t1
/ t2
);
123 // Returns a new bezier curve that is the section of this curve left of t
124 KCubicBezier
KCubicBezier::leftSection(qreal t
) const
126 KCubicBezier left
, right
;
127 split(&left
, &right
, t
);
131 // Returns a new bezier curve that is the section of this curve right of t
132 KCubicBezier
KCubicBezier::rightSection(qreal t
) const
134 KCubicBezier left
, right
;
135 split(&left
, &right
, t
);
139 // Returns the point at t
140 QPointF
KCubicBezier::pointAt(qreal t
) const
142 const qreal m_t
= 1 - t
;
143 const qreal m_t2
= m_t
* m_t
;
144 const qreal m_t3
= m_t2
* m_t
;
145 const qreal t2
= t
* t
;
146 const qreal t3
= t2
* t
;
148 const qreal x
= m_t3
* x0
+ 3 * t
* m_t2
* x1
+ 3 * t2
* m_t
* x2
+ t3
* x3
;
149 const qreal y
= m_t3
* y0
+ 3 * t
* m_t2
* y1
+ 3 * t2
* m_t
* y2
+ t3
* y3
;
151 return QPointF(x
, y
);
154 // Returns the delta at t, i.e. the first derivative of the cubic equation at t.
155 QPointF
KCubicBezier::deltaAt(qreal t
) const
157 const qreal m_t
= 1 - t
;
158 const qreal m_t2
= m_t
* m_t
;
159 const qreal t2
= t
* t
;
161 const qreal dx
= 3 * (x1
- x0
) * m_t2
+ 3 * (x2
- x1
) * 2 * t
* m_t
+ 3 * (x3
- x2
) * t2
;
162 const qreal dy
= 3 * (y1
- y0
) * m_t2
+ 3 * (y2
- y1
) * 2 * t
* m_t
+ 3 * (y3
- y2
) * t2
;
164 return QPointF(dx
, dy
);
167 // Returns the slope at t (dy / dx)
168 qreal
KCubicBezier::slopeAt(qreal t
) const
170 const QPointF delta
= deltaAt(t
);
171 return qFuzzyCompare(delta
.x() + 1.0, 1.0) ?
172 (delta
.y() < 0 ? -1 : 1) : delta
.y() / delta
.x();
175 // Returns the normal vector at t
176 QLineF
KCubicBezier::normalVectorAt(qreal t
) const
178 const QPointF point
= pointAt(t
);
179 const QPointF delta
= deltaAt(t
);
181 return QLineF(point
, point
+ delta
).normalVector();
184 // Returns a curve that is the first derivative of this curve.
185 // The first derivative of a cubic bezier curve is a quadratic bezier curve,
186 // but this function elevates it to a cubic curve.
187 KCubicBezier
KCubicBezier::derivative() const
191 c
.x0
= 3 * (x1
- x0
);
192 c
.x1
= x1
- x0
+ 2 * (x2
- x1
);
193 c
.x2
= 2 * (x2
- x1
) + x3
- x2
;
194 c
.x3
= 3 * (x3
- x2
);
196 c
.y0
= 3 * (y1
- y0
);
197 c
.y1
= y1
- y0
+ 2 * (y2
- y1
);
198 c
.y2
= 2 * (y2
- y1
) + y3
- y2
;
199 c
.y3
= 3 * (y3
- y2
);
204 // Returns the sum of the lengths of the lines connecting the control points
205 qreal
KCubicBezier::convexHullLength() const
208 K_ALIGN(16) float vals
[4];
211 m1
= _mm_set_ps(0, x1
- x0
, x2
- x1
, x3
- x2
);
212 m2
= _mm_set_ps(0, y1
- y0
, y2
- y1
, y3
- y2
);
213 m1
= _mm_add_ps(_mm_mul_ps(m1
, m1
), _mm_mul_ps(m2
, m2
));
214 _mm_store_ps(vals
, _mm_sqrt_ps(m1
));
216 return vals
[0] + vals
[1] + vals
[2];
218 return QLineF(x0
, y0
, x1
, y1
).length() + QLineF(x1
, y1
, x2
, y2
).length() + QLineF(x2
, y2
, x3
, y3
).length();
222 // Returns the chord, i.e. the line that connects the two endpoints of the curve
223 QLineF
KCubicBezier::chord() const
225 return QLineF(x0
, y0
, x3
, y3
);
228 // Returns the arc length of the bezier curve, computed by recursively subdividing the
229 // curve until the difference between the length of the convex hull of the section
230 // and its chord is less than .01 device units.
231 qreal
KCubicBezier::length() const
233 const qreal error
= .01;
234 const qreal len
= convexHullLength();
236 if ((len
- chord().length()) > error
) {
237 KCubicBezier left
, right
;
238 split(&left
, &right
);
239 return left
.length() + right
.length();
245 // Returns the value for the t parameter that corresponds to the point
246 // l device units from the starting point of the curve.
247 qreal
KCubicBezier::tAtLength(qreal l
) const
249 const qreal error
= 0.1;
254 qreal len
= length();
256 if (l
> len
|| qFuzzyCompare(l
+ 1.0, len
+ 1.0))
264 const qreal t
= lowerT
+ (upperT
- lowerT
) / 2;
265 const qreal len
= leftSection(t
).length();
267 if (qAbs(l
- len
) < error
)
277 // Returns the value of t at the point where the given line intersects
278 // the bezier curve. The line is treated as an infinitely long line.
279 // Note that this implementation assumes that there is a single point of
280 // intersection, that both control points are on the same side of
281 // the curve, and that the curve is not self intersecting.
282 qreal
KCubicBezier::tAtIntersection(const QLineF
&line
) const
284 const qreal error
= 0.1;
286 // Extend the line to make it a reasonable approximation of an infinitely long line
287 const qreal len
= line
.length();
288 const QLineF l
= QLineF(line
.pointAt(1.0 / len
* -1e10
), line
.pointAt(1.0 / len
* 1e10
));
290 // Check if the line intersects the curve at all
291 if (chord().intersect(l
, 0) != QLineF::BoundedIntersection
)
297 KCubicBezier c
= *this;
301 const qreal t
= lowerT
+ (upperT
- lowerT
) / 2;
302 if (c
.length() < error
)
305 KCubicBezier left
, right
;
306 c
.split(&left
, &right
);
308 // If the line intersects the left section
309 if (left
.chord().intersect(l
, 0) == QLineF::BoundedIntersection
) {
321 // ----------------------------------------------------------------------------
325 BorderArcStroker::BorderArcStroker()
329 BorderArcStroker::~BorderArcStroker()
333 QPainterPath
BorderArcStroker::createStroke(qreal
*nextOffset
) const
335 const QRectF outerRect
= rect
;
336 const QRectF innerRect
= rect
.adjusted(hlw
, vlw
, -hlw
, -vlw
);
338 QPainterPath innerPath
, outerPath
;
339 innerPath
.arcMoveTo(innerRect
, angle
);
340 outerPath
.arcMoveTo(outerRect
, angle
);
341 innerPath
.arcTo(innerRect
, angle
, sweepLength
);
342 outerPath
.arcTo(outerRect
, angle
, sweepLength
);
344 Q_ASSERT(qAbs(sweepLength
) <= 90);
345 Q_ASSERT(innerPath
.elementCount() == 4 && outerPath
.elementCount() == 4);
347 const KCubicBezier
inner(innerPath
.elementAt(0), innerPath
.elementAt(1), innerPath
.elementAt(2), innerPath
.elementAt(3));
348 const KCubicBezier
outer(outerPath
.elementAt(0), outerPath
.elementAt(1), outerPath
.elementAt(2), outerPath
.elementAt(3));
350 qreal a
= std::fmod(angle
, qreal(360.0));
354 // Figure out which border we're starting from
356 if ((a
>= 0 && a
< 90) || (a
>= 180 && a
< 270))
357 initialWidth
= sweepLength
> 0 ? hlw
: vlw
;
359 initialWidth
= sweepLength
> 0 ? vlw
: hlw
;
361 const qreal finalWidth
= QLineF(outer
.p3(), inner
.p3()).length();
362 const qreal dashAspect
= (pattern
[0] / initialWidth
);
363 const qreal spaceAspect
= (pattern
[1] / initialWidth
);
364 const qreal length
= inner
.length();
367 qreal innerStart
= 0, outerStart
= 0;
371 qreal offset
= fmod(patternOffset
, pattern
[0] + pattern
[1]);
373 offset
+= pattern
[0] + pattern
[1];
377 if (offset
>= pattern
[0]) {
378 offset
-= pattern
[0];
385 while (innerStart
< 1) {
386 const qreal lineWidth
= QLineF(outer
.pointAt(outerStart
), inner
.pointAt(innerStart
)).length();
387 const qreal length
= lineWidth
* (dash
? dashAspect
: spaceAspect
);
391 const qreal innerEnd
= inner
.tAtLength(pos
);
392 const QLineF normal
= inner
.normalVectorAt(innerEnd
);
393 const qreal outerEnd
= outer
.tAtIntersection(normal
);
396 // The outer and inner curves of the dash
397 const KCubicBezier a
= outer
.section(outerStart
, outerEnd
);
398 const KCubicBezier b
= inner
.section(innerStart
, innerEnd
);
400 // Add the dash to the path
402 path
.cubicTo(a
.p1(), a
.p2(), a
.p3());
404 path
.cubicTo(b
.p2(), b
.p1(), b
.p0());
408 innerStart
= innerEnd
;
409 outerStart
= outerEnd
;
416 const qreal remainder
= pos
- length
;
419 *nextOffset
= -remainder
;
421 *nextOffset
= (pattern
[0] / initialWidth
) * finalWidth
- remainder
;