LibreOffice Module basegfx (master) 1
b2dcubicbezier.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
25
26#include <osl/diagnose.h>
27
28#include <limits>
29
30// #i37443#
31#define FACTOR_FOR_UNSHARPEN (1.6)
32#ifdef DBG_UTIL
34#endif
35
36namespace basegfx
37{
38 namespace
39 {
40 void ImpSubDivAngle(
41 const B2DPoint& rfPA, // start point
42 const B2DPoint& rfEA, // edge on A
43 const B2DPoint& rfEB, // edge on B
44 const B2DPoint& rfPB, // end point
45 B2DPolygon& rTarget, // target polygon
46 double fAngleBound, // angle bound in [0.0 .. 2PI]
47 bool bAllowUnsharpen, // #i37443# allow the criteria to get unsharp in recursions
48 sal_uInt16 nMaxRecursionDepth) // endless loop protection
49 {
50 if(nMaxRecursionDepth)
51 {
52 // do angle test
53 B2DVector aLeft(rfEA - rfPA);
54 B2DVector aRight(rfEB - rfPB);
55
56 // #i72104#
57 if(aLeft.equalZero())
58 {
59 aLeft = rfEB - rfPA;
60 }
61
62 if(aRight.equalZero())
63 {
64 aRight = rfEA - rfPB;
65 }
66
67 const double fCurrentAngle(aLeft.angle(aRight));
68
69 if(fabs(fCurrentAngle) > (M_PI - fAngleBound))
70 {
71 // end recursion
72 nMaxRecursionDepth = 0;
73 }
74 else
75 {
76 if(bAllowUnsharpen)
77 {
78 // #i37443# unsharpen criteria
79#ifdef DBG_UTIL
80 fAngleBound *= fMultFactUnsharpen;
81#else
82 fAngleBound *= FACTOR_FOR_UNSHARPEN;
83#endif
84 }
85 }
86 }
87
88 if(nMaxRecursionDepth)
89 {
90 // divide at 0.5
91 const B2DPoint aS1L(average(rfPA, rfEA));
92 const B2DPoint aS1C(average(rfEA, rfEB));
93 const B2DPoint aS1R(average(rfEB, rfPB));
94 const B2DPoint aS2L(average(aS1L, aS1C));
95 const B2DPoint aS2R(average(aS1C, aS1R));
96 const B2DPoint aS3C(average(aS2L, aS2R));
97
98 // left recursion
99 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
100
101 // right recursion
102 ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
103 }
104 else
105 {
106 rTarget.append(rfPB);
107 }
108 }
109
110 void ImpSubDivAngleStart(
111 const B2DPoint& rfPA, // start point
112 const B2DPoint& rfEA, // edge on A
113 const B2DPoint& rfEB, // edge on B
114 const B2DPoint& rfPB, // end point
115 B2DPolygon& rTarget, // target polygon
116 const double& rfAngleBound) // angle bound in [0.0 .. 2PI]
117 {
118 sal_uInt16 nMaxRecursionDepth(8);
119 const B2DVector aLeft(rfEA - rfPA);
120 const B2DVector aRight(rfEB - rfPB);
121 bool bLeftEqualZero(aLeft.equalZero());
122 bool bRightEqualZero(aRight.equalZero());
123 bool bAllParallel(false);
124
125 if(bLeftEqualZero && bRightEqualZero)
126 {
127 nMaxRecursionDepth = 0;
128 }
129 else
130 {
131 const B2DVector aBase(rfPB - rfPA);
132 const bool bBaseEqualZero(aBase.equalZero()); // #i72104#
133
134 if(!bBaseEqualZero)
135 {
136 const bool bLeftParallel(bLeftEqualZero || areParallel(aLeft, aBase));
137 const bool bRightParallel(bRightEqualZero || areParallel(aRight, aBase));
138
139 if(bLeftParallel && bRightParallel)
140 {
141 bAllParallel = true;
142
143 if(!bLeftEqualZero)
144 {
145 double fFactor;
146
147 if(fabs(aBase.getX()) > fabs(aBase.getY()))
148 {
149 fFactor = aLeft.getX() / aBase.getX();
150 }
151 else
152 {
153 fFactor = aLeft.getY() / aBase.getY();
154 }
155
156 if(fFactor >= 0.0 && fFactor <= 1.0)
157 {
158 bLeftEqualZero = true;
159 }
160 }
161
162 if(!bRightEqualZero)
163 {
164 double fFactor;
165
166 if(fabs(aBase.getX()) > fabs(aBase.getY()))
167 {
168 fFactor = aRight.getX() / -aBase.getX();
169 }
170 else
171 {
172 fFactor = aRight.getY() / -aBase.getY();
173 }
174
175 if(fFactor >= 0.0 && fFactor <= 1.0)
176 {
177 bRightEqualZero = true;
178 }
179 }
180
181 if(bLeftEqualZero && bRightEqualZero)
182 {
183 nMaxRecursionDepth = 0;
184 }
185 }
186 }
187 }
188
189 if(nMaxRecursionDepth)
190 {
191 // divide at 0.5 ad test both edges for angle criteria
192 const B2DPoint aS1L(average(rfPA, rfEA));
193 const B2DPoint aS1C(average(rfEA, rfEB));
194 const B2DPoint aS1R(average(rfEB, rfPB));
195 const B2DPoint aS2L(average(aS1L, aS1C));
196 const B2DPoint aS2R(average(aS1C, aS1R));
197 const B2DPoint aS3C(average(aS2L, aS2R));
198
199 // test left
200 bool bAngleIsSmallerLeft(bAllParallel && bLeftEqualZero);
201 if(!bAngleIsSmallerLeft)
202 {
203 const B2DVector aLeftLeft(bLeftEqualZero ? aS2L - aS1L : aS1L - rfPA); // #i72104#
204 const B2DVector aRightLeft(aS2L - aS3C);
205 const double fCurrentAngleLeft(aLeftLeft.angle(aRightLeft));
206 bAngleIsSmallerLeft = (fabs(fCurrentAngleLeft) > (M_PI - rfAngleBound));
207 }
208
209 // test right
210 bool bAngleIsSmallerRight(bAllParallel && bRightEqualZero);
211 if(!bAngleIsSmallerRight)
212 {
213 const B2DVector aLeftRight(aS2R - aS3C);
214 const B2DVector aRightRight(bRightEqualZero ? aS2R - aS1R : aS1R - rfPB); // #i72104#
215 const double fCurrentAngleRight(aLeftRight.angle(aRightRight));
216 bAngleIsSmallerRight = (fabs(fCurrentAngleRight) > (M_PI - rfAngleBound));
217 }
218
219 if(bAngleIsSmallerLeft && bAngleIsSmallerRight)
220 {
221 // no recursion necessary at all
222 nMaxRecursionDepth = 0;
223 }
224 else
225 {
226 // left
227 if(bAngleIsSmallerLeft)
228 {
229 rTarget.append(aS3C);
230 }
231 else
232 {
233 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
234 }
235
236 // right
237 if(bAngleIsSmallerRight)
238 {
239 rTarget.append(rfPB);
240 }
241 else
242 {
243 ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
244 }
245 }
246 }
247
248 if(!nMaxRecursionDepth)
249 {
250 rTarget.append(rfPB);
251 }
252 }
253
254 void ImpSubDivDistance(
255 const B2DPoint& rfPA, // start point
256 const B2DPoint& rfEA, // edge on A
257 const B2DPoint& rfEB, // edge on B
258 const B2DPoint& rfPB, // end point
259 B2DPolygon& rTarget, // target polygon
260 double fDistanceBound2, // quadratic distance criteria
261 double fLastDistanceError2, // the last quadratic distance error
262 sal_uInt16 nMaxRecursionDepth) // endless loop protection
263 {
264 if(nMaxRecursionDepth)
265 {
266 // decide if another recursion is needed. If not, set
267 // nMaxRecursionDepth to zero
268
269 // Perform bezier flatness test (lecture notes from R. Schaback,
270 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
271
272 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
273 // 0<=j<=n
274
275 // What is calculated here is an upper bound to the distance from
276 // a line through b_0 and b_3 (rfPA and P4 in our notation) and the
277 // curve. We can drop 0 and n from the running indices, since the
278 // argument of max becomes zero for those cases.
279 const double fJ1x(rfEA.getX() - rfPA.getX() - 1.0/3.0*(rfPB.getX() - rfPA.getX()));
280 const double fJ1y(rfEA.getY() - rfPA.getY() - 1.0/3.0*(rfPB.getY() - rfPA.getY()));
281 const double fJ2x(rfEB.getX() - rfPA.getX() - 2.0/3.0*(rfPB.getX() - rfPA.getX()));
282 const double fJ2y(rfEB.getY() - rfPA.getY() - 2.0/3.0*(rfPB.getY() - rfPA.getY()));
283 const double fDistanceError2(std::max(fJ1x*fJ1x + fJ1y*fJ1y, fJ2x*fJ2x + fJ2y*fJ2y));
284
285 // stop if error measure does not improve anymore. This is a
286 // safety guard against floating point inaccuracies.
287 // stop if distance from line is guaranteed to be bounded by d
288 const bool bFurtherDivision(fLastDistanceError2 > fDistanceError2 && fDistanceError2 >= fDistanceBound2);
289
290 if(bFurtherDivision)
291 {
292 // remember last error value
293 fLastDistanceError2 = fDistanceError2;
294 }
295 else
296 {
297 // stop recursion
298 nMaxRecursionDepth = 0;
299 }
300 }
301
302 if(nMaxRecursionDepth)
303 {
304 // divide at 0.5
305 const B2DPoint aS1L(average(rfPA, rfEA));
306 const B2DPoint aS1C(average(rfEA, rfEB));
307 const B2DPoint aS1R(average(rfEB, rfPB));
308 const B2DPoint aS2L(average(aS1L, aS1C));
309 const B2DPoint aS2R(average(aS1C, aS1R));
310 const B2DPoint aS3C(average(aS2L, aS2R));
311
312 // left recursion
313 ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
314
315 // right recursion
316 ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
317 }
318 else
319 {
320 rTarget.append(rfPB);
321 }
322 }
323 } // end of anonymous namespace
324} // end of namespace basegfx
325
326namespace basegfx
327{
328 B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier&) = default;
329
331
332 B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd)
333 : maStartPoint(rStart),
334 maEndPoint(rEnd),
335 maControlPointA(rControlPointA),
336 maControlPointB(rControlPointB)
337 {
338 }
339
340 // assignment operator
342
343 // compare operators
345 {
346 return (
347 maStartPoint == rBezier.maStartPoint
348 && maEndPoint == rBezier.maEndPoint
349 && maControlPointA == rBezier.maControlPointA
350 && maControlPointB == rBezier.maControlPointB
351 );
352 }
353
355 {
356 return (
357 maStartPoint != rBezier.maStartPoint
358 || maEndPoint != rBezier.maEndPoint
359 || maControlPointA != rBezier.maControlPointA
360 || maControlPointB != rBezier.maControlPointB
361 );
362 }
363
364 bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const
365 {
366 return (
368 && maEndPoint.equal(rBezier.maEndPoint)
371 );
372 }
373
374 // test if vectors are used
376 {
378 }
379
381 {
383 return;
384
385 const B2DVector aEdge(maEndPoint - maStartPoint);
386
387 // controls parallel to edge can be trivial. No edge -> not parallel -> control can
388 // still not be trivial (e.g. ballon loop)
389 if(aEdge.equalZero())
390 return;
391
392 // get control vectors
394 const B2DVector aVecB(maControlPointB - maEndPoint);
395
396 // check if trivial per se
397 bool bAIsTrivial(aVecA.equalZero());
398 bool bBIsTrivial(aVecB.equalZero());
399
400 // #i102241# prepare inverse edge length to normalize cross values;
401 // else the small compare value used in fTools::equalZero
402 // will be length dependent and this detection will work as less
403 // precise as longer the edge is. In principle, the length of the control
404 // vector would need to be used too, but to be trivial it is assumed to
405 // be of roughly equal length to the edge, so edge length can be used
406 // for both. Only needed when one of both is not trivial per se.
407 const double fInverseEdgeLength(bAIsTrivial && bBIsTrivial
408 ? 1.0
409 : 1.0 / aEdge.getLength());
410
411 // if A is not zero, check if it could be
412 if(!bAIsTrivial)
413 {
414 // #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what
415 // we need here with the precision we need
416 const double fCross(aVecA.cross(aEdge) * fInverseEdgeLength);
417
418 if(fTools::equalZero(fCross))
419 {
420 // get scale to edge. Use bigger distance for numeric quality
421 const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
422 ? aVecA.getX() / aEdge.getX()
423 : aVecA.getY() / aEdge.getY());
424
425 // relative end point of vector in edge range?
426 if (fTools::betweenOrEqualEither(fScale, 0.0, 1.0))
427 {
428 bAIsTrivial = true;
429 }
430 }
431 }
432
433 // if B is not zero, check if it could be, but only if A is already trivial;
434 // else solve to trivial will not be possible for whole edge
435 if(bAIsTrivial && !bBIsTrivial)
436 {
437 // parallel to edge? Check aVecB, aEdge
438 const double fCross(aVecB.cross(aEdge) * fInverseEdgeLength);
439
440 if(fTools::equalZero(fCross))
441 {
442 // get scale to edge. Use bigger distance for numeric quality
443 const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
444 ? aVecB.getX() / aEdge.getX()
445 : aVecB.getY() / aEdge.getY());
446
447 // end point of vector in edge range? Caution: controlB is directed AGAINST edge
448 if (fTools::betweenOrEqualEither(fScale, -1.0, 0.0))
449 {
450 bBIsTrivial = true;
451 }
452 }
453 }
454
455 // if both are/can be reduced, do it.
456 // Not possible if only one is/can be reduced (!)
457 if(bAIsTrivial && bBIsTrivial)
458 {
461 }
462 }
463
464 namespace {
465 double impGetLength(const B2DCubicBezier& rEdge, double fDeviation, sal_uInt32 nRecursionWatch)
466 {
467 const double fEdgeLength(rEdge.getEdgeLength());
468 const double fControlPolygonLength(rEdge.getControlPolygonLength());
469 const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength) ? 0.0 : 1.0 - (fEdgeLength / fControlPolygonLength));
470
471 if(!nRecursionWatch || fTools:: lessOrEqual(fCurrentDeviation, fDeviation))
472 {
473 return (fEdgeLength + fControlPolygonLength) * 0.5;
474 }
475 else
476 {
477 B2DCubicBezier aLeft, aRight;
478 const double fNewDeviation(fDeviation * 0.5);
479 const sal_uInt32 nNewRecursionWatch(nRecursionWatch - 1);
480
481 rEdge.split(0.5, &aLeft, &aRight);
482
483 return impGetLength(aLeft, fNewDeviation, nNewRecursionWatch)
484 + impGetLength(aRight, fNewDeviation, nNewRecursionWatch);
485 }
486 }
487 }
488
489 double B2DCubicBezier::getLength(double fDeviation) const
490 {
491 if(isBezier())
492 {
493 if(fDeviation < 0.00000001)
494 {
495 fDeviation = 0.00000001;
496 }
497
498 return impGetLength(*this, fDeviation, 6);
499 }
500 else
501 {
503 }
504 }
505
507 {
508 const B2DVector aEdge(maEndPoint - maStartPoint);
509 return aEdge.getLength();
510 }
511
513 {
514 const B2DVector aVectorA(maControlPointA - maStartPoint);
515 const B2DVector aVectorB(maEndPoint - maControlPointB);
516
517 if(!aVectorA.equalZero() || !aVectorB.equalZero())
518 {
520 return (aVectorA.getLength() + aVectorB.getLength() + aTop.getLength());
521 }
522 else
523 {
524 return getEdgeLength();
525 }
526 }
527
528 void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound) const
529 {
530 if(isBezier())
531 {
532 // use support method #i37443# and allow unsharpen the criteria
534 deg2rad(fAngleBound));
535 }
536 else
537 {
538 rTarget.append(getEndPoint());
539 }
540 }
541
543 {
544 if(fTools::lessOrEqual(t, 0.0))
545 {
546 // tangent in start point
548
549 if(!aTangent.equalZero())
550 {
551 return aTangent;
552 }
553
554 // start point and control vector are the same, fallback
555 // to implicit start vector to control point B
556 aTangent = (getControlPointB() - getStartPoint()) * 0.3;
557
558 if(!aTangent.equalZero())
559 {
560 return aTangent;
561 }
562
563 // not a bezier at all, return edge vector
564 return (getEndPoint() - getStartPoint()) * 0.3;
565 }
566 else if(fTools::moreOrEqual(t, 1.0))
567 {
568 // tangent in end point
569 B2DVector aTangent(getEndPoint() - getControlPointB());
570
571 if(!aTangent.equalZero())
572 {
573 return aTangent;
574 }
575
576 // end point and control vector are the same, fallback
577 // to implicit start vector from control point A
578 aTangent = (getEndPoint() - getControlPointA()) * 0.3;
579
580 if(!aTangent.equalZero())
581 {
582 return aTangent;
583 }
584
585 // not a bezier at all, return edge vector
586 return (getEndPoint() - getStartPoint()) * 0.3;
587 }
588 else
589 {
590 // t is in ]0.0 .. 1.0[. Split and extract
591 B2DCubicBezier aRight;
592 split(t, nullptr, &aRight);
593
594 return aRight.getControlPointA() - aRight.getStartPoint();
595 }
596 }
597
598 // #i37443# adaptive subdivide by nCount subdivisions
599 void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const
600 {
601 const double fLenFact(1.0 / static_cast< double >(nCount + 1));
602
603 for(sal_uInt32 a(1); a <= nCount; a++)
604 {
605 const double fPos(static_cast< double >(a) * fLenFact);
606 rTarget.append(interpolatePoint(fPos));
607 }
608
609 rTarget.append(getEndPoint());
610 }
611
612 // adaptive subdivide by distance
613 void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound, int nRecurseLimit) const
614 {
615 if(isBezier())
616 {
618 fDistanceBound * fDistanceBound, std::numeric_limits<double>::max(), nRecurseLimit);
619 }
620 else
621 {
622 rTarget.append(getEndPoint());
623 }
624 }
625
627 {
628 OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)");
629
630 if(isBezier())
631 {
635 const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
636 const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
637
638 return interpolate(aS2L, aS2R, t);
639 }
640 else
641 {
643 }
644 }
645
646 double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const
647 {
648 const sal_uInt32 nInitialDivisions(3);
649 B2DPolygon aInitialPolygon;
650
651 // as start make a fix division, creates nInitialDivisions + 2 points
652 aInitialPolygon.append(getStartPoint());
653 adaptiveSubdivideByCount(aInitialPolygon, nInitialDivisions);
654
655 // now look for the closest point
656 const sal_uInt32 nPointCount(aInitialPolygon.count());
657 B2DVector aVector(rTestPoint - aInitialPolygon.getB2DPoint(0));
658 double fQuadDist(aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY());
659 double fNewQuadDist;
660 sal_uInt32 nSmallestIndex(0);
661
662 for(sal_uInt32 a(1); a < nPointCount; a++)
663 {
664 aVector = B2DVector(rTestPoint - aInitialPolygon.getB2DPoint(a));
665 fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
666
667 if(fNewQuadDist < fQuadDist)
668 {
669 fQuadDist = fNewQuadDist;
670 nSmallestIndex = a;
671 }
672 }
673
674 // look right and left for even smaller distances
675 double fStepValue(1.0 / static_cast<double>((nPointCount - 1) * 2)); // half the edge step width
676 double fPosition(static_cast<double>(nSmallestIndex) / static_cast<double>(nPointCount - 1));
677
678 while(true)
679 {
680 // test left
681 double fPosLeft(fPosition - fStepValue);
682
683 if(fPosLeft < 0.0)
684 {
685 fPosLeft = 0.0;
686 aVector = B2DVector(rTestPoint - maStartPoint);
687 }
688 else
689 {
690 aVector = B2DVector(rTestPoint - interpolatePoint(fPosLeft));
691 }
692
693 fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
694
695 if(fTools::less(fNewQuadDist, fQuadDist))
696 {
697 fQuadDist = fNewQuadDist;
698 fPosition = fPosLeft;
699 }
700 else
701 {
702 // test right
703 double fPosRight(fPosition + fStepValue);
704
705 if(fPosRight > 1.0)
706 {
707 fPosRight = 1.0;
708 aVector = B2DVector(rTestPoint - maEndPoint);
709 }
710 else
711 {
712 aVector = B2DVector(rTestPoint - interpolatePoint(fPosRight));
713 }
714
715 fNewQuadDist = aVector.getX() * aVector.getX() + aVector.getY() * aVector.getY();
716
717 if(fTools::less(fNewQuadDist, fQuadDist))
718 {
719 fQuadDist = fNewQuadDist;
720 fPosition = fPosRight;
721 }
722 else
723 {
724 // not less left or right, done
725 break;
726 }
727 }
728
729 if(fPosition == 0.0 || fPosition == 1.0)
730 {
731 // if we are completely left or right, we are done
732 break;
733 }
734
735 // prepare next step value
736 fStepValue /= 2.0;
737 }
738
739 rCut = fPosition;
740 return sqrt(fQuadDist);
741 }
742
743 void B2DCubicBezier::split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const
744 {
745 OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::split: Access out of range (!)");
746
747 if(!pBezierA && !pBezierB)
748 {
749 return;
750 }
751
752 if(isBezier())
753 {
757 const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
758 const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
759 const B2DPoint aS3C(interpolate(aS2L, aS2R, t));
760
761 if(pBezierA)
762 {
763 pBezierA->setStartPoint(maStartPoint);
764 pBezierA->setEndPoint(aS3C);
765 pBezierA->setControlPointA(aS1L);
766 pBezierA->setControlPointB(aS2L);
767 }
768
769 if(pBezierB)
770 {
771 pBezierB->setStartPoint(aS3C);
772 pBezierB->setEndPoint(maEndPoint);
773 pBezierB->setControlPointA(aS2R);
774 pBezierB->setControlPointB(aS1R);
775 }
776 }
777 else
778 {
780
781 if(pBezierA)
782 {
783 pBezierA->setStartPoint(maStartPoint);
784 pBezierA->setEndPoint(aSplit);
786 pBezierA->setControlPointB(aSplit);
787 }
788
789 if(pBezierB)
790 {
791 pBezierB->setStartPoint(aSplit);
792 pBezierB->setEndPoint(maEndPoint);
793 pBezierB->setControlPointA(aSplit);
794 pBezierB->setControlPointB(maEndPoint);
795 }
796 }
797 }
798
799 B2DCubicBezier B2DCubicBezier::snippet(double fStart, double fEnd) const
800 {
801 B2DCubicBezier aRetval;
802
803 if(fTools::more(fStart, 1.0))
804 {
805 fStart = 1.0;
806 }
807 else if(fTools::less(fStart, 0.0))
808 {
809 fStart = 0.0;
810 }
811
812 if(fTools::more(fEnd, 1.0))
813 {
814 fEnd = 1.0;
815 }
816 else if(fTools::less(fEnd, 0.0))
817 {
818 fEnd = 0.0;
819 }
820
821 if(fEnd <= fStart)
822 {
823 // empty or NULL, create single point at center
824 const double fSplit((fEnd + fStart) * 0.5);
825 const B2DPoint aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit));
826 aRetval.setStartPoint(aPoint);
827 aRetval.setEndPoint(aPoint);
828 aRetval.setControlPointA(aPoint);
829 aRetval.setControlPointB(aPoint);
830 }
831 else
832 {
833 if(isBezier())
834 {
835 // copy bezier; cut off right, then cut off left. Do not forget to
836 // adapt cut value when both cuts happen
837 const bool bEndIsOne(fTools::equal(fEnd, 1.0));
838 const bool bStartIsZero(fTools::equalZero(fStart));
839 aRetval = *this;
840
841 if(!bEndIsOne)
842 {
843 aRetval.split(fEnd, &aRetval, nullptr);
844
845 if(!bStartIsZero)
846 {
847 fStart /= fEnd;
848 }
849 }
850
851 if(!bStartIsZero)
852 {
853 aRetval.split(fStart, nullptr, &aRetval);
854 }
855 }
856 else
857 {
858 // no bezier, create simple edge
859 const B2DPoint aPointA(interpolate(getStartPoint(), getEndPoint(), fStart));
860 const B2DPoint aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd));
861 aRetval.setStartPoint(aPointA);
862 aRetval.setEndPoint(aPointB);
863 aRetval.setControlPointA(aPointA);
864 aRetval.setControlPointB(aPointB);
865 }
866 }
867
868 return aRetval;
869 }
870
872 {
874
875 aRetval.expand(maControlPointA);
876 aRetval.expand(maControlPointB);
877
878 return aRetval;
879 }
880
882 {
883 std::vector< double > aAllResults;
884
885 aAllResults.reserve(4);
886 getAllExtremumPositions(aAllResults);
887
888 const sal_uInt32 nCount(aAllResults.size());
889
890 if(!nCount)
891 {
892 return false;
893 }
894 else if(nCount == 1)
895 {
896 rfResult = aAllResults[0];
897 return true;
898 }
899 else
900 {
901 rfResult = *(std::min_element(aAllResults.begin(), aAllResults.end()));
902 return true;
903 }
904 }
905
906 namespace
907 {
908 void impCheckExtremumResult(double fCandidate, std::vector< double >& rResult)
909 {
910 // check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly
911 // by using the equalZero test, NOT ::more or ::less which will use the
912 // ApproxEqual() which is too exact here
913 if(fCandidate > 0.0 && !fTools::equalZero(fCandidate))
914 {
915 if(fCandidate < 1.0 && !fTools::equalZero(fCandidate - 1.0))
916 {
917 rResult.push_back(fCandidate);
918 }
919 }
920 }
921 }
922
923 void B2DCubicBezier::getAllExtremumPositions(std::vector< double >& rResults) const
924 {
925 rResults.clear();
926
927 // calculate the x-extrema parameters by zeroing first x-derivative
928 // of the cubic bezier's parametric formula, which results in a
929 // quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX
930 const B2DPoint aControlDiff( maControlPointA - maControlPointB );
931 double fCX = maControlPointA.getX() - maStartPoint.getX();
932 const double fBX = fCX + aControlDiff.getX();
933 const double fAX = 3 * aControlDiff.getX() + (maEndPoint.getX() - maStartPoint.getX());
934
935 if(fTools::equalZero(fCX))
936 {
937 // detect fCX equal zero and truncate to real zero value in that case
938 fCX = 0.0;
939 }
940
941 if( !fTools::equalZero(fAX) )
942 {
943 // derivative is polynomial of order 2 => use binomial formula
944 const double fD = fBX*fBX - fAX*fCX;
945 if( fD >= 0.0 )
946 {
947 const double fS = sqrt(fD);
948 // calculate both roots (avoiding a numerically unstable subtraction)
949 const double fQ = fBX + ((fBX >= 0) ? +fS : -fS);
950 impCheckExtremumResult(fQ / fAX, rResults);
951 if( !fTools::equalZero(fS) ) // ignore root multiplicity
952 impCheckExtremumResult(fCX / fQ, rResults);
953 }
954 }
955 else if( !fTools::equalZero(fBX) )
956 {
957 // derivative is polynomial of order 1 => one extrema
958 impCheckExtremumResult(fCX / (2 * fBX), rResults);
959 }
960
961 // calculate the y-extrema parameters by zeroing first y-derivative
962 double fCY = maControlPointA.getY() - maStartPoint.getY();
963 const double fBY = fCY + aControlDiff.getY();
964 const double fAY = 3 * aControlDiff.getY() + (maEndPoint.getY() - maStartPoint.getY());
965
966 if(fTools::equalZero(fCY))
967 {
968 // detect fCY equal zero and truncate to real zero value in that case
969 fCY = 0.0;
970 }
971
972 if( !fTools::equalZero(fAY) )
973 {
974 // derivative is polynomial of order 2 => use binomial formula
975 const double fD = fBY*fBY - fAY*fCY;
976 if( fD >= 0.0 )
977 {
978 const double fS = sqrt(fD);
979 // calculate both roots (avoiding a numerically unstable subtraction)
980 const double fQ = fBY + ((fBY >= 0) ? +fS : -fS);
981 impCheckExtremumResult(fQ / fAY, rResults);
982 if( !fTools::equalZero(fS) ) // ignore root multiplicity
983 impCheckExtremumResult(fCY / fQ, rResults);
984 }
985 }
986 else if( !fTools::equalZero(fBY) )
987 {
988 // derivative is polynomial of order 1 => one extrema
989 impCheckExtremumResult(fCY / (2 * fBY), rResults);
990 }
991 }
992
994 {
995 if(rMatrix.isIdentity())
996 return;
997
999 {
1001 }
1002 else
1003 {
1004 maStartPoint *= rMatrix;
1005 maControlPointA *= rMatrix;
1006 }
1007
1009 {
1011 }
1012 else
1013 {
1014 maEndPoint *= rMatrix;
1015 maControlPointB *= rMatrix;
1016 }
1017 }
1018
1020 {
1022 {
1026 }
1027 else
1028 {
1035 }
1036
1038 {
1042 }
1043 else
1044 {
1051 }
1052 }
1053} // end of namespace basegfx
1054
1055/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
XPropertyListType t
#define FACTOR_FOR_UNSHARPEN
const double fMultFactUnsharpen
B2DCubicBezier & operator=(const B2DCubicBezier &rBezier)
void setStartPoint(const B2DPoint &rValue)
const B2DPoint & getStartPoint() const
SAL_DLLPRIVATE B2DCubicBezier snippet(double fStart, double fEnd) const
bool operator!=(const B2DCubicBezier &rBezier) const
SAL_DLLPRIVATE void fround()
fround content
SAL_DLLPRIVATE void getAllExtremumPositions(::std::vector< double > &rResults) const
Get all extremum pos of this segment.
const B2DPoint & getControlPointB() const
SAL_DLLPRIVATE bool getMinimumExtremumPosition(double &rfResult) const
Get the minimum extremum position t.
SAL_DLLPRIVATE void adaptiveSubdivideByAngle(B2DPolygon &rTarget, double fAngleBound) const
adaptive subdivide by angle criteria no start point is added, but all necessary created edges and the...
double getLength(double fDeviation=0.01) const
get length of edge
SAL_DLLPRIVATE double getSmallestDistancePointToBezierSegment(const B2DPoint &rTestPoint, double &rCut) const
void setControlPointA(const B2DPoint &rValue)
void setEndPoint(const B2DPoint &rValue)
B2DRange getRange() const
void setControlPointB(const B2DPoint &rValue)
B2DPoint interpolatePoint(double t) const
void transform(const basegfx::B2DHomMatrix &rMatrix)
apply transformation given in matrix form
bool operator==(const B2DCubicBezier &rBezier) const
void split(double t, B2DCubicBezier *pBezierA, B2DCubicBezier *pBezierB) const
const B2DPoint & getEndPoint() const
const B2DPoint & getControlPointA() const
SAL_DLLPRIVATE double getControlPolygonLength() const
bool equal(const B2DCubicBezier &rBezier) const
B2DVector getTangent(double t) const
get the tangent in point t
SAL_DLLPRIVATE double getEdgeLength() const
SAL_DLLPRIVATE void adaptiveSubdivideByCount(B2DPolygon &rTarget, sal_uInt32 nCount) const
#i37443# adaptive subdivide by nCount subdivisions no start point is added, but all necessary created...
void adaptiveSubdivideByDistance(B2DPolygon &rTarget, double fDistanceBound, int nRecurseLimit=30) const
Subdivide cubic bezier segment.
bool isIdentity() const
Base Point class with two double values.
Definition: b2dpoint.hxx:42
basegfx::B2DPoint const & getB2DPoint(sal_uInt32 nIndex) const
Coordinate interface.
void append(const basegfx::B2DPoint &rPoint, sal_uInt32 nCount)
sal_uInt32 count() const
member count
A two-dimensional interval over doubles.
Definition: b2drange.hxx:54
Base Point class with two double values.
Definition: b2dvector.hxx:40
double cross(const B2DVector &rVec) const
Calculate the length of the cross product with another 2D Vector.
Definition: b2dvector.hxx:151
double getLength() const
Calculate the length of this 2D Vector.
Definition: b2dvector.cxx:54
void expand(const Tuple2D< TYPE > &rTuple)
add point to the set, expanding as necessary
Definition: Range2D.hxx:142
bool equalZero() const
Definition: Tuple2D.hxx:95
bool equal(const Tuple2D< TYPE > &rTup) const
Definition: Tuple2D.hxx:83
TYPE getX() const
Get X-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:63
TYPE getY() const
Get Y-Coordinate of 2D Tuple.
Definition: Tuple2D.hxx:66
int nCount
FilterGroup & rTarget
uno_Any a
::basegfx::B2DPoint maStartPoint
::basegfx::B2DPoint maEndPoint
bool lessOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:188
bool more(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:194
bool equalZero(const T &rfVal)
Compare against small value.
Definition: ftools.hxx:156
bool betweenOrEqualEither(const T &rfValA, const T &rfValB, const T &rfValC)
Definition: ftools.hxx:206
bool equal(T const &rfValA, T const &rfValB)
Definition: ftools.hxx:169
bool less(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:182
bool moreOrEqual(const T &rfValA, const T &rfValB)
Definition: ftools.hxx:200
B2DTuple interpolate(const B2DTuple &rOld1, const B2DTuple &rOld2, double t)
Definition: b2dtuple.hxx:96
bool areParallel(const B2DVector &rVecA, const B2DVector &rVecB)
Test two vectors which need not to be normalized for parallelism.
Definition: b2dvector.cxx:111
B2DTuple average(const B2DTuple &rOld1, const B2DTuple &rOld2)
Definition: b2dtuple.hxx:118
B2IRange fround(const B2DRange &rRange)
Round double to nearest integer for 2D range.
Definition: b2drange.cxx:64
constexpr double deg2rad(double v)
Convert value from degrees to radians.
Definition: ftools.hxx:82