Get the style color and number just once
[LibreOffice.git] / basegfx / source / curve / b2dcubicbezier.cxx
blob7485ccc54a9b39e6c223fbacd407d849a056953e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <basegfx/curve/b2dcubicbezier.hxx>
21 #include <basegfx/vector/b2dvector.hxx>
22 #include <basegfx/polygon/b2dpolygon.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <basegfx/numeric/ftools.hxx>
26 #include <osl/diagnose.h>
28 #include <cmath>
29 #include <limits>
31 // #i37443#
32 #define FACTOR_FOR_UNSHARPEN (1.6)
33 #ifdef DBG_UTIL
34 const double fMultFactUnsharpen = FACTOR_FOR_UNSHARPEN;
35 #endif
37 namespace basegfx
39 namespace
41 void ImpSubDivAngle(
42 const B2DPoint& rfPA, // start point
43 const B2DPoint& rfEA, // edge on A
44 const B2DPoint& rfEB, // edge on B
45 const B2DPoint& rfPB, // end point
46 B2DPolygon& rTarget, // target polygon
47 double fAngleBound, // angle bound in [0.0 .. 2PI]
48 bool bAllowUnsharpen, // #i37443# allow the criteria to get unsharp in recursions
49 sal_uInt16 nMaxRecursionDepth) // endless loop protection
51 if(nMaxRecursionDepth)
53 // do angle test
54 B2DVector aLeft(rfEA - rfPA);
55 B2DVector aRight(rfEB - rfPB);
57 // #i72104#
58 if(aLeft.equalZero())
60 aLeft = rfEB - rfPA;
63 if(aRight.equalZero())
65 aRight = rfEA - rfPB;
68 const double fCurrentAngle(aLeft.angle(aRight));
70 if(fabs(fCurrentAngle) > (M_PI - fAngleBound))
72 // end recursion
73 nMaxRecursionDepth = 0;
75 else
77 if(bAllowUnsharpen)
79 // #i37443# unsharpen criteria
80 #ifdef DBG_UTIL
81 fAngleBound *= fMultFactUnsharpen;
82 #else
83 fAngleBound *= FACTOR_FOR_UNSHARPEN;
84 #endif
89 if(nMaxRecursionDepth)
91 // divide at 0.5
92 const B2DPoint aS1L(average(rfPA, rfEA));
93 const B2DPoint aS1C(average(rfEA, rfEB));
94 const B2DPoint aS1R(average(rfEB, rfPB));
95 const B2DPoint aS2L(average(aS1L, aS1C));
96 const B2DPoint aS2R(average(aS1C, aS1R));
97 const B2DPoint aS3C(average(aS2L, aS2R));
99 // left recursion
100 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
102 // right recursion
103 ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, fAngleBound, bAllowUnsharpen, nMaxRecursionDepth - 1);
105 else
107 rTarget.append(rfPB);
111 void ImpSubDivAngleStart(
112 const B2DPoint& rfPA, // start point
113 const B2DPoint& rfEA, // edge on A
114 const B2DPoint& rfEB, // edge on B
115 const B2DPoint& rfPB, // end point
116 B2DPolygon& rTarget, // target polygon
117 const double& rfAngleBound) // angle bound in [0.0 .. 2PI]
119 sal_uInt16 nMaxRecursionDepth(8);
120 const B2DVector aLeft(rfEA - rfPA);
121 const B2DVector aRight(rfEB - rfPB);
122 bool bLeftEqualZero(aLeft.equalZero());
123 bool bRightEqualZero(aRight.equalZero());
124 bool bAllParallel(false);
126 if(bLeftEqualZero && bRightEqualZero)
128 nMaxRecursionDepth = 0;
130 else
132 const B2DVector aBase(rfPB - rfPA);
133 const bool bBaseEqualZero(aBase.equalZero()); // #i72104#
135 if(!bBaseEqualZero)
137 const bool bLeftParallel(bLeftEqualZero || areParallel(aLeft, aBase));
138 const bool bRightParallel(bRightEqualZero || areParallel(aRight, aBase));
140 if(bLeftParallel && bRightParallel)
142 bAllParallel = true;
144 if(!bLeftEqualZero)
146 double fFactor;
148 if(fabs(aBase.getX()) > fabs(aBase.getY()))
150 fFactor = aLeft.getX() / aBase.getX();
152 else
154 fFactor = aLeft.getY() / aBase.getY();
157 if(fFactor >= 0.0 && fFactor <= 1.0)
159 bLeftEqualZero = true;
163 if(!bRightEqualZero)
165 double fFactor;
167 if(fabs(aBase.getX()) > fabs(aBase.getY()))
169 fFactor = aRight.getX() / -aBase.getX();
171 else
173 fFactor = aRight.getY() / -aBase.getY();
176 if(fFactor >= 0.0 && fFactor <= 1.0)
178 bRightEqualZero = true;
182 if(bLeftEqualZero && bRightEqualZero)
184 nMaxRecursionDepth = 0;
190 if(nMaxRecursionDepth)
192 // divide at 0.5 ad test both edges for angle criteria
193 const B2DPoint aS1L(average(rfPA, rfEA));
194 const B2DPoint aS1C(average(rfEA, rfEB));
195 const B2DPoint aS1R(average(rfEB, rfPB));
196 const B2DPoint aS2L(average(aS1L, aS1C));
197 const B2DPoint aS2R(average(aS1C, aS1R));
198 const B2DPoint aS3C(average(aS2L, aS2R));
200 // test left
201 bool bAngleIsSmallerLeft(bAllParallel && bLeftEqualZero);
202 if(!bAngleIsSmallerLeft)
204 const B2DVector aLeftLeft(bLeftEqualZero ? aS2L - aS1L : aS1L - rfPA); // #i72104#
205 const B2DVector aRightLeft(aS2L - aS3C);
206 const double fCurrentAngleLeft(aLeftLeft.angle(aRightLeft));
207 bAngleIsSmallerLeft = (fabs(fCurrentAngleLeft) > (M_PI - rfAngleBound));
210 // test right
211 bool bAngleIsSmallerRight(bAllParallel && bRightEqualZero);
212 if(!bAngleIsSmallerRight)
214 const B2DVector aLeftRight(aS2R - aS3C);
215 const B2DVector aRightRight(bRightEqualZero ? aS2R - aS1R : aS1R - rfPB); // #i72104#
216 const double fCurrentAngleRight(aLeftRight.angle(aRightRight));
217 bAngleIsSmallerRight = (fabs(fCurrentAngleRight) > (M_PI - rfAngleBound));
220 if(bAngleIsSmallerLeft && bAngleIsSmallerRight)
222 // no recursion necessary at all
223 nMaxRecursionDepth = 0;
225 else
227 // left
228 if(bAngleIsSmallerLeft)
230 rTarget.append(aS3C);
232 else
234 ImpSubDivAngle(rfPA, aS1L, aS2L, aS3C, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
237 // right
238 if(bAngleIsSmallerRight)
240 rTarget.append(rfPB);
242 else
244 ImpSubDivAngle(aS3C, aS2R, aS1R, rfPB, rTarget, rfAngleBound, true/*bAllowUnsharpen*/, nMaxRecursionDepth);
249 if(!nMaxRecursionDepth)
251 rTarget.append(rfPB);
255 void ImpSubDivDistance(
256 const B2DPoint& rfPA, // start point
257 const B2DPoint& rfEA, // edge on A
258 const B2DPoint& rfEB, // edge on B
259 const B2DPoint& rfPB, // end point
260 B2DPolygon& rTarget, // target polygon
261 double fDistanceBound2, // quadratic distance criteria
262 double fLastDistanceError2, // the last quadratic distance error
263 sal_uInt16 nMaxRecursionDepth) // endless loop protection
265 if(nMaxRecursionDepth)
267 // decide if another recursion is needed. If not, set
268 // nMaxRecursionDepth to zero
270 // Perform bezier flatness test (lecture notes from R. Schaback,
271 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
273 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
274 // 0<=j<=n
276 // What is calculated here is an upper bound to the distance from
277 // a line through b_0 and b_3 (rfPA and P4 in our notation) and the
278 // curve. We can drop 0 and n from the running indices, since the
279 // argument of max becomes zero for those cases.
280 const double fJ1x(rfEA.getX() - rfPA.getX() - 1.0/3.0*(rfPB.getX() - rfPA.getX()));
281 const double fJ1y(rfEA.getY() - rfPA.getY() - 1.0/3.0*(rfPB.getY() - rfPA.getY()));
282 const double fJ2x(rfEB.getX() - rfPA.getX() - 2.0/3.0*(rfPB.getX() - rfPA.getX()));
283 const double fJ2y(rfEB.getY() - rfPA.getY() - 2.0/3.0*(rfPB.getY() - rfPA.getY()));
284 const double fDistanceError2(std::max(fJ1x*fJ1x + fJ1y*fJ1y, fJ2x*fJ2x + fJ2y*fJ2y));
286 // stop if error measure does not improve anymore. This is a
287 // safety guard against floating point inaccuracies.
288 // stop if distance from line is guaranteed to be bounded by d
289 const bool bFurtherDivision(fLastDistanceError2 > fDistanceError2 && fDistanceError2 >= fDistanceBound2);
291 if(bFurtherDivision)
293 // remember last error value
294 fLastDistanceError2 = fDistanceError2;
296 else
298 // stop recursion
299 nMaxRecursionDepth = 0;
303 if(nMaxRecursionDepth)
305 // divide at 0.5
306 const B2DPoint aS1L(average(rfPA, rfEA));
307 const B2DPoint aS1C(average(rfEA, rfEB));
308 const B2DPoint aS1R(average(rfEB, rfPB));
309 const B2DPoint aS2L(average(aS1L, aS1C));
310 const B2DPoint aS2R(average(aS1C, aS1R));
311 const B2DPoint aS3C(average(aS2L, aS2R));
313 // left recursion
314 ImpSubDivDistance(rfPA, aS1L, aS2L, aS3C, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
316 // right recursion
317 ImpSubDivDistance(aS3C, aS2R, aS1R, rfPB, rTarget, fDistanceBound2, fLastDistanceError2, nMaxRecursionDepth - 1);
319 else
321 rTarget.append(rfPB);
324 } // end of anonymous namespace
325 } // end of namespace basegfx
327 namespace basegfx
329 B2DCubicBezier::B2DCubicBezier(const B2DCubicBezier&) = default;
331 B2DCubicBezier::B2DCubicBezier() = default;
333 B2DCubicBezier::B2DCubicBezier(const B2DPoint& rStart, const B2DPoint& rControlPointA, const B2DPoint& rControlPointB, const B2DPoint& rEnd)
334 : maStartPoint(rStart),
335 maEndPoint(rEnd),
336 maControlPointA(rControlPointA),
337 maControlPointB(rControlPointB)
341 // assignment operator
342 B2DCubicBezier& B2DCubicBezier::operator=(const B2DCubicBezier&) = default;
344 // compare operators
345 bool B2DCubicBezier::operator==(const B2DCubicBezier& rBezier) const
347 return (
348 maStartPoint == rBezier.maStartPoint
349 && maEndPoint == rBezier.maEndPoint
350 && maControlPointA == rBezier.maControlPointA
351 && maControlPointB == rBezier.maControlPointB
355 bool B2DCubicBezier::equal(const B2DCubicBezier& rBezier) const
357 return (
358 maStartPoint.equal(rBezier.maStartPoint)
359 && maEndPoint.equal(rBezier.maEndPoint)
360 && maControlPointA.equal(rBezier.maControlPointA)
361 && maControlPointB.equal(rBezier.maControlPointB)
365 // test if vectors are used
366 bool B2DCubicBezier::isBezier() const
368 return maControlPointA != maStartPoint || maControlPointB != maEndPoint;
371 void B2DCubicBezier::testAndSolveTrivialBezier()
373 if(maControlPointA == maStartPoint && maControlPointB == maEndPoint)
374 return;
376 const B2DVector aEdge(maEndPoint - maStartPoint);
378 // controls parallel to edge can be trivial. No edge -> not parallel -> control can
379 // still not be trivial (e.g. ballon loop)
380 if(aEdge.equalZero())
381 return;
383 // get control vectors
384 const B2DVector aVecA(maControlPointA - maStartPoint);
385 const B2DVector aVecB(maControlPointB - maEndPoint);
387 // check if trivial per se
388 bool bAIsTrivial(aVecA.equalZero());
389 bool bBIsTrivial(aVecB.equalZero());
391 // #i102241# prepare inverse edge length to normalize cross values;
392 // else the small compare value used in fTools::equalZero
393 // will be length dependent and this detection will work as less
394 // precise as longer the edge is. In principle, the length of the control
395 // vector would need to be used too, but to be trivial it is assumed to
396 // be of roughly equal length to the edge, so edge length can be used
397 // for both. Only needed when one of both is not trivial per se.
398 const double fInverseEdgeLength(bAIsTrivial && bBIsTrivial
399 ? 1.0
400 : 1.0 / aEdge.getLength());
402 // if A is not zero, check if it could be
403 if(!bAIsTrivial)
405 // #i102241# parallel to edge? Check aVecA, aEdge. Use cross() which does what
406 // we need here with the precision we need
407 const double fCross(aVecA.cross(aEdge) * fInverseEdgeLength);
409 if(fTools::equalZero(fCross))
411 // get scale to edge. Use bigger distance for numeric quality
412 const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
413 ? aVecA.getX() / aEdge.getX()
414 : aVecA.getY() / aEdge.getY());
416 // relative end point of vector in edge range?
417 if (fTools::betweenOrEqualEither(fScale, 0.0, 1.0))
419 bAIsTrivial = true;
424 // if B is not zero, check if it could be, but only if A is already trivial;
425 // else solve to trivial will not be possible for whole edge
426 if(bAIsTrivial && !bBIsTrivial)
428 // parallel to edge? Check aVecB, aEdge
429 const double fCross(aVecB.cross(aEdge) * fInverseEdgeLength);
431 if(fTools::equalZero(fCross))
433 // get scale to edge. Use bigger distance for numeric quality
434 const double fScale(fabs(aEdge.getX()) > fabs(aEdge.getY())
435 ? aVecB.getX() / aEdge.getX()
436 : aVecB.getY() / aEdge.getY());
438 // end point of vector in edge range? Caution: controlB is directed AGAINST edge
439 if (fTools::betweenOrEqualEither(fScale, -1.0, 0.0))
441 bBIsTrivial = true;
446 // if both are/can be reduced, do it.
447 // Not possible if only one is/can be reduced (!)
448 if(bAIsTrivial && bBIsTrivial)
450 maControlPointA = maStartPoint;
451 maControlPointB = maEndPoint;
455 namespace {
456 double impGetLength(const B2DCubicBezier& rEdge, double fDeviation, sal_uInt32 nRecursionWatch)
458 const double fEdgeLength(rEdge.getEdgeLength());
459 const double fControlPolygonLength(rEdge.getControlPolygonLength());
460 const double fCurrentDeviation(fTools::equalZero(fControlPolygonLength) ? 0.0 : 1.0 - (fEdgeLength / fControlPolygonLength));
462 if(!nRecursionWatch || fTools:: lessOrEqual(fCurrentDeviation, fDeviation))
464 return (fEdgeLength + fControlPolygonLength) * 0.5;
466 else
468 B2DCubicBezier aLeft, aRight;
469 const double fNewDeviation(fDeviation * 0.5);
470 const sal_uInt32 nNewRecursionWatch(nRecursionWatch - 1);
472 rEdge.split(0.5, &aLeft, &aRight);
474 return impGetLength(aLeft, fNewDeviation, nNewRecursionWatch)
475 + impGetLength(aRight, fNewDeviation, nNewRecursionWatch);
480 double B2DCubicBezier::getLength(double fDeviation) const
482 if(isBezier())
484 if(fDeviation < 0.00000001)
486 fDeviation = 0.00000001;
489 return impGetLength(*this, fDeviation, 6);
491 else
493 return B2DVector(getEndPoint() - getStartPoint()).getLength();
497 double B2DCubicBezier::getEdgeLength() const
499 const B2DVector aEdge(maEndPoint - maStartPoint);
500 return aEdge.getLength();
503 double B2DCubicBezier::getControlPolygonLength() const
505 const B2DVector aVectorA(maControlPointA - maStartPoint);
506 const B2DVector aVectorB(maEndPoint - maControlPointB);
508 if(!aVectorA.equalZero() || !aVectorB.equalZero())
510 const B2DVector aTop(maControlPointB - maControlPointA);
511 return (aVectorA.getLength() + aVectorB.getLength() + aTop.getLength());
513 else
515 return getEdgeLength();
519 void B2DCubicBezier::adaptiveSubdivideByAngle(B2DPolygon& rTarget, double fAngleBound) const
521 if(isBezier())
523 // use support method #i37443# and allow unsharpen the criteria
524 ImpSubDivAngleStart(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
525 deg2rad(fAngleBound));
527 else
529 rTarget.append(getEndPoint());
533 B2DVector B2DCubicBezier::getTangent(double t) const
535 if(t <= 0.0)
537 // tangent in start point
538 B2DVector aTangent(getControlPointA() - getStartPoint());
540 if(!aTangent.equalZero())
542 return aTangent;
545 // start point and control vector are the same, fallback
546 // to implicit start vector to control point B
547 aTangent = (getControlPointB() - getStartPoint()) * 0.3;
549 if(!aTangent.equalZero())
551 return aTangent;
554 // not a bezier at all, return edge vector
555 return (getEndPoint() - getStartPoint()) * 0.3;
557 else if(fTools::moreOrEqual(t, 1.0))
559 // tangent in end point
560 B2DVector aTangent(getEndPoint() - getControlPointB());
562 if(!aTangent.equalZero())
564 return aTangent;
567 // end point and control vector are the same, fallback
568 // to implicit start vector from control point A
569 aTangent = (getEndPoint() - getControlPointA()) * 0.3;
571 if(!aTangent.equalZero())
573 return aTangent;
576 // not a bezier at all, return edge vector
577 return (getEndPoint() - getStartPoint()) * 0.3;
579 else
581 // t is in ]0.0 .. 1.0[. Split and extract
582 B2DCubicBezier aRight;
583 split(t, nullptr, &aRight);
585 return aRight.getControlPointA() - aRight.getStartPoint();
589 // #i37443# adaptive subdivide by nCount subdivisions
590 void B2DCubicBezier::adaptiveSubdivideByCount(B2DPolygon& rTarget, sal_uInt32 nCount) const
592 const double fLenFact(1.0 / static_cast< double >(nCount + 1));
594 for(sal_uInt32 a(1); a <= nCount; a++)
596 const double fPos(static_cast< double >(a) * fLenFact);
597 rTarget.append(interpolatePoint(fPos));
600 rTarget.append(getEndPoint());
603 // adaptive subdivide by distance
604 void B2DCubicBezier::adaptiveSubdivideByDistance(B2DPolygon& rTarget, double fDistanceBound, int nRecurseLimit) const
606 if(isBezier())
608 ImpSubDivDistance(maStartPoint, maControlPointA, maControlPointB, maEndPoint, rTarget,
609 fDistanceBound * fDistanceBound, std::numeric_limits<double>::max(), nRecurseLimit);
611 else
613 rTarget.append(getEndPoint());
617 B2DPoint B2DCubicBezier::interpolatePoint(double t) const
619 OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::interpolatePoint: Access out of range (!)");
621 if(isBezier())
623 const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
624 const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
625 const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
626 const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
627 const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
629 return interpolate(aS2L, aS2R, t);
631 else
633 return interpolate(maStartPoint, maEndPoint, t);
637 double B2DCubicBezier::getSmallestDistancePointToBezierSegment(const B2DPoint& rTestPoint, double& rCut) const
639 const sal_uInt32 nInitialDivisions(3);
640 B2DPolygon aInitialPolygon;
642 // as start make a fix division, creates nInitialDivisions + 2 points
643 aInitialPolygon.append(getStartPoint());
644 adaptiveSubdivideByCount(aInitialPolygon, nInitialDivisions);
646 // now look for the closest point
647 const sal_uInt32 nPointCount(aInitialPolygon.count());
648 B2DVector aVector(rTestPoint - aInitialPolygon.getB2DPoint(0));
649 double pointDistance(std::hypot(aVector.getX(), aVector.getY()));
650 double newPointDistance;
651 sal_uInt32 nSmallestIndex(0);
653 for(sal_uInt32 a(1); a < nPointCount; a++)
655 aVector = B2DVector(rTestPoint - aInitialPolygon.getB2DPoint(a));
656 newPointDistance = std::hypot(aVector.getX(), aVector.getY());
657 if(newPointDistance < pointDistance)
659 pointDistance = newPointDistance;
660 nSmallestIndex = a;
664 // look right and left for even smaller distances
665 double fStepValue(1.0 / static_cast<double>((nPointCount - 1) * 2)); // half the edge step width
666 double fPosition(static_cast<double>(nSmallestIndex) / static_cast<double>(nPointCount - 1));
668 while(true)
670 // test left
671 double fPosLeft(fPosition - fStepValue);
673 if(fPosLeft < 0.0)
675 fPosLeft = 0.0;
676 aVector = B2DVector(rTestPoint - maStartPoint);
678 else
680 aVector = B2DVector(rTestPoint - interpolatePoint(fPosLeft));
683 newPointDistance = std::hypot(aVector.getX(), aVector.getY());
685 if(fTools::less(newPointDistance, pointDistance))
687 pointDistance = newPointDistance;
688 fPosition = fPosLeft;
690 else
692 // test right
693 double fPosRight(fPosition + fStepValue);
695 if(fPosRight > 1.0)
697 fPosRight = 1.0;
698 aVector = B2DVector(rTestPoint - maEndPoint);
700 else
702 aVector = B2DVector(rTestPoint - interpolatePoint(fPosRight));
705 newPointDistance = std::hypot(aVector.getX(), aVector.getY());
707 if(fTools::less(newPointDistance, pointDistance))
709 pointDistance = newPointDistance;
710 fPosition = fPosRight;
712 else
714 // not less left or right, done
715 break;
719 if(fPosition == 0.0 || fPosition == 1.0)
721 // if we are completely left or right, we are done
722 break;
725 // prepare next step value
726 fStepValue /= 2.0;
729 rCut = fPosition;
730 return pointDistance;
733 void B2DCubicBezier::split(double t, B2DCubicBezier* pBezierA, B2DCubicBezier* pBezierB) const
735 OSL_ENSURE(t >= 0.0 && t <= 1.0, "B2DCubicBezier::split: Access out of range (!)");
737 if(!pBezierA && !pBezierB)
739 return;
742 if(isBezier())
744 const B2DPoint aS1L(interpolate(maStartPoint, maControlPointA, t));
745 const B2DPoint aS1C(interpolate(maControlPointA, maControlPointB, t));
746 const B2DPoint aS1R(interpolate(maControlPointB, maEndPoint, t));
747 const B2DPoint aS2L(interpolate(aS1L, aS1C, t));
748 const B2DPoint aS2R(interpolate(aS1C, aS1R, t));
749 const B2DPoint aS3C(interpolate(aS2L, aS2R, t));
751 if(pBezierA)
753 pBezierA->setStartPoint(maStartPoint);
754 pBezierA->setEndPoint(aS3C);
755 pBezierA->setControlPointA(aS1L);
756 pBezierA->setControlPointB(aS2L);
759 if(pBezierB)
761 pBezierB->setStartPoint(aS3C);
762 pBezierB->setEndPoint(maEndPoint);
763 pBezierB->setControlPointA(aS2R);
764 pBezierB->setControlPointB(aS1R);
767 else
769 const B2DPoint aSplit(interpolate(maStartPoint, maEndPoint, t));
771 if(pBezierA)
773 pBezierA->setStartPoint(maStartPoint);
774 pBezierA->setEndPoint(aSplit);
775 pBezierA->setControlPointA(maStartPoint);
776 pBezierA->setControlPointB(aSplit);
779 if(pBezierB)
781 pBezierB->setStartPoint(aSplit);
782 pBezierB->setEndPoint(maEndPoint);
783 pBezierB->setControlPointA(aSplit);
784 pBezierB->setControlPointB(maEndPoint);
789 B2DCubicBezier B2DCubicBezier::snippet(double fStart, double fEnd) const
791 B2DCubicBezier aRetval;
793 fStart = std::clamp(fStart, 0.0, 1.0);
794 fEnd = std::clamp(fEnd, 0.0, 1.0);
796 if(fEnd <= fStart)
798 // empty or NULL, create single point at center
799 const double fSplit((fEnd + fStart) * 0.5);
800 const B2DPoint aPoint(interpolate(getStartPoint(), getEndPoint(), fSplit));
801 aRetval.setStartPoint(aPoint);
802 aRetval.setEndPoint(aPoint);
803 aRetval.setControlPointA(aPoint);
804 aRetval.setControlPointB(aPoint);
806 else
808 if(isBezier())
810 // copy bezier; cut off right, then cut off left. Do not forget to
811 // adapt cut value when both cuts happen
812 const bool bEndIsOne(fTools::equal(fEnd, 1.0));
813 const bool bStartIsZero(fTools::equalZero(fStart));
814 aRetval = *this;
816 if(!bEndIsOne)
818 aRetval.split(fEnd, &aRetval, nullptr);
820 if(!bStartIsZero)
822 fStart /= fEnd;
826 if(!bStartIsZero)
828 aRetval.split(fStart, nullptr, &aRetval);
831 else
833 // no bezier, create simple edge
834 const B2DPoint aPointA(interpolate(getStartPoint(), getEndPoint(), fStart));
835 const B2DPoint aPointB(interpolate(getStartPoint(), getEndPoint(), fEnd));
836 aRetval.setStartPoint(aPointA);
837 aRetval.setEndPoint(aPointB);
838 aRetval.setControlPointA(aPointA);
839 aRetval.setControlPointB(aPointB);
843 return aRetval;
846 B2DRange B2DCubicBezier::getRange() const
848 B2DRange aRetval(maStartPoint, maEndPoint);
850 aRetval.expand(maControlPointA);
851 aRetval.expand(maControlPointB);
853 return aRetval;
856 bool B2DCubicBezier::getMinimumExtremumPosition(double& rfResult) const
858 std::vector< double > aAllResults;
860 aAllResults.reserve(4);
861 getAllExtremumPositions(aAllResults);
863 const sal_uInt32 nCount(aAllResults.size());
865 if(!nCount)
867 return false;
869 else if(nCount == 1)
871 rfResult = aAllResults[0];
872 return true;
874 else
876 rfResult = *(std::min_element(aAllResults.begin(), aAllResults.end()));
877 return true;
881 namespace
883 void impCheckExtremumResult(double fCandidate, std::vector< double >& rResult)
885 // check for range ]0.0 .. 1.0[ with excluding 1.0 and 0.0 clearly
886 // by using the equalZero test, NOT ::more or ::less which will use the
887 // ApproxEqual() which is too exact here
888 if(fCandidate > 0.0 && !fTools::equalZero(fCandidate))
890 if(fCandidate < 1.0 && !fTools::equalZero(fCandidate - 1.0))
892 rResult.push_back(fCandidate);
898 void B2DCubicBezier::getAllExtremumPositions(std::vector< double >& rResults) const
900 rResults.clear();
902 // calculate the x-extrema parameters by zeroing first x-derivative
903 // of the cubic bezier's parametric formula, which results in a
904 // quadratic equation: dBezier/dt = t*t*fAX - 2*t*fBX + fCX
905 const B2DPoint aControlDiff( maControlPointA - maControlPointB );
906 double fCX = maControlPointA.getX() - maStartPoint.getX();
907 const double fBX = fCX + aControlDiff.getX();
908 const double fAX = 3 * aControlDiff.getX() + (maEndPoint.getX() - maStartPoint.getX());
910 if(fTools::equalZero(fCX))
912 // detect fCX equal zero and truncate to real zero value in that case
913 fCX = 0.0;
916 if( !fTools::equalZero(fAX) )
918 // derivative is polynomial of order 2 => use binomial formula
919 const double fD = fBX*fBX - fAX*fCX;
920 if( fD >= 0.0 )
922 const double fS = sqrt(fD);
923 // calculate both roots (avoiding a numerically unstable subtraction)
924 const double fQ = fBX + ((fBX >= 0) ? +fS : -fS);
925 impCheckExtremumResult(fQ / fAX, rResults);
926 if( !fTools::equalZero(fS) ) // ignore root multiplicity
927 impCheckExtremumResult(fCX / fQ, rResults);
930 else if( !fTools::equalZero(fBX) )
932 // derivative is polynomial of order 1 => one extrema
933 impCheckExtremumResult(fCX / (2 * fBX), rResults);
936 // calculate the y-extrema parameters by zeroing first y-derivative
937 double fCY = maControlPointA.getY() - maStartPoint.getY();
938 const double fBY = fCY + aControlDiff.getY();
939 const double fAY = 3 * aControlDiff.getY() + (maEndPoint.getY() - maStartPoint.getY());
941 if(fTools::equalZero(fCY))
943 // detect fCY equal zero and truncate to real zero value in that case
944 fCY = 0.0;
947 if( !fTools::equalZero(fAY) )
949 // derivative is polynomial of order 2 => use binomial formula
950 const double fD = fBY*fBY - fAY*fCY;
951 if( fD >= 0.0 )
953 const double fS = sqrt(fD);
954 // calculate both roots (avoiding a numerically unstable subtraction)
955 const double fQ = fBY + ((fBY >= 0) ? +fS : -fS);
956 impCheckExtremumResult(fQ / fAY, rResults);
957 if( !fTools::equalZero(fS) ) // ignore root multiplicity
958 impCheckExtremumResult(fCY / fQ, rResults);
961 else if( !fTools::equalZero(fBY) )
963 // derivative is polynomial of order 1 => one extrema
964 impCheckExtremumResult(fCY / (2 * fBY), rResults);
968 void B2DCubicBezier::transform(const basegfx::B2DHomMatrix& rMatrix)
970 if(rMatrix.isIdentity())
971 return;
973 if(maControlPointA == maStartPoint)
975 maControlPointA = maStartPoint = rMatrix * maStartPoint;
977 else
979 maStartPoint *= rMatrix;
980 maControlPointA *= rMatrix;
983 if(maControlPointB == maEndPoint)
985 maControlPointB = maEndPoint = rMatrix * maEndPoint;
987 else
989 maEndPoint *= rMatrix;
990 maControlPointB *= rMatrix;
994 void B2DCubicBezier::fround()
996 if(maControlPointA == maStartPoint)
998 maControlPointA = maStartPoint = basegfx::B2DPoint(
999 std::round(maStartPoint.getX()),
1000 std::round(maStartPoint.getY()));
1002 else
1004 maStartPoint = basegfx::B2DPoint(
1005 std::round(maStartPoint.getX()),
1006 std::round(maStartPoint.getY()));
1007 maControlPointA = basegfx::B2DPoint(
1008 std::round(maControlPointA.getX()),
1009 std::round(maControlPointA.getY()));
1012 if(maControlPointB == maEndPoint)
1014 maControlPointB = maEndPoint = basegfx::B2DPoint(
1015 std::round(maEndPoint.getX()),
1016 std::round(maEndPoint.getY()));
1018 else
1020 maEndPoint = basegfx::B2DPoint(
1021 std::round(maEndPoint.getX()),
1022 std::round(maEndPoint.getY()));
1023 maControlPointB = basegfx::B2DPoint(
1024 std::round(maControlPointB.getX()),
1025 std::round(maControlPointB.getY()));
1028 } // end of namespace basegfx
1030 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */