1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <osl/endian.h>
21 #include <osl/diagnose.h>
22 #include <sal/log.hxx>
23 #include <tools/bigint.hxx>
24 #include <tools/debug.hxx>
25 #include <tools/helpers.hxx>
26 #include <tools/stream.hxx>
27 #include <tools/vcompat.hxx>
28 #include <tools/gen.hxx>
30 #include <o3tl/safeint.hxx>
31 #include <tools/line.hxx>
32 #include <tools/poly.hxx>
33 #include <basegfx/polygon/b2dpolygon.hxx>
34 #include <basegfx/point/b2dpoint.hxx>
35 #include <basegfx/vector/b2dvector.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
37 #include <basegfx/curve/b2dcubicbezier.hxx>
52 #define EDGE_HORZ (EDGE_RIGHT | EDGE_LEFT)
53 #define EDGE_VERT (EDGE_TOP | EDGE_BOTTOM)
54 #define SMALL_DVALUE 0.0000001
55 #define FSQRT2 1.4142135623730950488016887242097
57 static double ImplGetParameter( const Point
& rCenter
, const Point
& rPt
, double fWR
, double fHR
)
59 const tools::Long nDX
= rPt
.X() - rCenter
.X();
60 double fAngle
= atan2( -rPt
.Y() + rCenter
.Y(), ( ( nDX
== 0 ) ? 0.000000001 : nDX
) );
62 return atan2(fWR
*sin(fAngle
), fHR
*cos(fAngle
));
65 ImplPolygon::ImplPolygon( sal_uInt16 nInitSize
)
67 ImplInitSize(nInitSize
, false);
70 ImplPolygon::ImplPolygon( const ImplPolygon
& rImpPoly
)
72 if ( rImpPoly
.mnPoints
)
74 mxPointAry
.reset(new Point
[rImpPoly
.mnPoints
]);
75 memcpy(mxPointAry
.get(), rImpPoly
.mxPointAry
.get(), rImpPoly
.mnPoints
* sizeof(Point
));
77 if( rImpPoly
.mxFlagAry
)
79 mxFlagAry
.reset(new PolyFlags
[rImpPoly
.mnPoints
]);
80 memcpy(mxFlagAry
.get(), rImpPoly
.mxFlagAry
.get(), rImpPoly
.mnPoints
);
84 mnPoints
= rImpPoly
.mnPoints
;
87 ImplPolygon::ImplPolygon( sal_uInt16 nInitSize
, const Point
* pInitAry
, const PolyFlags
* pInitFlags
)
91 mxPointAry
.reset(new Point
[nInitSize
]);
92 memcpy(mxPointAry
.get(), pInitAry
, nInitSize
* sizeof(Point
));
96 mxFlagAry
.reset(new PolyFlags
[nInitSize
]);
97 memcpy(mxFlagAry
.get(), pInitFlags
, nInitSize
);
101 mnPoints
= nInitSize
;
104 ImplPolygon::ImplPolygon( const tools::Rectangle
& rRect
)
106 if ( !rRect
.IsEmpty() )
109 mxPointAry
[0] = rRect
.TopLeft();
110 mxPointAry
[1] = rRect
.TopRight();
111 mxPointAry
[2] = rRect
.BottomRight();
112 mxPointAry
[3] = rRect
.BottomLeft();
113 mxPointAry
[4] = rRect
.TopLeft();
119 ImplPolygon::ImplPolygon( const tools::Rectangle
& rRect
, sal_uInt32 nHorzRound
, sal_uInt32 nVertRound
)
121 if ( !rRect
.IsEmpty() )
123 tools::Rectangle
aRect( rRect
);
124 aRect
.Justify(); // SJ: i9140
126 nHorzRound
= std::min( nHorzRound
, static_cast<sal_uInt32
>(std::abs( aRect
.GetWidth() >> 1 )) );
127 nVertRound
= std::min( nVertRound
, static_cast<sal_uInt32
>(std::abs( aRect
.GetHeight() >> 1 )) );
129 if( !nHorzRound
&& !nVertRound
)
132 mxPointAry
[0] = aRect
.TopLeft();
133 mxPointAry
[1] = aRect
.TopRight();
134 mxPointAry
[2] = aRect
.BottomRight();
135 mxPointAry
[3] = aRect
.BottomLeft();
136 mxPointAry
[4] = aRect
.TopLeft();
140 const Point
aTL( aRect
.Left() + nHorzRound
, aRect
.Top() + nVertRound
);
141 const Point
aTR( aRect
.Right() - nHorzRound
, aRect
.Top() + nVertRound
);
142 const Point
aBR( aRect
.Right() - nHorzRound
, aRect
.Bottom() - nVertRound
);
143 const Point
aBL( aRect
.Left() + nHorzRound
, aRect
.Bottom() - nVertRound
);
144 std::unique_ptr
<tools::Polygon
> pEllipsePoly( new tools::Polygon( Point(), nHorzRound
, nVertRound
) );
145 sal_uInt16 i
, nEnd
, nSize4
= pEllipsePoly
->GetSize() >> 2;
147 ImplInitSize((pEllipsePoly
->GetSize() + 1));
149 const Point
* pSrcAry
= pEllipsePoly
->GetConstPointAry();
150 Point
* pDstAry
= mxPointAry
.get();
152 for( i
= 0, nEnd
= nSize4
; i
< nEnd
; i
++ )
153 pDstAry
[ i
] = pSrcAry
[ i
] + aTR
;
155 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
156 pDstAry
[ i
] = pSrcAry
[ i
] + aTL
;
158 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
159 pDstAry
[ i
] = pSrcAry
[ i
] + aBL
;
161 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
162 pDstAry
[ i
] = pSrcAry
[ i
] + aBR
;
164 pDstAry
[ nEnd
] = pDstAry
[ 0 ];
171 ImplPolygon::ImplPolygon( const Point
& rCenter
, tools::Long nRadX
, tools::Long nRadY
)
176 // Compute default (depends on size)
178 const bool bOverflow
= o3tl::checked_multiply(nRadX
, nRadY
, nRadXY
);
181 nPoints
= static_cast<sal_uInt16
>(MinMax(
182 ( F_PI
* ( 1.5 * ( nRadX
+ nRadY
) -
183 sqrt( static_cast<double>(std::abs(nRadXY
)) ) ) ),
191 if( ( nRadX
> 32 ) && ( nRadY
> 32 ) && ( nRadX
+ nRadY
) < 8192 )
194 // Ceil number of points until divisible by four
195 nPoints
= (nPoints
+ 3) & ~3;
196 ImplInitSize(nPoints
);
199 sal_uInt16 nPoints2
= nPoints
>> 1;
200 sal_uInt16 nPoints4
= nPoints
>> 2;
202 double nAngleStep
= F_PI2
/ ( nPoints4
- 1 );
204 for( i
=0, nAngle
= 0.0; i
< nPoints4
; i
++, nAngle
+= nAngleStep
)
206 tools::Long nX
= FRound( nRadX
* cos( nAngle
) );
207 tools::Long nY
= FRound( -nRadY
* sin( nAngle
) );
209 Point
* pPt
= &(mxPointAry
[i
]);
210 pPt
->setX( nX
+ rCenter
.X() );
211 pPt
->setY( nY
+ rCenter
.Y() );
212 pPt
= &(mxPointAry
[nPoints2
-i
-1]);
213 pPt
->setX( -nX
+ rCenter
.X() );
214 pPt
->setY( nY
+ rCenter
.Y() );
215 pPt
= &(mxPointAry
[i
+nPoints2
]);
216 pPt
->setX( -nX
+ rCenter
.X() );
217 pPt
->setY( -nY
+ rCenter
.Y() );
218 pPt
= &(mxPointAry
[nPoints
-i
-1]);
219 pPt
->setX( nX
+ rCenter
.X() );
220 pPt
->setY( -nY
+ rCenter
.Y() );
227 ImplPolygon::ImplPolygon( const tools::Rectangle
& rBound
, const Point
& rStart
, const Point
& rEnd
,
230 const tools::Long nWidth
= rBound
.GetWidth();
231 const tools::Long nHeight
= rBound
.GetHeight();
233 if( ( nWidth
!= 0 ) && ( nHeight
!= 0 ) )
235 const Point
aCenter( rBound
.Center() );
236 // tdf#142268 Get Top Left corner of rectangle (the rectangle is not always correctly created)
237 const auto aBoundLeft
= rBound
.Left() < aCenter
.X() ? rBound
.Left() : rBound
.Right();
238 const auto aBoundTop
= rBound
.Top() < aCenter
.Y() ? rBound
.Top() : rBound
.Bottom();
239 const tools::Long nRadX
= aCenter
.X() - aBoundLeft
;
240 const tools::Long nRadY
= aCenter
.Y() - aBoundTop
;
244 const bool bOverflow
= o3tl::checked_multiply(nRadX
, nRadY
, nRadXY
);
247 nPoints
= static_cast<sal_uInt16
>(MinMax(
248 ( F_PI
* ( 1.5 * ( nRadX
+ nRadY
) -
249 sqrt( static_cast<double>(std::abs(nRadXY
)) ) ) ),
258 if (nRadX
> 32 && nRadY
> 32 && o3tl::saturating_add(nRadX
, nRadY
) < 8192)
262 const double fRadX
= nRadX
;
263 const double fRadY
= nRadY
;
264 const double fCenterX
= aCenter
.X();
265 const double fCenterY
= aCenter
.Y();
266 double fStart
= ImplGetParameter( aCenter
, rStart
, fRadX
, fRadY
);
267 double fEnd
= ImplGetParameter( aCenter
, rEnd
, fRadX
, fRadY
);
268 double fDiff
= fEnd
- fStart
;
276 // Proportionally shrink number of points( fDiff / (2PI) );
277 nPoints
= std::max( static_cast<sal_uInt16
>( ( fDiff
* 0.1591549 ) * nPoints
), sal_uInt16(16) );
278 fStep
= fDiff
/ ( nPoints
- 1 );
280 if( PolyStyle::Pie
== eStyle
)
282 const Point
aCenter2( FRound( fCenterX
), FRound( fCenterY
) );
286 ImplInitSize((nPoints
+ 2));
287 mxPointAry
[0] = aCenter2
;
288 mxPointAry
[nEnd
] = aCenter2
;
292 ImplInitSize( ( PolyStyle::Chord
== eStyle
) ? ( nPoints
+ 1 ) : nPoints
);
297 for(; nStart
< nEnd
; nStart
++, fStart
+= fStep
)
299 Point
& rPt
= mxPointAry
[nStart
];
301 rPt
.setX( FRound( fCenterX
+ fRadX
* cos( fStart
) ) );
302 rPt
.setY( FRound( fCenterY
- fRadY
* sin( fStart
) ) );
305 if( PolyStyle::Chord
== eStyle
)
306 mxPointAry
[nPoints
] = mxPointAry
[0];
312 ImplPolygon::ImplPolygon( const Point
& rBezPt1
, const Point
& rCtrlPt1
,
313 const Point
& rBezPt2
, const Point
& rCtrlPt2
, sal_uInt16 nPoints
)
315 nPoints
= ( 0 == nPoints
) ? 25 : ( ( nPoints
< 2 ) ? 2 : nPoints
);
317 const double fInc
= 1.0 / ( nPoints
- 1 );
318 double fK_1
= 0.0, fK1_1
= 1.0;
319 double fK_2
, fK_3
, fK1_2
, fK1_3
;
320 const double fX0
= rBezPt1
.X();
321 const double fY0
= rBezPt1
.Y();
322 const double fX1
= 3.0 * rCtrlPt1
.X();
323 const double fY1
= 3.0 * rCtrlPt1
.Y();
324 const double fX2
= 3.0 * rCtrlPt2
.X();
325 const double fY2
= 3.0 * rCtrlPt2
.Y();
326 const double fX3
= rBezPt2
.X();
327 const double fY3
= rBezPt2
.Y();
329 ImplInitSize(nPoints
);
331 for( sal_uInt16 i
= 0; i
< nPoints
; i
++, fK_1
+= fInc
, fK1_1
-= fInc
)
333 Point
& rPt
= mxPointAry
[i
];
343 double fK12
= fK_1
* fK1_2
;
344 double fK21
= fK_2
* fK1_1
;
346 rPt
.setX( FRound( fK1_3
* fX0
+ fK12
* fX1
+ fK21
* fX2
+ fK_3
* fX3
) );
347 rPt
.setY( FRound( fK1_3
* fY0
+ fK12
* fY1
+ fK21
* fY2
+ fK_3
* fY3
) );
351 // constructor to convert from basegfx::B2DPolygon
352 // #i76891# Needed to change from adding all control points (even for unused
353 // edges) and creating a fixed-size Polygon in the first run to creating the
354 // minimal Polygon. This requires a temporary Point- and Flag-Array for curves
355 // and a memcopy at ImplPolygon creation, but contains no zero-controlpoints
356 // for straight edges.
357 ImplPolygon::ImplPolygon(const basegfx::B2DPolygon
& rPolygon
)
360 const bool bCurve(rPolygon
.areControlPointsUsed());
361 const bool bClosed(rPolygon
.isClosed());
362 sal_uInt32
nB2DLocalCount(rPolygon
.count());
366 // #127979# Reduce source point count hard to the limit of the tools Polygon
367 if(nB2DLocalCount
> ((0x0000ffff / 3) - 1))
369 OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
370 nB2DLocalCount
= ((0x0000ffff / 3) - 1);
373 // calculate target point count
374 const sal_uInt32
nLoopCount(bClosed
? nB2DLocalCount
: (nB2DLocalCount
? nB2DLocalCount
- 1 : 0 ));
378 // calculate maximum array size and allocate; prepare insert index
379 const sal_uInt32
nMaxTargetCount((nLoopCount
* 3) + 1);
380 ImplInitSize(static_cast< sal_uInt16
>(nMaxTargetCount
), true);
382 // prepare insert index and current point
383 sal_uInt32
nArrayInsert(0);
384 basegfx::B2DCubicBezier aBezier
;
385 aBezier
.setStartPoint(rPolygon
.getB2DPoint(0));
387 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
389 // add current point (always) and remember StartPointIndex for evtl. later corrections
390 const Point
aStartPoint(FRound(aBezier
.getStartPoint().getX()), FRound(aBezier
.getStartPoint().getY()));
391 const sal_uInt32
nStartPointIndex(nArrayInsert
);
392 mxPointAry
[nStartPointIndex
] = aStartPoint
;
393 mxFlagAry
[nStartPointIndex
] = PolyFlags::Normal
;
396 // prepare next segment
397 const sal_uInt32
nNextIndex((a
+ 1) % nB2DLocalCount
);
398 aBezier
.setEndPoint(rPolygon
.getB2DPoint(nNextIndex
));
399 aBezier
.setControlPointA(rPolygon
.getNextControlPoint(a
));
400 aBezier
.setControlPointB(rPolygon
.getPrevControlPoint(nNextIndex
));
402 if(aBezier
.isBezier())
404 // if one is used, add always two control points due to the old schema
405 mxPointAry
[nArrayInsert
] = Point(FRound(aBezier
.getControlPointA().getX()), FRound(aBezier
.getControlPointA().getY()));
406 mxFlagAry
[nArrayInsert
] = PolyFlags::Control
;
409 mxPointAry
[nArrayInsert
] = Point(FRound(aBezier
.getControlPointB().getX()), FRound(aBezier
.getControlPointB().getY()));
410 mxFlagAry
[nArrayInsert
] = PolyFlags::Control
;
414 // test continuity with previous control point to set flag value
415 if(aBezier
.getControlPointA() != aBezier
.getStartPoint() && (bClosed
|| a
))
417 const basegfx::B2VectorContinuity
eCont(rPolygon
.getContinuityInPoint(a
));
419 if(basegfx::B2VectorContinuity::C1
== eCont
)
421 mxFlagAry
[nStartPointIndex
] = PolyFlags::Smooth
;
423 else if(basegfx::B2VectorContinuity::C2
== eCont
)
425 mxFlagAry
[nStartPointIndex
] = PolyFlags::Symmetric
;
429 // prepare next polygon step
430 aBezier
.setStartPoint(aBezier
.getEndPoint());
435 // add first point again as closing point due to old definition
436 mxPointAry
[nArrayInsert
] = mxPointAry
[0];
437 mxFlagAry
[nArrayInsert
] = PolyFlags::Normal
;
442 // add last point as closing point
443 const basegfx::B2DPoint
aClosingPoint(rPolygon
.getB2DPoint(nB2DLocalCount
- 1));
444 const Point
aEnd(FRound(aClosingPoint
.getX()), FRound(aClosingPoint
.getY()));
445 mxPointAry
[nArrayInsert
] = aEnd
;
446 mxFlagAry
[nArrayInsert
] = PolyFlags::Normal
;
450 DBG_ASSERT(nArrayInsert
<= nMaxTargetCount
, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)");
452 if(nArrayInsert
!= nMaxTargetCount
)
454 ImplSetSize(static_cast< sal_uInt16
>(nArrayInsert
));
460 // #127979# Reduce source point count hard to the limit of the tools Polygon
461 if(nB2DLocalCount
> (0x0000ffff - 1))
463 OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
464 nB2DLocalCount
= (0x0000ffff - 1);
469 // point list creation
470 const sal_uInt32
nTargetCount(nB2DLocalCount
+ (bClosed
? 1 : 0));
471 ImplInitSize(static_cast< sal_uInt16
>(nTargetCount
));
472 sal_uInt16
nIndex(0);
474 for(sal_uInt32
a(0); a
< nB2DLocalCount
; a
++)
476 basegfx::B2DPoint
aB2DPoint(rPolygon
.getB2DPoint(a
));
477 Point
aPoint(FRound(aB2DPoint
.getX()), FRound(aB2DPoint
.getY()));
478 mxPointAry
[nIndex
++] = aPoint
;
483 // add first point as closing point
484 mxPointAry
[nIndex
] = mxPointAry
[0];
490 bool ImplPolygon::operator==( const ImplPolygon
& rCandidate
) const
492 return mnPoints
== rCandidate
.mnPoints
&&
493 mxFlagAry
.get() == rCandidate
.mxFlagAry
.get() &&
494 mxPointAry
.get() == rCandidate
.mxPointAry
.get();
497 void ImplPolygon::ImplInitSize(sal_uInt16 nInitSize
, bool bFlags
)
501 mxPointAry
.reset(new Point
[nInitSize
]);
506 mxFlagAry
.reset(new PolyFlags
[nInitSize
]);
507 memset(mxFlagAry
.get(), 0, nInitSize
);
510 mnPoints
= nInitSize
;
513 void ImplPolygon::ImplSetSize( sal_uInt16 nNewSize
, bool bResize
)
515 if( mnPoints
== nNewSize
)
518 std::unique_ptr
<Point
[]> xNewAry
;
522 const std::size_t nNewSz(static_cast<std::size_t>(nNewSize
)*sizeof(Point
));
523 xNewAry
.reset(new Point
[nNewSize
]);
527 // Copy the old points
528 if ( mnPoints
< nNewSize
)
530 // New points are already implicitly initialized to zero
531 const std::size_t nOldSz(mnPoints
* sizeof(Point
));
533 memcpy(xNewAry
.get(), mxPointAry
.get(), nOldSz
);
538 memcpy(xNewAry
.get(), mxPointAry
.get(), nNewSz
);
543 mxPointAry
= std::move(xNewAry
);
545 // take FlagArray into account, if applicable
548 std::unique_ptr
<PolyFlags
[]> xNewFlagAry
;
552 xNewFlagAry
.reset(new PolyFlags
[nNewSize
]);
556 // copy the old flags
557 if ( mnPoints
< nNewSize
)
559 // initialize new flags to zero
560 memset(xNewFlagAry
.get() + mnPoints
, 0, nNewSize
-mnPoints
);
561 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), mnPoints
);
564 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), nNewSize
);
568 mxFlagAry
= std::move(xNewFlagAry
);
574 bool ImplPolygon::ImplSplit( sal_uInt16 nPos
, sal_uInt16 nSpace
, ImplPolygon
const * pInitPoly
)
576 //Can't fit this in :-(, throw ?
577 if (mnPoints
+ nSpace
> USHRT_MAX
)
579 SAL_WARN("tools", "Polygon needs " << mnPoints
+ nSpace
<< " points, but only " << USHRT_MAX
<< " possible");
583 const sal_uInt16 nNewSize
= mnPoints
+ nSpace
;
584 const std::size_t nSpaceSize
= static_cast<std::size_t>(nSpace
) * sizeof(Point
);
586 if( nPos
>= mnPoints
)
588 // Append at the back
590 ImplSetSize( nNewSize
);
594 memcpy(mxPointAry
.get() + nPos
, pInitPoly
->mxPointAry
.get(), nSpaceSize
);
596 if (pInitPoly
->mxFlagAry
)
597 memcpy(mxFlagAry
.get() + nPos
, pInitPoly
->mxFlagAry
.get(), nSpace
);
602 const sal_uInt16 nSecPos
= nPos
+ nSpace
;
603 const sal_uInt16 nRest
= mnPoints
- nPos
;
605 std::unique_ptr
<Point
[]> xNewAry(new Point
[nNewSize
]);
606 memcpy(xNewAry
.get(), mxPointAry
.get(), nPos
* sizeof(Point
));
609 memcpy(xNewAry
.get() + nPos
, pInitPoly
->mxPointAry
.get(), nSpaceSize
);
611 memcpy(xNewAry
.get() + nSecPos
, mxPointAry
.get() + nPos
, nRest
* sizeof(Point
));
612 mxPointAry
= std::move(xNewAry
);
614 // consider FlagArray
617 std::unique_ptr
<PolyFlags
[]> xNewFlagAry(new PolyFlags
[nNewSize
]);
619 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), nPos
);
621 if (pInitPoly
&& pInitPoly
->mxFlagAry
)
622 memcpy(xNewFlagAry
.get() + nPos
, pInitPoly
->mxFlagAry
.get(), nSpace
);
624 memset(xNewFlagAry
.get() + nPos
, 0, nSpace
);
626 memcpy(xNewFlagAry
.get() + nSecPos
, mxFlagAry
.get() + nPos
, nRest
);
627 mxFlagAry
= std::move(xNewFlagAry
);
636 void ImplPolygon::ImplCreateFlagArray()
640 mxFlagAry
.reset(new PolyFlags
[mnPoints
]);
641 memset(mxFlagAry
.get(), 0, mnPoints
);
647 class ImplPointFilter
650 virtual void LastPoint() = 0;
651 virtual void Input( const Point
& rPoint
) = 0;
654 ~ImplPointFilter() {}
657 class ImplPolygonPointFilter
: public ImplPointFilter
662 explicit ImplPolygonPointFilter(sal_uInt16 nDestSize
)
668 virtual ~ImplPolygonPointFilter()
672 virtual void LastPoint() override
;
673 virtual void Input( const Point
& rPoint
) override
;
675 ImplPolygon
& get() { return maPoly
; }
680 void ImplPolygonPointFilter::Input( const Point
& rPoint
)
682 if ( !mnSize
|| (rPoint
!= maPoly
.mxPointAry
[mnSize
-1]) )
685 if ( mnSize
> maPoly
.mnPoints
)
686 maPoly
.ImplSetSize( mnSize
);
687 maPoly
.mxPointAry
[mnSize
-1] = rPoint
;
691 void ImplPolygonPointFilter::LastPoint()
693 if ( mnSize
< maPoly
.mnPoints
)
694 maPoly
.ImplSetSize( mnSize
);
699 class ImplEdgePointFilter
: public ImplPointFilter
703 ImplPointFilter
& mrNextFilter
;
704 const tools::Long mnLow
;
705 const tools::Long mnHigh
;
711 ImplEdgePointFilter( int nEdge
, tools::Long nLow
, tools::Long nHigh
,
712 ImplPointFilter
& rNextFilter
) :
713 mrNextFilter( rNextFilter
),
722 virtual ~ImplEdgePointFilter() {}
724 Point
EdgeSection( const Point
& rPoint
, int nEdge
) const;
725 int VisibleSide( const Point
& rPoint
) const;
726 bool IsPolygon() const
727 { return maFirstPoint
== maLastPoint
; }
729 virtual void Input( const Point
& rPoint
) override
;
730 virtual void LastPoint() override
;
735 inline int ImplEdgePointFilter::VisibleSide( const Point
& rPoint
) const
737 if ( mnEdge
& EDGE_HORZ
)
739 return rPoint
.X() < mnLow
? EDGE_LEFT
:
740 rPoint
.X() > mnHigh
? EDGE_RIGHT
: 0;
744 return rPoint
.Y() < mnLow
? EDGE_TOP
:
745 rPoint
.Y() > mnHigh
? EDGE_BOTTOM
: 0;
749 Point
ImplEdgePointFilter::EdgeSection( const Point
& rPoint
, int nEdge
) const
751 tools::Long lx
= maLastPoint
.X();
752 tools::Long ly
= maLastPoint
.Y();
753 tools::Long md
= rPoint
.X() - lx
;
754 tools::Long mn
= rPoint
.Y() - ly
;
758 if ( nEdge
& EDGE_VERT
)
760 nNewY
= (nEdge
== EDGE_TOP
) ? mnLow
: mnHigh
;
761 tools::Long dy
= nNewY
- ly
;
764 else if ( (LONG_MAX
/ std::abs(md
)) >= std::abs(dy
) )
765 nNewX
= (dy
* md
) / mn
+ lx
;
781 nNewX
= static_cast<tools::Long
>(ady
) + lx
;
786 nNewX
= (nEdge
== EDGE_LEFT
) ? mnLow
: mnHigh
;
787 tools::Long dx
= nNewX
- lx
;
790 else if ( (LONG_MAX
/ std::abs(mn
)) >= std::abs(dx
) )
791 nNewY
= (dx
* mn
) / md
+ ly
;
807 nNewY
= static_cast<tools::Long
>(adx
) + ly
;
811 return Point( nNewX
, nNewY
);
814 void ImplEdgePointFilter::Input( const Point
& rPoint
)
816 int nOutside
= VisibleSide( rPoint
);
820 maFirstPoint
= rPoint
;
823 mrNextFilter
.Input( rPoint
);
825 else if ( rPoint
== maLastPoint
)
827 else if ( !nOutside
)
830 mrNextFilter
.Input( EdgeSection( rPoint
, mnLastOutside
) );
831 mrNextFilter
.Input( rPoint
);
833 else if ( !mnLastOutside
)
834 mrNextFilter
.Input( EdgeSection( rPoint
, nOutside
) );
835 else if ( nOutside
!= mnLastOutside
)
837 mrNextFilter
.Input( EdgeSection( rPoint
, mnLastOutside
) );
838 mrNextFilter
.Input( EdgeSection( rPoint
, nOutside
) );
841 maLastPoint
= rPoint
;
842 mnLastOutside
= nOutside
;
845 void ImplEdgePointFilter::LastPoint()
849 int nOutside
= VisibleSide( maFirstPoint
);
851 if ( nOutside
!= mnLastOutside
)
852 Input( maFirstPoint
);
853 mrNextFilter
.LastPoint();
860 tools::Polygon
Polygon::SubdivideBezier( const tools::Polygon
& rPoly
)
862 tools::Polygon aPoly
;
864 // #100127# Use adaptive subdivide instead of fixed 25 segments
865 rPoly
.AdaptiveSubdivide( aPoly
);
870 Polygon::Polygon() : mpImplPolygon(ImplPolygon())
874 Polygon::Polygon( sal_uInt16 nSize
) : mpImplPolygon(ImplPolygon(nSize
))
878 Polygon::Polygon( sal_uInt16 nPoints
, const Point
* pPtAry
, const PolyFlags
* pFlagAry
) : mpImplPolygon(ImplPolygon(nPoints
, pPtAry
, pFlagAry
))
882 Polygon::Polygon( const tools::Polygon
& rPoly
) : mpImplPolygon(rPoly
.mpImplPolygon
)
886 Polygon::Polygon( tools::Polygon
&& rPoly
) noexcept
887 : mpImplPolygon(std::move(rPoly
.mpImplPolygon
))
891 Polygon::Polygon( const tools::Rectangle
& rRect
) : mpImplPolygon(ImplPolygon(rRect
))
895 Polygon::Polygon( const tools::Rectangle
& rRect
, sal_uInt32 nHorzRound
, sal_uInt32 nVertRound
)
896 : mpImplPolygon(ImplPolygon(rRect
, nHorzRound
, nVertRound
))
900 Polygon::Polygon( const Point
& rCenter
, tools::Long nRadX
, tools::Long nRadY
)
901 : mpImplPolygon(ImplPolygon(rCenter
, nRadX
, nRadY
))
905 Polygon::Polygon( const tools::Rectangle
& rBound
, const Point
& rStart
, const Point
& rEnd
,
906 PolyStyle eStyle
) : mpImplPolygon(ImplPolygon(rBound
, rStart
, rEnd
, eStyle
))
910 Polygon::Polygon( const Point
& rBezPt1
, const Point
& rCtrlPt1
,
911 const Point
& rBezPt2
, const Point
& rCtrlPt2
,
912 sal_uInt16 nPoints
) : mpImplPolygon(ImplPolygon(rBezPt1
, rCtrlPt1
, rBezPt2
, rCtrlPt2
, nPoints
))
920 Point
* Polygon::GetPointAry()
922 return mpImplPolygon
->mxPointAry
.get();
925 const Point
* Polygon::GetConstPointAry() const
927 return mpImplPolygon
->mxPointAry
.get();
930 const PolyFlags
* Polygon::GetConstFlagAry() const
932 return mpImplPolygon
->mxFlagAry
.get();
935 void Polygon::SetPoint( const Point
& rPt
, sal_uInt16 nPos
)
937 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
938 "Polygon::SetPoint(): nPos >= nPoints" );
940 mpImplPolygon
->mxPointAry
[nPos
] = rPt
;
943 void Polygon::SetFlags( sal_uInt16 nPos
, PolyFlags eFlags
)
945 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
946 "Polygon::SetFlags(): nPos >= nPoints" );
948 // we do only want to create the flag array if there
949 // is at least one flag different to PolyFlags::Normal
950 if ( eFlags
!= PolyFlags::Normal
)
952 mpImplPolygon
->ImplCreateFlagArray();
953 mpImplPolygon
->mxFlagAry
[ nPos
] = eFlags
;
957 const Point
& Polygon::GetPoint( sal_uInt16 nPos
) const
959 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
960 "Polygon::GetPoint(): nPos >= nPoints" );
962 return mpImplPolygon
->mxPointAry
[nPos
];
965 PolyFlags
Polygon::GetFlags( sal_uInt16 nPos
) const
967 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
968 "Polygon::GetFlags(): nPos >= nPoints" );
969 return mpImplPolygon
->mxFlagAry
970 ? mpImplPolygon
->mxFlagAry
[ nPos
]
974 bool Polygon::HasFlags() const
976 return bool(mpImplPolygon
->mxFlagAry
);
979 bool Polygon::IsRect() const
981 bool bIsRect
= false;
982 if (!mpImplPolygon
->mxFlagAry
)
984 if ( ( ( mpImplPolygon
->mnPoints
== 5 ) && ( mpImplPolygon
->mxPointAry
[ 0 ] == mpImplPolygon
->mxPointAry
[ 4 ] ) ) ||
985 ( mpImplPolygon
->mnPoints
== 4 ) )
987 if ( ( mpImplPolygon
->mxPointAry
[ 0 ].X() == mpImplPolygon
->mxPointAry
[ 3 ].X() ) &&
988 ( mpImplPolygon
->mxPointAry
[ 0 ].Y() == mpImplPolygon
->mxPointAry
[ 1 ].Y() ) &&
989 ( mpImplPolygon
->mxPointAry
[ 1 ].X() == mpImplPolygon
->mxPointAry
[ 2 ].X() ) &&
990 ( mpImplPolygon
->mxPointAry
[ 2 ].Y() == mpImplPolygon
->mxPointAry
[ 3 ].Y() ) )
997 void Polygon::SetSize( sal_uInt16 nNewSize
)
999 if( nNewSize
!= mpImplPolygon
->mnPoints
)
1001 mpImplPolygon
->ImplSetSize( nNewSize
);
1005 sal_uInt16
Polygon::GetSize() const
1007 return mpImplPolygon
->mnPoints
;
1010 void Polygon::Clear()
1012 mpImplPolygon
= ImplType(ImplPolygon());
1015 double Polygon::CalcDistance( sal_uInt16 nP1
, sal_uInt16 nP2
) const
1017 DBG_ASSERT( nP1
< mpImplPolygon
->mnPoints
,
1018 "Polygon::CalcDistance(): nPos1 >= nPoints" );
1019 DBG_ASSERT( nP2
< mpImplPolygon
->mnPoints
,
1020 "Polygon::CalcDistance(): nPos2 >= nPoints" );
1022 const Point
& rP1
= mpImplPolygon
->mxPointAry
[ nP1
];
1023 const Point
& rP2
= mpImplPolygon
->mxPointAry
[ nP2
];
1024 const double fDx
= rP2
.X() - rP1
.X();
1025 const double fDy
= rP2
.Y() - rP1
.Y();
1027 return sqrt( fDx
* fDx
+ fDy
* fDy
);
1030 void Polygon::Optimize( PolyOptimizeFlags nOptimizeFlags
)
1032 DBG_ASSERT( !mpImplPolygon
->mxFlagAry
, "Optimizing could fail with beziers!" );
1034 sal_uInt16 nSize
= mpImplPolygon
->mnPoints
;
1036 if( !(bool(nOptimizeFlags
) && nSize
) )
1039 if( nOptimizeFlags
& PolyOptimizeFlags::EDGES
)
1041 const tools::Rectangle
aBound( GetBoundRect() );
1042 const double fArea
= ( aBound
.GetWidth() + aBound
.GetHeight() ) * 0.5;
1043 const sal_uInt16 nPercent
= 50;
1045 Optimize( PolyOptimizeFlags::NO_SAME
);
1046 ImplReduceEdges( *this, fArea
, nPercent
);
1048 else if( nOptimizeFlags
& PolyOptimizeFlags::NO_SAME
)
1050 tools::Polygon aNewPoly
;
1051 const Point
& rFirst
= mpImplPolygon
->mxPointAry
[ 0 ];
1053 while( nSize
&& ( mpImplPolygon
->mxPointAry
[ nSize
- 1 ] == rFirst
) )
1058 sal_uInt16 nLast
= 0, nNewCount
= 1;
1060 aNewPoly
.SetSize( nSize
);
1061 aNewPoly
[ 0 ] = rFirst
;
1063 for( sal_uInt16 i
= 1; i
< nSize
; i
++ )
1065 if( mpImplPolygon
->mxPointAry
[ i
] != mpImplPolygon
->mxPointAry
[ nLast
])
1068 aNewPoly
[ nNewCount
++ ] = mpImplPolygon
->mxPointAry
[ i
];
1072 if( nNewCount
== 1 )
1075 aNewPoly
.SetSize( nNewCount
);
1081 nSize
= mpImplPolygon
->mnPoints
;
1085 if( ( nOptimizeFlags
& PolyOptimizeFlags::CLOSE
) &&
1086 ( mpImplPolygon
->mxPointAry
[ 0 ] != mpImplPolygon
->mxPointAry
[ nSize
- 1 ] ) )
1088 SetSize( mpImplPolygon
->mnPoints
+ 1 );
1089 mpImplPolygon
->mxPointAry
[ mpImplPolygon
->mnPoints
- 1 ] = mpImplPolygon
->mxPointAry
[ 0 ];
1095 /** Recursively subdivide cubic bezier curve via deCasteljau.
1098 Output iterator, where the subdivided polylines are written to.
1101 Squared difference of curve to a straight line
1104 Exactly four points, interpreted as support and control points of
1105 a cubic bezier curve. Must be in device coordinates, since stop
1106 criterion is based on the following assumption: the device has a
1107 finite resolution, it is thus sufficient to stop subdivision if the
1108 curve does not deviate more than one pixel from a straight line.
1111 static void ImplAdaptiveSubdivide( ::std::back_insert_iterator
< ::std::vector
< Point
> >& rPointIter
,
1112 const double old_d2
,
1115 const double P1x
, const double P1y
,
1116 const double P2x
, const double P2y
,
1117 const double P3x
, const double P3y
,
1118 const double P4x
, const double P4y
)
1120 // Hard limit on recursion depth, empiric number.
1121 enum {maxRecursionDepth
=128};
1123 // Perform bezier flatness test (lecture notes from R. Schaback,
1124 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
1126 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
1129 // What is calculated here is an upper bound to the distance from
1130 // a line through b_0 and b_3 (P1 and P4 in our notation) and the
1131 // curve. We can drop 0 and n from the running indices, since the
1132 // argument of max becomes zero for those cases.
1133 const double fJ1x( P2x
- P1x
- 1.0/3.0*(P4x
- P1x
) );
1134 const double fJ1y( P2y
- P1y
- 1.0/3.0*(P4y
- P1y
) );
1135 const double fJ2x( P3x
- P1x
- 2.0/3.0*(P4x
- P1x
) );
1136 const double fJ2y( P3y
- P1y
- 2.0/3.0*(P4y
- P1y
) );
1137 const double distance2( ::std::max( fJ1x
*fJ1x
+ fJ1y
*fJ1y
,
1138 fJ2x
*fJ2x
+ fJ2y
*fJ2y
) );
1140 // stop if error measure does not improve anymore. This is a
1141 // safety guard against floating point inaccuracies.
1142 // stop at recursion level 128. This is a safety guard against
1143 // floating point inaccuracies.
1144 // stop if distance from line is guaranteed to be bounded by d
1146 recursionDepth
< maxRecursionDepth
&&
1149 // deCasteljau bezier arc, split at t=0.5
1150 // Foley/vanDam, p. 508
1151 const double L1x( P1x
), L1y( P1y
);
1152 const double L2x( (P1x
+ P2x
)*0.5 ), L2y( (P1y
+ P2y
)*0.5 );
1153 const double Hx ( (P2x
+ P3x
)*0.5 ), Hy ( (P2y
+ P3y
)*0.5 );
1154 const double L3x( (L2x
+ Hx
)*0.5 ), L3y( (L2y
+ Hy
)*0.5 );
1155 const double R4x( P4x
), R4y( P4y
);
1156 const double R3x( (P3x
+ P4x
)*0.5 ), R3y( (P3y
+ P4y
)*0.5 );
1157 const double R2x( (Hx
+ R3x
)*0.5 ), R2y( (Hy
+ R3y
)*0.5 );
1158 const double R1x( (L3x
+ R2x
)*0.5 ), R1y( (L3y
+ R2y
)*0.5 );
1159 const double L4x( R1x
), L4y( R1y
);
1161 // subdivide further
1163 ImplAdaptiveSubdivide(rPointIter
, distance2
, recursionDepth
, d2
, L1x
, L1y
, L2x
, L2y
, L3x
, L3y
, L4x
, L4y
);
1164 ImplAdaptiveSubdivide(rPointIter
, distance2
, recursionDepth
, d2
, R1x
, R1y
, R2x
, R2y
, R3x
, R3y
, R4x
, R4y
);
1168 // requested resolution reached.
1169 // Add end points to output iterator.
1170 // order is preserved, since this is so to say depth first traversal.
1171 *rPointIter
++ = Point( FRound(P1x
), FRound(P1y
) );
1175 void Polygon::AdaptiveSubdivide( Polygon
& rResult
, const double d
) const
1177 if (!mpImplPolygon
->mxFlagAry
)
1184 sal_uInt16
nPts( GetSize() );
1185 ::std::vector
< Point
> aPoints
;
1186 aPoints
.reserve( nPts
);
1187 ::std::back_insert_iterator
< ::std::vector
< Point
> > aPointIter( aPoints
);
1191 if( ( i
+ 3 ) < nPts
)
1193 PolyFlags
P1( mpImplPolygon
->mxFlagAry
[ i
] );
1194 PolyFlags
P4( mpImplPolygon
->mxFlagAry
[ i
+ 3 ] );
1196 if( ( PolyFlags::Normal
== P1
|| PolyFlags::Smooth
== P1
|| PolyFlags::Symmetric
== P1
) &&
1197 ( PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[ i
+ 1 ] ) &&
1198 ( PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[ i
+ 2 ] ) &&
1199 ( PolyFlags::Normal
== P4
|| PolyFlags::Smooth
== P4
|| PolyFlags::Symmetric
== P4
) )
1201 ImplAdaptiveSubdivide( aPointIter
, d
*d
+1.0, 0, d
*d
,
1202 mpImplPolygon
->mxPointAry
[ i
].X(), mpImplPolygon
->mxPointAry
[ i
].Y(),
1203 mpImplPolygon
->mxPointAry
[ i
+1 ].X(), mpImplPolygon
->mxPointAry
[ i
+1 ].Y(),
1204 mpImplPolygon
->mxPointAry
[ i
+2 ].X(), mpImplPolygon
->mxPointAry
[ i
+2 ].Y(),
1205 mpImplPolygon
->mxPointAry
[ i
+3 ].X(), mpImplPolygon
->mxPointAry
[ i
+3 ].Y() );
1211 *aPointIter
++ = mpImplPolygon
->mxPointAry
[ i
++ ];
1213 if (aPoints
.size() >= SAL_MAX_UINT16
)
1215 OSL_ENSURE(aPoints
.size() < SAL_MAX_UINT16
,
1216 "Polygon::AdaptiveSubdivision created polygon too many points;"
1217 " using original polygon instead");
1219 // The resulting polygon can not hold all the points
1220 // that we have created so far. Stop the subdivision
1221 // and return a copy of the unmodified polygon.
1227 // fill result polygon
1228 rResult
= tools::Polygon( static_cast<sal_uInt16
>(aPoints
.size()) ); // ensure sufficient size for copy
1229 ::std::copy(aPoints
.begin(), aPoints
.end(), rResult
.mpImplPolygon
->mxPointAry
.get());
1241 explicit Vector2D( const Point
& rPoint
) : mfX( rPoint
.X() ), mfY( rPoint
.Y() ) {};
1242 double GetLength() const { return hypot( mfX
, mfY
); }
1243 Vector2D
& operator-=( const Vector2D
& rVec
) { mfX
-= rVec
.mfX
; mfY
-= rVec
.mfY
; return *this; }
1244 double Scalar( const Vector2D
& rVec
) const { return mfX
* rVec
.mfX
+ mfY
* rVec
.mfY
; }
1245 Vector2D
& Normalize();
1246 bool IsPositive( Vector2D
const & rVec
) const { return ( mfX
* rVec
.mfY
- mfY
* rVec
.mfX
) >= 0.0; }
1247 bool IsNegative( Vector2D
const & rVec
) const { return !IsPositive( rVec
); }
1252 Vector2D
& Vector2D::Normalize()
1254 double fLen
= Scalar( *this );
1256 if( ( fLen
!= 0.0 ) && ( fLen
!= 1.0 ) )
1258 fLen
= sqrt( fLen
);
1269 void Polygon::ImplReduceEdges( tools::Polygon
& rPoly
, const double& rArea
, sal_uInt16 nPercent
)
1271 const double fBound
= 2000.0 * ( 100 - nPercent
) * 0.01;
1272 sal_uInt16 nNumNoChange
= 0,
1275 while( nNumNoChange
< 2 )
1277 sal_uInt16 nPntCnt
= rPoly
.GetSize(), nNewPos
= 0;
1278 tools::Polygon
aNewPoly( nPntCnt
);
1279 bool bChangeInThisRun
= false;
1281 for( sal_uInt16 n
= 0; n
< nPntCnt
; n
++ )
1283 bool bDeletePoint
= false;
1285 if( ( n
+ nNumRuns
) % 2 )
1287 sal_uInt16 nIndPrev
= !n
? nPntCnt
- 1 : n
- 1;
1288 sal_uInt16 nIndPrevPrev
= !nIndPrev
? nPntCnt
- 1 : nIndPrev
- 1;
1289 sal_uInt16 nIndNext
= ( n
== nPntCnt
-1 ) ? 0 : n
+ 1;
1290 sal_uInt16 nIndNextNext
= ( nIndNext
== nPntCnt
- 1 ) ? 0 : nIndNext
+ 1;
1291 Vector2D
aVec1( rPoly
[ nIndPrev
] ); aVec1
-= Vector2D(rPoly
[ nIndPrevPrev
]);
1292 Vector2D
aVec2( rPoly
[ n
] ); aVec2
-= Vector2D(rPoly
[ nIndPrev
]);
1293 Vector2D
aVec3( rPoly
[ nIndNext
] ); aVec3
-= Vector2D(rPoly
[ n
]);
1294 Vector2D
aVec4( rPoly
[ nIndNextNext
] ); aVec4
-= Vector2D(rPoly
[ nIndNext
]);
1295 double fDist1
= aVec1
.GetLength(), fDist2
= aVec2
.GetLength();
1296 double fDist3
= aVec3
.GetLength(), fDist4
= aVec4
.GetLength();
1297 double fTurnB
= aVec2
.Normalize().Scalar( aVec3
.Normalize() );
1299 if( fabs( fTurnB
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnB
) > ( 1.0 - SMALL_DVALUE
) )
1300 bDeletePoint
= true;
1303 Vector2D
aVecB( rPoly
[ nIndNext
] );
1304 aVecB
-= Vector2D(rPoly
[ nIndPrev
] );
1305 double fDistB
= aVecB
.GetLength();
1306 double fLenWithB
= fDist2
+ fDist3
;
1307 double fLenFact
= ( fDistB
!= 0.0 ) ? fLenWithB
/ fDistB
: 1.0;
1308 double fTurnPrev
= aVec1
.Normalize().Scalar( aVec2
);
1309 double fTurnNext
= aVec3
.Scalar( aVec4
.Normalize() );
1310 double fGradPrev
, fGradB
, fGradNext
;
1312 if( fabs( fTurnPrev
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnPrev
) > ( 1.0 - SMALL_DVALUE
) )
1315 fGradPrev
= basegfx::rad2deg(acos(fTurnPrev
)) * (aVec1
.IsNegative(aVec2
) ? -1 : 1);
1317 fGradB
= basegfx::rad2deg(acos(fTurnB
)) * (aVec2
.IsNegative(aVec3
) ? -1 : 1);
1319 if( fabs( fTurnNext
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnNext
) > ( 1.0 - SMALL_DVALUE
) )
1322 fGradNext
= basegfx::rad2deg(acos(fTurnNext
)) * (aVec3
.IsNegative(aVec4
) ? -1 : 1);
1324 if( ( fGradPrev
> 0.0 && fGradB
< 0.0 && fGradNext
> 0.0 ) ||
1325 ( fGradPrev
< 0.0 && fGradB
> 0.0 && fGradNext
< 0.0 ) )
1327 if( ( fLenFact
< ( FSQRT2
+ SMALL_DVALUE
) ) &&
1328 ( ( ( fDist1
+ fDist4
) / ( fDist2
+ fDist3
) ) * 2000.0 ) > fBound
)
1330 bDeletePoint
= true;
1335 double fRelLen
= 1.0 - sqrt( fDistB
/ rArea
);
1339 else if( fRelLen
> 1.0 )
1342 if( ( std::round( ( fLenFact
- 1.0 ) * 1000000.0 ) < fBound
) &&
1343 ( fabs( fGradB
) <= ( fRelLen
* fBound
* 0.01 ) ) )
1345 bDeletePoint
= true;
1352 aNewPoly
[ nNewPos
++ ] = rPoly
[ n
];
1354 bChangeInThisRun
= true;
1357 if( bChangeInThisRun
&& nNewPos
)
1359 aNewPoly
.SetSize( nNewPos
);
1370 void Polygon::Move( tools::Long nHorzMove
, tools::Long nVertMove
)
1372 // This check is required for DrawEngine
1373 if ( !nHorzMove
&& !nVertMove
)
1377 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1378 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
1380 Point
& rPt
= mpImplPolygon
->mxPointAry
[i
];
1381 rPt
.AdjustX(nHorzMove
);
1382 rPt
.AdjustY(nVertMove
);
1386 void Polygon::Translate(const Point
& rTrans
)
1388 for ( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1389 mpImplPolygon
->mxPointAry
[ i
] += rTrans
;
1392 void Polygon::Scale( double fScaleX
, double fScaleY
)
1394 for ( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1396 Point
& rPnt
= mpImplPolygon
->mxPointAry
[i
];
1397 rPnt
.setX( static_cast<tools::Long
>( fScaleX
* rPnt
.X() ) );
1398 rPnt
.setY( static_cast<tools::Long
>( fScaleY
* rPnt
.Y() ) );
1402 void Polygon::Rotate( const Point
& rCenter
, Degree10 nAngle10
)
1404 nAngle10
%= Degree10(3600);
1408 const double fAngle
= F_PI1800
* nAngle10
.get();
1409 Rotate( rCenter
, sin( fAngle
), cos( fAngle
) );
1413 void Polygon::Rotate( const Point
& rCenter
, double fSin
, double fCos
)
1415 tools::Long nCenterX
= rCenter
.X();
1416 tools::Long nCenterY
= rCenter
.Y();
1418 for( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1420 Point
& rPt
= mpImplPolygon
->mxPointAry
[ i
];
1422 const tools::Long nX
= rPt
.X() - nCenterX
;
1423 const tools::Long nY
= rPt
.Y() - nCenterY
;
1424 rPt
.setX( FRound( fCos
* nX
+ fSin
* nY
) + nCenterX
);
1425 rPt
.setY( - FRound( fSin
* nX
- fCos
* nY
) + nCenterY
);
1429 void Polygon::Clip( const tools::Rectangle
& rRect
)
1431 // This algorithm is broken for bezier curves, they would get converted to lines.
1432 // Use PolyPolygon::Clip().
1433 assert( !HasFlags());
1435 // #105251# Justify rect before edge filtering
1436 tools::Rectangle
aJustifiedRect( rRect
);
1437 aJustifiedRect
.Justify();
1439 sal_uInt16 nSourceSize
= mpImplPolygon
->mnPoints
;
1440 ImplPolygonPointFilter
aPolygon( nSourceSize
);
1441 ImplEdgePointFilter
aHorzFilter( EDGE_HORZ
, aJustifiedRect
.Left(), aJustifiedRect
.Right(),
1443 ImplEdgePointFilter
aVertFilter( EDGE_VERT
, aJustifiedRect
.Top(), aJustifiedRect
.Bottom(),
1446 for ( sal_uInt16 i
= 0; i
< nSourceSize
; i
++ )
1447 aVertFilter
.Input( mpImplPolygon
->mxPointAry
[i
] );
1448 if ( aVertFilter
.IsPolygon() )
1449 aVertFilter
.LastPoint();
1451 aPolygon
.LastPoint();
1453 mpImplPolygon
= ImplType(aPolygon
.get());
1456 tools::Rectangle
Polygon::GetBoundRect() const
1458 // Removing the assert. Bezier curves have the attribute that each single
1459 // curve segment defined by four points can not exit the four-point polygon
1460 // defined by that points. This allows to say that the curve segment can also
1461 // never leave the Range of its defining points.
1462 // The result is that Polygon::GetBoundRect() may not create the minimal
1463 // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes),
1464 // but will always create a valid BoundRect, at least as long as this method
1465 // 'blindly' travels over all points, including control points.
1467 // DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" );
1469 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1471 return tools::Rectangle();
1473 tools::Long nXMin
, nXMax
, nYMin
, nYMax
;
1475 const Point
& pFirstPt
= mpImplPolygon
->mxPointAry
[0];
1476 nXMin
= nXMax
= pFirstPt
.X();
1477 nYMin
= nYMax
= pFirstPt
.Y();
1479 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
1481 const Point
& rPt
= mpImplPolygon
->mxPointAry
[i
];
1483 if (rPt
.X() < nXMin
)
1485 if (rPt
.X() > nXMax
)
1487 if (rPt
.Y() < nYMin
)
1489 if (rPt
.Y() > nYMax
)
1493 return tools::Rectangle( nXMin
, nYMin
, nXMax
, nYMax
);
1496 bool Polygon::IsInside( const Point
& rPoint
) const
1498 DBG_ASSERT( !mpImplPolygon
->mxFlagAry
, "IsInside could fail with beziers!" );
1500 const tools::Rectangle
aBound( GetBoundRect() );
1501 const Line
aLine( rPoint
, Point( aBound
.Right() + 100, rPoint
.Y() ) );
1502 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1503 sal_uInt16 nPCounter
= 0;
1505 if ( ( nCount
> 2 ) && aBound
.IsInside( rPoint
) )
1507 Point
aPt1( mpImplPolygon
->mxPointAry
[ 0 ] );
1508 Point aIntersection
;
1509 Point aLastIntersection
;
1511 while ( ( aPt1
== mpImplPolygon
->mxPointAry
[ nCount
- 1 ] ) && ( nCount
> 3 ) )
1514 for ( sal_uInt16 i
= 1; i
<= nCount
; i
++ )
1516 const Point
& rPt2
= mpImplPolygon
->mxPointAry
[ ( i
< nCount
) ? i
: 0 ];
1518 if ( aLine
.Intersection( Line( aPt1
, rPt2
), aIntersection
) )
1520 // This avoids insertion of double intersections
1523 if ( aIntersection
!= aLastIntersection
)
1525 aLastIntersection
= aIntersection
;
1531 aLastIntersection
= aIntersection
;
1540 // is inside, if number of intersection points is odd
1541 return ( ( nPCounter
& 1 ) == 1 );
1544 void Polygon::Insert( sal_uInt16 nPos
, const Point
& rPt
)
1546 if( nPos
>= mpImplPolygon
->mnPoints
)
1547 nPos
= mpImplPolygon
->mnPoints
;
1549 if (mpImplPolygon
->ImplSplit(nPos
, 1))
1550 mpImplPolygon
->mxPointAry
[ nPos
] = rPt
;
1553 void Polygon::Insert( sal_uInt16 nPos
, const tools::Polygon
& rPoly
)
1555 const sal_uInt16 nInsertCount
= rPoly
.mpImplPolygon
->mnPoints
;
1559 if( nPos
>= mpImplPolygon
->mnPoints
)
1560 nPos
= mpImplPolygon
->mnPoints
;
1562 if (rPoly
.mpImplPolygon
->mxFlagAry
)
1563 mpImplPolygon
->ImplCreateFlagArray();
1565 mpImplPolygon
->ImplSplit( nPos
, nInsertCount
, rPoly
.mpImplPolygon
.get() );
1569 Point
& Polygon::operator[]( sal_uInt16 nPos
)
1571 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
, "Polygon::[]: nPos >= nPoints" );
1573 return mpImplPolygon
->mxPointAry
[nPos
];
1576 tools::Polygon
& Polygon::operator=( const tools::Polygon
& rPoly
)
1578 mpImplPolygon
= rPoly
.mpImplPolygon
;
1582 tools::Polygon
& Polygon::operator=( tools::Polygon
&& rPoly
) noexcept
1584 mpImplPolygon
= std::move(rPoly
.mpImplPolygon
);
1588 bool Polygon::operator==( const tools::Polygon
& rPoly
) const
1590 return (mpImplPolygon
== rPoly
.mpImplPolygon
);
1593 bool Polygon::IsEqual( const tools::Polygon
& rPoly
) const
1595 bool bIsEqual
= true;
1597 if ( GetSize() != rPoly
.GetSize() )
1601 for ( i
= 0; i
< GetSize(); i
++ )
1603 if ( ( GetPoint( i
) != rPoly
.GetPoint( i
) ) ||
1604 ( GetFlags( i
) != rPoly
.GetFlags( i
) ) )
1614 SvStream
& ReadPolygon( SvStream
& rIStream
, tools::Polygon
& rPoly
)
1616 sal_uInt16
nPoints(0);
1618 // read all points and create array
1619 rIStream
.ReadUInt16( nPoints
);
1621 const size_t nMaxRecordsPossible
= rIStream
.remainingSize() / (2 * sizeof(sal_Int32
));
1622 if (nPoints
> nMaxRecordsPossible
)
1624 SAL_WARN("tools", "Polygon claims " << nPoints
<< " records, but only " << nMaxRecordsPossible
<< " possible");
1625 nPoints
= nMaxRecordsPossible
;
1628 rPoly
.mpImplPolygon
->ImplSetSize( nPoints
, false );
1630 for (sal_uInt16 i
= 0; i
< nPoints
; i
++)
1632 sal_Int32
nTmpX(0), nTmpY(0);
1633 rIStream
.ReadInt32(nTmpX
).ReadInt32(nTmpY
);
1634 rPoly
.mpImplPolygon
->mxPointAry
[i
].setX(nTmpX
);
1635 rPoly
.mpImplPolygon
->mxPointAry
[i
].setY(nTmpY
);
1641 SvStream
& WritePolygon( SvStream
& rOStream
, const tools::Polygon
& rPoly
)
1643 sal_uInt16 nPoints
= rPoly
.GetSize();
1645 // Write number of points
1646 rOStream
.WriteUInt16( nPoints
);
1648 for (sal_uInt16 i
= 0; i
< nPoints
; i
++)
1650 rOStream
.WriteInt32(rPoly
.mpImplPolygon
->mxPointAry
[i
].X())
1651 .WriteInt32(rPoly
.mpImplPolygon
->mxPointAry
[i
].Y());
1657 void Polygon::ImplRead( SvStream
& rIStream
)
1659 sal_uInt8
bHasPolyFlags(0);
1661 ReadPolygon( rIStream
, *this );
1662 rIStream
.ReadUChar( bHasPolyFlags
);
1664 if ( bHasPolyFlags
)
1666 mpImplPolygon
->mxFlagAry
.reset(new PolyFlags
[mpImplPolygon
->mnPoints
]);
1667 rIStream
.ReadBytes(mpImplPolygon
->mxFlagAry
.get(), mpImplPolygon
->mnPoints
);
1671 void Polygon::Read( SvStream
& rIStream
)
1673 VersionCompat
aCompat( rIStream
, StreamMode::READ
);
1675 ImplRead( rIStream
);
1678 void Polygon::ImplWrite( SvStream
& rOStream
) const
1680 bool bHasPolyFlags(mpImplPolygon
->mxFlagAry
);
1681 WritePolygon( rOStream
, *this );
1682 rOStream
.WriteBool(bHasPolyFlags
);
1684 if ( bHasPolyFlags
)
1685 rOStream
.WriteBytes(mpImplPolygon
->mxFlagAry
.get(), mpImplPolygon
->mnPoints
);
1688 void Polygon::Write( SvStream
& rOStream
) const
1690 VersionCompat
aCompat( rOStream
, StreamMode::WRITE
, 1 );
1692 ImplWrite( rOStream
);
1695 // #i74631#/#i115917# numerical correction method for B2DPolygon
1696 static void impCorrectContinuity(basegfx::B2DPolygon
& roPolygon
, sal_uInt32 nIndex
, PolyFlags nCFlag
)
1698 const sal_uInt32
nPointCount(roPolygon
.count());
1699 OSL_ENSURE(nIndex
< nPointCount
, "impCorrectContinuity: index access out of range (!)");
1701 if(nIndex
>= nPointCount
|| (PolyFlags::Smooth
!= nCFlag
&& PolyFlags::Symmetric
!= nCFlag
))
1704 if(!roPolygon
.isPrevControlPointUsed(nIndex
) || !roPolygon
.isNextControlPointUsed(nIndex
))
1707 // #i115917# Patch from osnola (modified, thanks for showing the problem)
1709 // The correction is needed because an integer polygon with control points
1710 // is converted to double precision. When C1 or C2 is used the involved vectors
1711 // may not have the same directions/lengths since these come from integer coordinates
1712 // and may have been snapped to different nearest integer coordinates. The snap error
1713 // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless,
1714 // it needs to be corrected to be able to detect the continuity in this points
1717 // We only have the integer data here (already in double precision form, but no mantissa
1718 // used), so the best correction is to use:
1720 // for C1: The longest vector since it potentially has best preserved the original vector.
1721 // Even better the sum of the vectors, weighted by their length. This gives the
1722 // normal vector addition to get the vector itself, lengths need to be preserved.
1723 // for C2: The mediated vector(s) since both should be the same, but mirrored
1725 // extract the point and vectors
1726 const basegfx::B2DPoint
aPoint(roPolygon
.getB2DPoint(nIndex
));
1727 const basegfx::B2DVector
aNext(roPolygon
.getNextControlPoint(nIndex
) - aPoint
);
1728 const basegfx::B2DVector
aPrev(aPoint
- roPolygon
.getPrevControlPoint(nIndex
));
1730 // calculate common direction vector, normalize
1731 const basegfx::B2DVector
aDirection(aNext
+ aPrev
);
1732 const double fDirectionLen
= aDirection
.getLength();
1733 if (fDirectionLen
== 0.0)
1736 if (PolyFlags::Smooth
== nCFlag
)
1738 // C1: apply common direction vector, preserve individual lengths
1739 const double fInvDirectionLen(1.0 / fDirectionLen
);
1740 roPolygon
.setNextControlPoint(nIndex
, basegfx::B2DPoint(aPoint
+ (aDirection
* (aNext
.getLength() * fInvDirectionLen
))));
1741 roPolygon
.setPrevControlPoint(nIndex
, basegfx::B2DPoint(aPoint
- (aDirection
* (aPrev
.getLength() * fInvDirectionLen
))));
1743 else // PolyFlags::Symmetric
1745 // C2: get mediated length. Taking half of the unnormalized direction would be
1746 // an approximation, but not correct.
1747 const double fMedLength((aNext
.getLength() + aPrev
.getLength()) * (0.5 / fDirectionLen
));
1748 const basegfx::B2DVector
aScaledDirection(aDirection
* fMedLength
);
1750 // Bring Direction to correct length and apply
1751 roPolygon
.setNextControlPoint(nIndex
, basegfx::B2DPoint(aPoint
+ aScaledDirection
));
1752 roPolygon
.setPrevControlPoint(nIndex
, basegfx::B2DPoint(aPoint
- aScaledDirection
));
1756 // convert to basegfx::B2DPolygon and return
1757 basegfx::B2DPolygon
Polygon::getB2DPolygon() const
1759 basegfx::B2DPolygon aRetval
;
1760 const sal_uInt16
nCount(mpImplPolygon
->mnPoints
);
1764 if (mpImplPolygon
->mxFlagAry
)
1766 // handling for curves. Add start point
1767 const Point
aStartPoint(mpImplPolygon
->mxPointAry
[0]);
1768 PolyFlags
nPointFlag(mpImplPolygon
->mxFlagAry
[0]);
1769 aRetval
.append(basegfx::B2DPoint(aStartPoint
.X(), aStartPoint
.Y()));
1770 Point aControlA
, aControlB
;
1772 for(sal_uInt16
a(1); a
< nCount
;)
1774 bool bControlA(false);
1775 bool bControlB(false);
1777 if(PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[a
])
1779 aControlA
= mpImplPolygon
->mxPointAry
[a
++];
1783 if(a
< nCount
&& PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[a
])
1785 aControlB
= mpImplPolygon
->mxPointAry
[a
++];
1789 // assert invalid polygons
1790 OSL_ENSURE(bControlA
== bControlB
, "Polygon::getB2DPolygon: Invalid source polygon (!)");
1794 const Point
aEndPoint(mpImplPolygon
->mxPointAry
[a
]);
1799 aRetval
.appendBezierSegment(
1800 basegfx::B2DPoint(aControlA
.X(), aControlA
.Y()),
1801 basegfx::B2DPoint(aControlB
.X(), aControlB
.Y()),
1802 basegfx::B2DPoint(aEndPoint
.X(), aEndPoint
.Y()));
1804 impCorrectContinuity(aRetval
, aRetval
.count() - 2, nPointFlag
);
1808 // no bezier edge, add end point
1809 aRetval
.append(basegfx::B2DPoint(aEndPoint
.X(), aEndPoint
.Y()));
1812 nPointFlag
= mpImplPolygon
->mxFlagAry
[a
++];
1816 // if exist, remove double first/last points, set closed and correct control points
1817 basegfx::utils::checkClosed(aRetval
);
1819 if(aRetval
.isClosed())
1821 // closeWithGeometryChange did really close, so last point(s) were removed.
1822 // Correct the continuity in the changed point
1823 impCorrectContinuity(aRetval
, 0, mpImplPolygon
->mxFlagAry
[0]);
1828 // extra handling for non-curves (most-used case) for speedup
1829 for(sal_uInt16
a(0); a
< nCount
; a
++)
1831 // get point and add
1832 const Point
aPoint(mpImplPolygon
->mxPointAry
[a
]);
1833 aRetval
.append(basegfx::B2DPoint(aPoint
.X(), aPoint
.Y()));
1837 basegfx::utils::checkClosed(aRetval
);
1844 Polygon::Polygon(const basegfx::B2DPolygon
& rPolygon
) : mpImplPolygon(ImplPolygon(rPolygon
))
1848 } // namespace tools
1850 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */