Coverage Report

Created: 2025-11-16 09:57

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