| 1 | /**************************************************************************** | 
|---|
| 2 | ** | 
|---|
| 3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | 
|---|
| 4 | ** All rights reserved. | 
|---|
| 5 | ** Contact: Nokia Corporation (qt-info@nokia.com) | 
|---|
| 6 | ** | 
|---|
| 7 | ** This file is part of the QtGui module of the Qt Toolkit. | 
|---|
| 8 | ** | 
|---|
| 9 | ** $QT_BEGIN_LICENSE:LGPL$ | 
|---|
| 10 | ** Commercial Usage | 
|---|
| 11 | ** Licensees holding valid Qt Commercial licenses may use this file in | 
|---|
| 12 | ** accordance with the Qt Commercial License Agreement provided with the | 
|---|
| 13 | ** Software or, alternatively, in accordance with the terms contained in | 
|---|
| 14 | ** a written agreement between you and Nokia. | 
|---|
| 15 | ** | 
|---|
| 16 | ** GNU Lesser General Public License Usage | 
|---|
| 17 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
|---|
| 18 | ** General Public License version 2.1 as published by the Free Software | 
|---|
| 19 | ** Foundation and appearing in the file LICENSE.LGPL included in the | 
|---|
| 20 | ** packaging of this file.  Please review the following information to | 
|---|
| 21 | ** ensure the GNU Lesser General Public License version 2.1 requirements | 
|---|
| 22 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | 
|---|
| 23 | ** | 
|---|
| 24 | ** In addition, as a special exception, Nokia gives you certain additional | 
|---|
| 25 | ** rights.  These rights are described in the Nokia Qt LGPL Exception | 
|---|
| 26 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | 
|---|
| 27 | ** | 
|---|
| 28 | ** GNU General Public License Usage | 
|---|
| 29 | ** Alternatively, this file may be used under the terms of the GNU | 
|---|
| 30 | ** General Public License version 3.0 as published by the Free Software | 
|---|
| 31 | ** Foundation and appearing in the file LICENSE.GPL included in the | 
|---|
| 32 | ** packaging of this file.  Please review the following information to | 
|---|
| 33 | ** ensure the GNU General Public License version 3.0 requirements will be | 
|---|
| 34 | ** met: http://www.gnu.org/copyleft/gpl.html. | 
|---|
| 35 | ** | 
|---|
| 36 | ** If you have questions regarding the use of this file, please contact | 
|---|
| 37 | ** Nokia at qt-info@nokia.com. | 
|---|
| 38 | ** $QT_END_LICENSE$ | 
|---|
| 39 | ** | 
|---|
| 40 | ****************************************************************************/ | 
|---|
| 41 |  | 
|---|
| 42 | #include "qbezier_p.h" | 
|---|
| 43 | #include <qdebug.h> | 
|---|
| 44 | #include <qline.h> | 
|---|
| 45 | #include <qpolygon.h> | 
|---|
| 46 | #include <qvector.h> | 
|---|
| 47 | #include <qlist.h> | 
|---|
| 48 | #include <qmath.h> | 
|---|
| 49 |  | 
|---|
| 50 | #include <private/qnumeric_p.h> | 
|---|
| 51 | #include <private/qmath_p.h> | 
|---|
| 52 |  | 
|---|
| 53 | QT_BEGIN_NAMESPACE | 
|---|
| 54 |  | 
|---|
| 55 | //#define QDEBUG_BEZIER | 
|---|
| 56 |  | 
|---|
| 57 | #ifdef FLOAT_ACCURACY | 
|---|
| 58 | #define INV_EPS (1L<<23) | 
|---|
| 59 | #else | 
|---|
| 60 | /* The value of 1.0 / (1L<<14) is enough for most applications */ | 
|---|
| 61 | #define INV_EPS (1L<<14) | 
|---|
| 62 | #endif | 
|---|
| 63 |  | 
|---|
| 64 | #ifndef M_SQRT2 | 
|---|
| 65 | #define M_SQRT2 1.41421356237309504880 | 
|---|
| 66 | #endif | 
|---|
| 67 |  | 
|---|
| 68 | #define log2(x) (qLn(x)/qLn(2.)) | 
|---|
| 69 |  | 
|---|
| 70 | static inline qreal log4(qreal x) | 
|---|
| 71 | { | 
|---|
| 72 | return qreal(0.5) * log2(x); | 
|---|
| 73 | } | 
|---|
| 74 |  | 
|---|
| 75 | /*! | 
|---|
| 76 | \internal | 
|---|
| 77 | */ | 
|---|
| 78 | QBezier QBezier::fromPoints(const QPointF &p1, const QPointF &p2, | 
|---|
| 79 | const QPointF &p3, const QPointF &p4) | 
|---|
| 80 | { | 
|---|
| 81 | QBezier b; | 
|---|
| 82 | b.x1 = p1.x(); | 
|---|
| 83 | b.y1 = p1.y(); | 
|---|
| 84 | b.x2 = p2.x(); | 
|---|
| 85 | b.y2 = p2.y(); | 
|---|
| 86 | b.x3 = p3.x(); | 
|---|
| 87 | b.y3 = p3.y(); | 
|---|
| 88 | b.x4 = p4.x(); | 
|---|
| 89 | b.y4 = p4.y(); | 
|---|
| 90 | return b; | 
|---|
| 91 | } | 
|---|
| 92 |  | 
|---|
| 93 | /*! | 
|---|
| 94 | \internal | 
|---|
| 95 | */ | 
|---|
| 96 | QPolygonF QBezier::toPolygon(qreal bezier_flattening_threshold) const | 
|---|
| 97 | { | 
|---|
| 98 | // flattening is done by splitting the bezier until we can replace the segment by a straight | 
|---|
| 99 | // line. We split further until the control points are close enough to the line connecting the | 
|---|
| 100 | // boundary points. | 
|---|
| 101 | // | 
|---|
| 102 | // the Distance of a point p from a line given by the points (a,b) is given by: | 
|---|
| 103 | // | 
|---|
| 104 | // d = abs( (bx - ax)(ay - py) - (by - ay)(ax - px) ) / line_length | 
|---|
| 105 | // | 
|---|
| 106 | // We can stop splitting if both control points are close enough to the line. | 
|---|
| 107 | // To make the algorithm faster we use the manhattan length of the line. | 
|---|
| 108 |  | 
|---|
| 109 | QPolygonF polygon; | 
|---|
| 110 | polygon.append(QPointF(x1, y1)); | 
|---|
| 111 | addToPolygon(&polygon, bezier_flattening_threshold); | 
|---|
| 112 | return polygon; | 
|---|
| 113 | } | 
|---|
| 114 |  | 
|---|
| 115 | QBezier QBezier::mapBy(const QTransform &transform) const | 
|---|
| 116 | { | 
|---|
| 117 | return QBezier::fromPoints(transform.map(pt1()), transform.map(pt2()), transform.map(pt3()), transform.map(pt4())); | 
|---|
| 118 | } | 
|---|
| 119 |  | 
|---|
| 120 | QBezier QBezier::getSubRange(qreal t0, qreal t1) const | 
|---|
| 121 | { | 
|---|
| 122 | QBezier result; | 
|---|
| 123 | QBezier temp; | 
|---|
| 124 |  | 
|---|
| 125 | // cut at t1 | 
|---|
| 126 | if (qFuzzyIsNull(t1 - qreal(1.))) { | 
|---|
| 127 | result = *this; | 
|---|
| 128 | } else { | 
|---|
| 129 | temp = *this; | 
|---|
| 130 | temp.parameterSplitLeft(t1, &result); | 
|---|
| 131 | } | 
|---|
| 132 |  | 
|---|
| 133 | // cut at t0 | 
|---|
| 134 | if (!qFuzzyIsNull(t0)) | 
|---|
| 135 | result.parameterSplitLeft(t0 / t1, &temp); | 
|---|
| 136 |  | 
|---|
| 137 | return result; | 
|---|
| 138 | } | 
|---|
| 139 |  | 
|---|
| 140 | static inline int quadraticRoots(qreal a, qreal b, qreal c, | 
|---|
| 141 | qreal *x1, qreal *x2) | 
|---|
| 142 | { | 
|---|
| 143 | if (qFuzzyIsNull(a)) { | 
|---|
| 144 | if (qFuzzyIsNull(b)) | 
|---|
| 145 | return 0; | 
|---|
| 146 | *x1 = *x2 = (-c / b); | 
|---|
| 147 | return 1; | 
|---|
| 148 | } else { | 
|---|
| 149 | const qreal det = b * b - 4 * a * c; | 
|---|
| 150 | if (qFuzzyIsNull(det)) { | 
|---|
| 151 | *x1 = *x2 = -b / (2 * a); | 
|---|
| 152 | return 1; | 
|---|
| 153 | } | 
|---|
| 154 | if (det > 0) { | 
|---|
| 155 | if (qFuzzyIsNull(b)) { | 
|---|
| 156 | *x2 = qSqrt(-c / a); | 
|---|
| 157 | *x1 = -(*x2); | 
|---|
| 158 | return 2; | 
|---|
| 159 | } | 
|---|
| 160 | const qreal stableA = b / (2 * a); | 
|---|
| 161 | const qreal stableB = c / (a * stableA * stableA); | 
|---|
| 162 | const qreal stableC = -1 - qSqrt(1 - stableB); | 
|---|
| 163 | *x2 = stableA * stableC; | 
|---|
| 164 | *x1 = (stableA * stableB) / stableC; | 
|---|
| 165 | return 2; | 
|---|
| 166 | } else | 
|---|
| 167 | return 0; | 
|---|
| 168 | } | 
|---|
| 169 | } | 
|---|
| 170 |  | 
|---|
| 171 | static inline bool findInflections(qreal a, qreal b, qreal c, | 
|---|
| 172 | qreal *t1 , qreal *t2, qreal *tCups) | 
|---|
| 173 | { | 
|---|
| 174 | qreal r1 = 0, r2 = 0; | 
|---|
| 175 |  | 
|---|
| 176 | short rootsCount = quadraticRoots(a, b, c, &r1, &r2); | 
|---|
| 177 |  | 
|---|
| 178 | if (rootsCount >= 1) { | 
|---|
| 179 | if (r1 < r2) { | 
|---|
| 180 | *t1 = r1; | 
|---|
| 181 | *t2 = r2; | 
|---|
| 182 | } else { | 
|---|
| 183 | *t1 = r2; | 
|---|
| 184 | *t2 = r1; | 
|---|
| 185 | } | 
|---|
| 186 | if (!qFuzzyIsNull(a)) | 
|---|
| 187 | *tCups = 0.5 * (-b / a); | 
|---|
| 188 | else | 
|---|
| 189 | *tCups = 2; | 
|---|
| 190 |  | 
|---|
| 191 | return true; | 
|---|
| 192 | } | 
|---|
| 193 |  | 
|---|
| 194 | return false; | 
|---|
| 195 | } | 
|---|
| 196 |  | 
|---|
| 197 |  | 
|---|
| 198 | void QBezier::addToPolygon(QPolygonF *polygon, qreal bezier_flattening_threshold) const | 
|---|
| 199 | { | 
|---|
| 200 | QBezier beziers[32]; | 
|---|
| 201 | beziers[0] = *this; | 
|---|
| 202 | QBezier *b = beziers; | 
|---|
| 203 |  | 
|---|
| 204 | while (b >= beziers) { | 
|---|
| 205 | // check if we can pop the top bezier curve from the stack | 
|---|
| 206 | qreal y4y1 = b->y4 - b->y1; | 
|---|
| 207 | qreal x4x1 = b->x4 - b->x1; | 
|---|
| 208 | qreal l = qAbs(x4x1) + qAbs(y4y1); | 
|---|
| 209 | qreal d; | 
|---|
| 210 | if (l > 1.) { | 
|---|
| 211 | d = qAbs( (x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2) ) | 
|---|
| 212 | + qAbs( (x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3) ); | 
|---|
| 213 | } else { | 
|---|
| 214 | d = qAbs(b->x1 - b->x2) + qAbs(b->y1 - b->y2) + | 
|---|
| 215 | qAbs(b->x1 - b->x3) + qAbs(b->y1 - b->y3); | 
|---|
| 216 | l = 1.; | 
|---|
| 217 | } | 
|---|
| 218 | if (d < bezier_flattening_threshold*l || b == beziers + 31) { | 
|---|
| 219 | // good enough, we pop it off and add the endpoint | 
|---|
| 220 | polygon->append(QPointF(b->x4, b->y4)); | 
|---|
| 221 | --b; | 
|---|
| 222 | } else { | 
|---|
| 223 | // split, second half of the polygon goes lower into the stack | 
|---|
| 224 | b->split(b+1, b); | 
|---|
| 225 | ++b; | 
|---|
| 226 | } | 
|---|
| 227 | } | 
|---|
| 228 | } | 
|---|
| 229 |  | 
|---|
| 230 | QRectF QBezier::bounds() const | 
|---|
| 231 | { | 
|---|
| 232 | qreal xmin = x1; | 
|---|
| 233 | qreal xmax = x1; | 
|---|
| 234 | if (x2 < xmin) | 
|---|
| 235 | xmin = x2; | 
|---|
| 236 | else if (x2 > xmax) | 
|---|
| 237 | xmax = x2; | 
|---|
| 238 | if (x3 < xmin) | 
|---|
| 239 | xmin = x3; | 
|---|
| 240 | else if (x3 > xmax) | 
|---|
| 241 | xmax = x3; | 
|---|
| 242 | if (x4 < xmin) | 
|---|
| 243 | xmin = x4; | 
|---|
| 244 | else if (x4 > xmax) | 
|---|
| 245 | xmax = x4; | 
|---|
| 246 |  | 
|---|
| 247 | qreal ymin = y1; | 
|---|
| 248 | qreal ymax = y1; | 
|---|
| 249 | if (y2 < ymin) | 
|---|
| 250 | ymin = y2; | 
|---|
| 251 | else if (y2 > ymax) | 
|---|
| 252 | ymax = y2; | 
|---|
| 253 | if (y3 < ymin) | 
|---|
| 254 | ymin = y3; | 
|---|
| 255 | else if (y3 > ymax) | 
|---|
| 256 | ymax = y3; | 
|---|
| 257 | if (y4 < ymin) | 
|---|
| 258 | ymin = y4; | 
|---|
| 259 | else if (y4 > ymax) | 
|---|
| 260 | ymax = y4; | 
|---|
| 261 | return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); | 
|---|
| 262 | } | 
|---|
| 263 |  | 
|---|
| 264 |  | 
|---|
| 265 | enum ShiftResult { | 
|---|
| 266 | Ok, | 
|---|
| 267 | Discard, | 
|---|
| 268 | Split, | 
|---|
| 269 | Circle | 
|---|
| 270 | }; | 
|---|
| 271 |  | 
|---|
| 272 | static ShiftResult good_offset(const QBezier *b1, const QBezier *b2, qreal offset, qreal threshold) | 
|---|
| 273 | { | 
|---|
| 274 | const qreal o2 = offset*offset; | 
|---|
| 275 | const qreal max_dist_line = threshold*offset*offset; | 
|---|
| 276 | const qreal max_dist_normal = threshold*offset; | 
|---|
| 277 | const qreal spacing = 0.25; | 
|---|
| 278 | for (qreal i = spacing; i < 0.99; i += spacing) { | 
|---|
| 279 | QPointF p1 = b1->pointAt(i); | 
|---|
| 280 | QPointF p2 = b2->pointAt(i); | 
|---|
| 281 | qreal d = (p1.x() - p2.x())*(p1.x() - p2.x()) + (p1.y() - p2.y())*(p1.y() - p2.y()); | 
|---|
| 282 | if (qAbs(d - o2) > max_dist_line) | 
|---|
| 283 | return Split; | 
|---|
| 284 |  | 
|---|
| 285 | QPointF normalPoint = b1->normalVector(i); | 
|---|
| 286 | qreal l = qAbs(normalPoint.x()) + qAbs(normalPoint.y()); | 
|---|
| 287 | if (l != 0.) { | 
|---|
| 288 | d = qAbs( normalPoint.x()*(p1.y() - p2.y()) - normalPoint.y()*(p1.x() - p2.x()) ) / l; | 
|---|
| 289 | if (d > max_dist_normal) | 
|---|
| 290 | return Split; | 
|---|
| 291 | } | 
|---|
| 292 | } | 
|---|
| 293 | return Ok; | 
|---|
| 294 | } | 
|---|
| 295 |  | 
|---|
| 296 | static ShiftResult shift(const QBezier *orig, QBezier *shifted, qreal offset, qreal threshold) | 
|---|
| 297 | { | 
|---|
| 298 | int map[4]; | 
|---|
| 299 | bool p1_p2_equal = (orig->x1 == orig->x2 && orig->y1 == orig->y2); | 
|---|
| 300 | bool p2_p3_equal = (orig->x2 == orig->x3 && orig->y2 == orig->y3); | 
|---|
| 301 | bool p3_p4_equal = (orig->x3 == orig->x4 && orig->y3 == orig->y4); | 
|---|
| 302 |  | 
|---|
| 303 | QPointF points[4]; | 
|---|
| 304 | int np = 0; | 
|---|
| 305 | points[np] = QPointF(orig->x1, orig->y1); | 
|---|
| 306 | map[0] = 0; | 
|---|
| 307 | ++np; | 
|---|
| 308 | if (!p1_p2_equal) { | 
|---|
| 309 | points[np] = QPointF(orig->x2, orig->y2); | 
|---|
| 310 | ++np; | 
|---|
| 311 | } | 
|---|
| 312 | map[1] = np - 1; | 
|---|
| 313 | if (!p2_p3_equal) { | 
|---|
| 314 | points[np] = QPointF(orig->x3, orig->y3); | 
|---|
| 315 | ++np; | 
|---|
| 316 | } | 
|---|
| 317 | map[2] = np - 1; | 
|---|
| 318 | if (!p3_p4_equal) { | 
|---|
| 319 | points[np] = QPointF(orig->x4, orig->y4); | 
|---|
| 320 | ++np; | 
|---|
| 321 | } | 
|---|
| 322 | map[3] = np - 1; | 
|---|
| 323 | if (np == 1) | 
|---|
| 324 | return Discard; | 
|---|
| 325 |  | 
|---|
| 326 | QRectF b = orig->bounds(); | 
|---|
| 327 | if (np == 4 && b.width() < .1*offset && b.height() < .1*offset) { | 
|---|
| 328 | qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) + | 
|---|
| 329 | (orig->y1 - orig->y2)*(orig->y1 - orig->y1) * | 
|---|
| 330 | (orig->x3 - orig->x4)*(orig->x3 - orig->x4) + | 
|---|
| 331 | (orig->y3 - orig->y4)*(orig->y3 - orig->y4); | 
|---|
| 332 | qreal dot = (orig->x1 - orig->x2)*(orig->x3 - orig->x4) + | 
|---|
| 333 | (orig->y1 - orig->y2)*(orig->y3 - orig->y4); | 
|---|
| 334 | if (dot < 0 && dot*dot < 0.8*l) | 
|---|
| 335 | // the points are close and reverse dirction. Approximate the whole | 
|---|
| 336 | // thing by a semi circle | 
|---|
| 337 | return Circle; | 
|---|
| 338 | } | 
|---|
| 339 |  | 
|---|
| 340 | QPointF points_shifted[4]; | 
|---|
| 341 |  | 
|---|
| 342 | QLineF prev = QLineF(QPointF(), points[1] - points[0]); | 
|---|
| 343 | QPointF prev_normal = prev.normalVector().unitVector().p2(); | 
|---|
| 344 |  | 
|---|
| 345 | points_shifted[0] = points[0] + offset * prev_normal; | 
|---|
| 346 |  | 
|---|
| 347 | for (int i = 1; i < np - 1; ++i) { | 
|---|
| 348 | QLineF next = QLineF(QPointF(), points[i + 1] - points[i]); | 
|---|
| 349 | QPointF next_normal = next.normalVector().unitVector().p2(); | 
|---|
| 350 |  | 
|---|
| 351 | QPointF normal_sum = prev_normal + next_normal; | 
|---|
| 352 |  | 
|---|
| 353 | qreal r = 1.0 + prev_normal.x() * next_normal.x() | 
|---|
| 354 | + prev_normal.y() * next_normal.y(); | 
|---|
| 355 |  | 
|---|
| 356 | if (qFuzzyIsNull(r)) { | 
|---|
| 357 | points_shifted[i] = points[i] + offset * prev_normal; | 
|---|
| 358 | } else { | 
|---|
| 359 | qreal k = offset / r; | 
|---|
| 360 | points_shifted[i] = points[i] + k * normal_sum; | 
|---|
| 361 | } | 
|---|
| 362 |  | 
|---|
| 363 | prev_normal = next_normal; | 
|---|
| 364 | } | 
|---|
| 365 |  | 
|---|
| 366 | points_shifted[np - 1] = points[np - 1] + offset * prev_normal; | 
|---|
| 367 |  | 
|---|
| 368 | *shifted = QBezier::fromPoints(points_shifted[map[0]], points_shifted[map[1]], | 
|---|
| 369 | points_shifted[map[2]], points_shifted[map[3]]); | 
|---|
| 370 |  | 
|---|
| 371 | return good_offset(orig, shifted, offset, threshold); | 
|---|
| 372 | } | 
|---|
| 373 |  | 
|---|
| 374 | // This value is used to determine the length of control point vectors | 
|---|
| 375 | // when approximating arc segments as curves. The factor is multiplied | 
|---|
| 376 | // with the radius of the circle. | 
|---|
| 377 | #define KAPPA 0.5522847498 | 
|---|
| 378 |  | 
|---|
| 379 |  | 
|---|
| 380 | static bool addCircle(const QBezier *b, qreal offset, QBezier *o) | 
|---|
| 381 | { | 
|---|
| 382 | QPointF normals[3]; | 
|---|
| 383 |  | 
|---|
| 384 | normals[0] = QPointF(b->y2 - b->y1, b->x1 - b->x2); | 
|---|
| 385 | qreal dist = qSqrt(normals[0].x()*normals[0].x() + normals[0].y()*normals[0].y()); | 
|---|
| 386 | if (qFuzzyIsNull(dist)) | 
|---|
| 387 | return false; | 
|---|
| 388 | normals[0] /= dist; | 
|---|
| 389 | normals[2] = QPointF(b->y4 - b->y3, b->x3 - b->x4); | 
|---|
| 390 | dist = qSqrt(normals[2].x()*normals[2].x() + normals[2].y()*normals[2].y()); | 
|---|
| 391 | if (qFuzzyIsNull(dist)) | 
|---|
| 392 | return false; | 
|---|
| 393 | normals[2] /= dist; | 
|---|
| 394 |  | 
|---|
| 395 | normals[1] = QPointF(b->x1 - b->x2 - b->x3 + b->x4, b->y1 - b->y2 - b->y3 + b->y4); | 
|---|
| 396 | normals[1] /= -1*qSqrt(normals[1].x()*normals[1].x() + normals[1].y()*normals[1].y()); | 
|---|
| 397 |  | 
|---|
| 398 | qreal angles[2]; | 
|---|
| 399 | qreal sign = 1.; | 
|---|
| 400 | for (int i = 0; i < 2; ++i) { | 
|---|
| 401 | qreal cos_a = normals[i].x()*normals[i+1].x() + normals[i].y()*normals[i+1].y(); | 
|---|
| 402 | if (cos_a > 1.) | 
|---|
| 403 | cos_a = 1.; | 
|---|
| 404 | if (cos_a < -1.) | 
|---|
| 405 | cos_a = -1; | 
|---|
| 406 | angles[i] = qAcos(cos_a)/Q_PI; | 
|---|
| 407 | } | 
|---|
| 408 |  | 
|---|
| 409 | if (angles[0] + angles[1] > 1.) { | 
|---|
| 410 | // more than 180 degrees | 
|---|
| 411 | normals[1] = -normals[1]; | 
|---|
| 412 | angles[0] = 1. - angles[0]; | 
|---|
| 413 | angles[1] = 1. - angles[1]; | 
|---|
| 414 | sign = -1.; | 
|---|
| 415 |  | 
|---|
| 416 | } | 
|---|
| 417 |  | 
|---|
| 418 | QPointF circle[3]; | 
|---|
| 419 | circle[0] = QPointF(b->x1, b->y1) + normals[0]*offset; | 
|---|
| 420 | circle[1] = QPointF(0.5*(b->x1 + b->x4), 0.5*(b->y1 + b->y4)) + normals[1]*offset; | 
|---|
| 421 | circle[2] = QPointF(b->x4, b->y4) + normals[2]*offset; | 
|---|
| 422 |  | 
|---|
| 423 | for (int i = 0; i < 2; ++i) { | 
|---|
| 424 | qreal kappa = 2.*KAPPA * sign * offset * angles[i]; | 
|---|
| 425 |  | 
|---|
| 426 | o->x1 = circle[i].x(); | 
|---|
| 427 | o->y1 = circle[i].y(); | 
|---|
| 428 | o->x2 = circle[i].x() - normals[i].y()*kappa; | 
|---|
| 429 | o->y2 = circle[i].y() + normals[i].x()*kappa; | 
|---|
| 430 | o->x3 = circle[i+1].x() + normals[i+1].y()*kappa; | 
|---|
| 431 | o->y3 = circle[i+1].y() - normals[i+1].x()*kappa; | 
|---|
| 432 | o->x4 = circle[i+1].x(); | 
|---|
| 433 | o->y4 = circle[i+1].y(); | 
|---|
| 434 |  | 
|---|
| 435 | ++o; | 
|---|
| 436 | } | 
|---|
| 437 | return true; | 
|---|
| 438 | } | 
|---|
| 439 |  | 
|---|
| 440 | int QBezier::shifted(QBezier *curveSegments, int maxSegments, qreal offset, float threshold) const | 
|---|
| 441 | { | 
|---|
| 442 | Q_ASSERT(curveSegments); | 
|---|
| 443 | Q_ASSERT(maxSegments > 0); | 
|---|
| 444 |  | 
|---|
| 445 | if (x1 == x2 && x1 == x3 && x1 == x4 && | 
|---|
| 446 | y1 == y2 && y1 == y3 && y1 == y4) | 
|---|
| 447 | return 0; | 
|---|
| 448 |  | 
|---|
| 449 | --maxSegments; | 
|---|
| 450 | QBezier beziers[10]; | 
|---|
| 451 | redo: | 
|---|
| 452 | beziers[0] = *this; | 
|---|
| 453 | QBezier *b = beziers; | 
|---|
| 454 | QBezier *o = curveSegments; | 
|---|
| 455 |  | 
|---|
| 456 | while (b >= beziers) { | 
|---|
| 457 | int stack_segments = b - beziers + 1; | 
|---|
| 458 | if ((stack_segments == 10) || (o - curveSegments == maxSegments - stack_segments)) { | 
|---|
| 459 | threshold *= 1.5; | 
|---|
| 460 | if (threshold > 2.) | 
|---|
| 461 | goto give_up; | 
|---|
| 462 | goto redo; | 
|---|
| 463 | } | 
|---|
| 464 | ShiftResult res = shift(b, o, offset, threshold); | 
|---|
| 465 | if (res == Discard) { | 
|---|
| 466 | --b; | 
|---|
| 467 | } else if (res == Ok) { | 
|---|
| 468 | ++o; | 
|---|
| 469 | --b; | 
|---|
| 470 | continue; | 
|---|
| 471 | } else if (res == Circle && maxSegments - (o - curveSegments) >= 2) { | 
|---|
| 472 | // add semi circle | 
|---|
| 473 | if (addCircle(b, offset, o)) | 
|---|
| 474 | o += 2; | 
|---|
| 475 | --b; | 
|---|
| 476 | } else { | 
|---|
| 477 | b->split(b+1, b); | 
|---|
| 478 | ++b; | 
|---|
| 479 | } | 
|---|
| 480 | } | 
|---|
| 481 |  | 
|---|
| 482 | give_up: | 
|---|
| 483 | while (b >= beziers) { | 
|---|
| 484 | ShiftResult res = shift(b, o, offset, threshold); | 
|---|
| 485 |  | 
|---|
| 486 | // if res isn't Ok or Split then *o is undefined | 
|---|
| 487 | if (res == Ok || res == Split) | 
|---|
| 488 | ++o; | 
|---|
| 489 |  | 
|---|
| 490 | --b; | 
|---|
| 491 | } | 
|---|
| 492 |  | 
|---|
| 493 | Q_ASSERT(o - curveSegments <= maxSegments); | 
|---|
| 494 | return o - curveSegments; | 
|---|
| 495 | } | 
|---|
| 496 |  | 
|---|
| 497 | #ifdef QDEBUG_BEZIER | 
|---|
| 498 | static QDebug operator<<(QDebug dbg, const QBezier &bz) | 
|---|
| 499 | { | 
|---|
| 500 | dbg << '[' << bz.x1<< ", " << bz.y1 << "], " | 
|---|
| 501 | << '[' << bz.x2 <<", " << bz.y2 << "], " | 
|---|
| 502 | << '[' << bz.x3 <<", " << bz.y3 << "], " | 
|---|
| 503 | << '[' << bz.x4 <<", " << bz.y4 << ']'; | 
|---|
| 504 | return dbg; | 
|---|
| 505 | } | 
|---|
| 506 | #endif | 
|---|
| 507 |  | 
|---|
| 508 | static inline void splitBezierAt(const QBezier &bez, qreal t, | 
|---|
| 509 | QBezier *left, QBezier *right) | 
|---|
| 510 | { | 
|---|
| 511 | left->x1 = bez.x1; | 
|---|
| 512 | left->y1 = bez.y1; | 
|---|
| 513 |  | 
|---|
| 514 | left->x2 = bez.x1 + t * ( bez.x2 - bez.x1 ); | 
|---|
| 515 | left->y2 = bez.y1 + t * ( bez.y2 - bez.y1 ); | 
|---|
| 516 |  | 
|---|
| 517 | left->x3 = bez.x2 + t * ( bez.x3 - bez.x2 ); // temporary holding spot | 
|---|
| 518 | left->y3 = bez.y2 + t * ( bez.y3 - bez.y2 ); // temporary holding spot | 
|---|
| 519 |  | 
|---|
| 520 | right->x3 = bez.x3 + t * ( bez.x4 - bez.x3 ); | 
|---|
| 521 | right->y3 = bez.y3 + t * ( bez.y4 - bez.y3 ); | 
|---|
| 522 |  | 
|---|
| 523 | right->x2 = left->x3 + t * ( right->x3 - left->x3); | 
|---|
| 524 | right->y2 = left->y3 + t * ( right->y3 - left->y3); | 
|---|
| 525 |  | 
|---|
| 526 | left->x3 = left->x2 + t * ( left->x3 - left->x2 ); | 
|---|
| 527 | left->y3 = left->y2 + t * ( left->y3 - left->y2 ); | 
|---|
| 528 |  | 
|---|
| 529 | left->x4 = right->x1 = left->x3 + t * (right->x2 - left->x3); | 
|---|
| 530 | left->y4 = right->y1 = left->y3 + t * (right->y2 - left->y3); | 
|---|
| 531 |  | 
|---|
| 532 | right->x4 = bez.x4; | 
|---|
| 533 | right->y4 = bez.y4; | 
|---|
| 534 | } | 
|---|
| 535 |  | 
|---|
| 536 | qreal QBezier::length(qreal error) const | 
|---|
| 537 | { | 
|---|
| 538 | qreal length = 0.0; | 
|---|
| 539 |  | 
|---|
| 540 | addIfClose(&length, error); | 
|---|
| 541 |  | 
|---|
| 542 | return length; | 
|---|
| 543 | } | 
|---|
| 544 |  | 
|---|
| 545 | void QBezier::addIfClose(qreal *length, qreal error) const | 
|---|
| 546 | { | 
|---|
| 547 | QBezier left, right;     /* bez poly splits */ | 
|---|
| 548 |  | 
|---|
| 549 | qreal len = 0.0;        /* arc length */ | 
|---|
| 550 | qreal chord;            /* chord length */ | 
|---|
| 551 |  | 
|---|
| 552 | len = len + QLineF(QPointF(x1, y1),QPointF(x2, y2)).length(); | 
|---|
| 553 | len = len + QLineF(QPointF(x2, y2),QPointF(x3, y3)).length(); | 
|---|
| 554 | len = len + QLineF(QPointF(x3, y3),QPointF(x4, y4)).length(); | 
|---|
| 555 |  | 
|---|
| 556 | chord = QLineF(QPointF(x1, y1),QPointF(x4, y4)).length(); | 
|---|
| 557 |  | 
|---|
| 558 | if((len-chord) > error) { | 
|---|
| 559 | split(&left, &right);                 /* split in two */ | 
|---|
| 560 | left.addIfClose(length, error);       /* try left side */ | 
|---|
| 561 | right.addIfClose(length, error);      /* try right side */ | 
|---|
| 562 | return; | 
|---|
| 563 | } | 
|---|
| 564 |  | 
|---|
| 565 | *length = *length + len; | 
|---|
| 566 |  | 
|---|
| 567 | return; | 
|---|
| 568 | } | 
|---|
| 569 |  | 
|---|
| 570 | qreal QBezier::tForY(qreal t0, qreal t1, qreal y) const | 
|---|
| 571 | { | 
|---|
| 572 | qreal py0 = pointAt(t0).y(); | 
|---|
| 573 | qreal py1 = pointAt(t1).y(); | 
|---|
| 574 |  | 
|---|
| 575 | if (py0 > py1) { | 
|---|
| 576 | qSwap(py0, py1); | 
|---|
| 577 | qSwap(t0, t1); | 
|---|
| 578 | } | 
|---|
| 579 |  | 
|---|
| 580 | Q_ASSERT(py0 <= py1); | 
|---|
| 581 |  | 
|---|
| 582 | if (py0 >= y) | 
|---|
| 583 | return t0; | 
|---|
| 584 | else if (py1 <= y) | 
|---|
| 585 | return t1; | 
|---|
| 586 |  | 
|---|
| 587 | Q_ASSERT(py0 < y && y < py1); | 
|---|
| 588 |  | 
|---|
| 589 | qreal lt = t0; | 
|---|
| 590 | qreal dt; | 
|---|
| 591 | do { | 
|---|
| 592 | qreal t = 0.5 * (t0 + t1); | 
|---|
| 593 |  | 
|---|
| 594 | qreal a, b, c, d; | 
|---|
| 595 | QBezier::coefficients(t, a, b, c, d); | 
|---|
| 596 | qreal yt = a * y1 + b * y2 + c * y3 + d * y4; | 
|---|
| 597 |  | 
|---|
| 598 | if (yt < y) { | 
|---|
| 599 | t0 = t; | 
|---|
| 600 | py0 = yt; | 
|---|
| 601 | } else { | 
|---|
| 602 | t1 = t; | 
|---|
| 603 | py1 = yt; | 
|---|
| 604 | } | 
|---|
| 605 | dt = lt - t; | 
|---|
| 606 | lt = t; | 
|---|
| 607 | } while (qAbs(dt) > 1e-7); | 
|---|
| 608 |  | 
|---|
| 609 | return t0; | 
|---|
| 610 | } | 
|---|
| 611 |  | 
|---|
| 612 | int QBezier::stationaryYPoints(qreal &t0, qreal &t1) const | 
|---|
| 613 | { | 
|---|
| 614 | // y(t) = (1 - t)^3 * y1 + 3 * (1 - t)^2 * t * y2 + 3 * (1 - t) * t^2 * y3 + t^3 * y4 | 
|---|
| 615 | // y'(t) = 3 * (-(1-2t+t^2) * y1 + (1 - 4 * t + 3 * t^2) * y2 + (2 * t - 3 * t^2) * y3 + t^2 * y4) | 
|---|
| 616 | // y'(t) = 3 * ((-y1 + 3 * y2 - 3 * y3 + y4)t^2 + (2 * y1 - 4 * y2 + 2 * y3)t + (-y1 + y2)) | 
|---|
| 617 |  | 
|---|
| 618 | const qreal a = -y1 + 3 * y2 - 3 * y3 + y4; | 
|---|
| 619 | const qreal b = 2 * y1 - 4 * y2 + 2 * y3; | 
|---|
| 620 | const qreal c = -y1 + y2; | 
|---|
| 621 |  | 
|---|
| 622 | if (qFuzzyIsNull(a)) { | 
|---|
| 623 | if (qFuzzyIsNull(b)) | 
|---|
| 624 | return 0; | 
|---|
| 625 |  | 
|---|
| 626 | t0 = -c / b; | 
|---|
| 627 | return t0 > 0 && t0 < 1; | 
|---|
| 628 | } | 
|---|
| 629 |  | 
|---|
| 630 | qreal reciprocal = b * b - 4 * a * c; | 
|---|
| 631 |  | 
|---|
| 632 | if (qFuzzyIsNull(reciprocal)) { | 
|---|
| 633 | t0 = -b / (2 * a); | 
|---|
| 634 | return t0 > 0 && t0 < 1; | 
|---|
| 635 | } else if (reciprocal > 0) { | 
|---|
| 636 | qreal temp = qSqrt(reciprocal); | 
|---|
| 637 |  | 
|---|
| 638 | t0 = (-b - temp)/(2*a); | 
|---|
| 639 | t1 = (-b + temp)/(2*a); | 
|---|
| 640 |  | 
|---|
| 641 | if (t1 < t0) | 
|---|
| 642 | qSwap(t0, t1); | 
|---|
| 643 |  | 
|---|
| 644 | int count = 0; | 
|---|
| 645 | qreal t[2] = { 0, 1 }; | 
|---|
| 646 |  | 
|---|
| 647 | if (t0 > 0 && t0 < 1) | 
|---|
| 648 | t[count++] = t0; | 
|---|
| 649 | if (t1 > 0 && t1 < 1) | 
|---|
| 650 | t[count++] = t1; | 
|---|
| 651 |  | 
|---|
| 652 | t0 = t[0]; | 
|---|
| 653 | t1 = t[1]; | 
|---|
| 654 |  | 
|---|
| 655 | return count; | 
|---|
| 656 | } | 
|---|
| 657 |  | 
|---|
| 658 | return 0; | 
|---|
| 659 | } | 
|---|
| 660 |  | 
|---|
| 661 | qreal QBezier::tAtLength(qreal l) const | 
|---|
| 662 | { | 
|---|
| 663 | qreal len = length(); | 
|---|
| 664 | qreal t   = 1.0; | 
|---|
| 665 | const qreal error = (qreal)0.01; | 
|---|
| 666 | if (l > len || qFuzzyCompare(l, len)) | 
|---|
| 667 | return t; | 
|---|
| 668 |  | 
|---|
| 669 | t *= 0.5; | 
|---|
| 670 | //int iters = 0; | 
|---|
| 671 | //qDebug()<<"LEN is "<<l<<len; | 
|---|
| 672 | qreal lastBigger = 1.; | 
|---|
| 673 | while (1) { | 
|---|
| 674 | //qDebug()<<"\tt is "<<t; | 
|---|
| 675 | QBezier right = *this; | 
|---|
| 676 | QBezier left; | 
|---|
| 677 | right.parameterSplitLeft(t, &left); | 
|---|
| 678 | qreal lLen = left.length(); | 
|---|
| 679 | if (qAbs(lLen - l) < error) | 
|---|
| 680 | break; | 
|---|
| 681 |  | 
|---|
| 682 | if (lLen < l) { | 
|---|
| 683 | t += (lastBigger - t)*.5; | 
|---|
| 684 | } else { | 
|---|
| 685 | lastBigger = t; | 
|---|
| 686 | t -= t*.5; | 
|---|
| 687 | } | 
|---|
| 688 | //++iters; | 
|---|
| 689 | } | 
|---|
| 690 | //qDebug()<<"number of iters is "<<iters; | 
|---|
| 691 | return t; | 
|---|
| 692 | } | 
|---|
| 693 |  | 
|---|
| 694 | QBezier QBezier::bezierOnInterval(qreal t0, qreal t1) const | 
|---|
| 695 | { | 
|---|
| 696 | if (t0 == 0 && t1 == 1) | 
|---|
| 697 | return *this; | 
|---|
| 698 |  | 
|---|
| 699 | QBezier bezier = *this; | 
|---|
| 700 |  | 
|---|
| 701 | QBezier result; | 
|---|
| 702 | bezier.parameterSplitLeft(t0, &result); | 
|---|
| 703 | qreal trueT = (t1-t0)/(1-t0); | 
|---|
| 704 | bezier.parameterSplitLeft(trueT, &result); | 
|---|
| 705 |  | 
|---|
| 706 | return result; | 
|---|
| 707 | } | 
|---|
| 708 |  | 
|---|
| 709 | QT_END_NAMESPACE | 
|---|