diff options
Diffstat (limited to 'basegfx/source/polygon/b2dpolygontools.cxx')
-rw-r--r-- | basegfx/source/polygon/b2dpolygontools.cxx | 307 |
1 files changed, 202 insertions, 105 deletions
diff --git a/basegfx/source/polygon/b2dpolygontools.cxx b/basegfx/source/polygon/b2dpolygontools.cxx index d497716e9c31..482d35d4df1d 100644 --- a/basegfx/source/polygon/b2dpolygontools.cxx +++ b/basegfx/source/polygon/b2dpolygontools.cxx @@ -18,6 +18,7 @@ */ #include <numeric> #include <algorithm> +#include <stack> #include <basegfx/numeric/ftools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> @@ -157,7 +158,7 @@ namespace basegfx::utils return rCandidate.getContinuityInPoint(nIndex); } - B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound) + B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound, int nRecurseLimit) { if(rCandidate.areControlPointsUsed()) { @@ -213,7 +214,7 @@ namespace basegfx::utils } // call adaptive subdivide which adds edges to aRetval accordingly - aBezier.adaptiveSubdivideByDistance(aRetval, fBound); + aBezier.adaptiveSubdivideByDistance(aRetval, fBound, nRecurseLimit); } else { @@ -1204,30 +1205,26 @@ namespace basegfx::utils void applyLineDashing( const B2DPolygon& rCandidate, const std::vector<double>& rDotDashArray, - std::function<void(const basegfx::B2DPolygon& rSnippet)> aLineTargetCallback, - std::function<void(const basegfx::B2DPolygon& rSnippet)> aGapTargetCallback, + const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rLineTargetCallback, + const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rGapTargetCallback, double fDotDashLength) { const sal_uInt32 nPointCount(rCandidate.count()); const sal_uInt32 nDotDashCount(rDotDashArray.size()); - if(fTools::lessOrEqual(fDotDashLength, 0.0)) + if(fDotDashLength <= 0.0) { fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0); } - if(fTools::lessOrEqual(fDotDashLength, 0.0) || (!aLineTargetCallback && !aGapTargetCallback) || !nPointCount) + if(fDotDashLength <= 0.0 || (!rLineTargetCallback && !rGapTargetCallback) || !nPointCount) { // parameters make no sense, just add source to targets - if(aLineTargetCallback) - { - aLineTargetCallback(rCandidate); - } + if (rLineTargetCallback) + rLineTargetCallback(rCandidate); - if(aGapTargetCallback) - { - aGapTargetCallback(rCandidate); - } + if (rGapTargetCallback) + rGapTargetCallback(rCandidate); return; } @@ -1255,7 +1252,6 @@ namespace basegfx::utils // to enlarge these as needed const double fFactor(fCandidateLength / fAllowedLength); std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; }); - fDotDashLength *= fFactor; } // prepare current edge's start @@ -1299,8 +1295,8 @@ namespace basegfx::utils while(fTools::less(fDotDashMovingLength, fEdgeLength)) { // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] - const bool bHandleLine(bIsLine && aLineTargetCallback); - const bool bHandleGap(!bIsLine && aGapTargetCallback); + const bool bHandleLine(bIsLine && rLineTargetCallback); + const bool bHandleGap(!bIsLine && rGapTargetCallback); if(bHandleLine || bHandleGap) { @@ -1317,12 +1313,12 @@ namespace basegfx::utils if(bHandleLine) { - implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine); + implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine); } if(bHandleGap) { - implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap); + implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap); } aSnippet.clear(); @@ -1335,8 +1331,8 @@ namespace basegfx::utils } // append closing snippet [fLastDotDashMovingLength, fEdgeLength] - const bool bHandleLine(bIsLine && aLineTargetCallback); - const bool bHandleGap(!bIsLine && aGapTargetCallback); + const bool bHandleLine(bIsLine && rLineTargetCallback); + const bool bHandleGap(!bIsLine && rGapTargetCallback); if(bHandleLine || bHandleGap) { @@ -1367,8 +1363,8 @@ namespace basegfx::utils while(fTools::less(fDotDashMovingLength, fEdgeLength)) { // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength] - const bool bHandleLine(bIsLine && aLineTargetCallback); - const bool bHandleGap(!bIsLine && aGapTargetCallback); + const bool bHandleLine(bIsLine && rLineTargetCallback); + const bool bHandleGap(!bIsLine && rGapTargetCallback); if(bHandleLine || bHandleGap) { @@ -1381,12 +1377,12 @@ namespace basegfx::utils if(bHandleLine) { - implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine); + implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine); } if(bHandleGap) { - implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap); + implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap); } aSnippet.clear(); @@ -1399,8 +1395,8 @@ namespace basegfx::utils } // append snippet [fLastDotDashMovingLength, fEdgeLength] - const bool bHandleLine(bIsLine && aLineTargetCallback); - const bool bHandleGap(!bIsLine && aGapTargetCallback); + const bool bHandleLine(bIsLine && rLineTargetCallback); + const bool bHandleGap(!bIsLine && rGapTargetCallback); if(bHandleLine || bHandleGap) { @@ -1424,28 +1420,28 @@ namespace basegfx::utils // append last intermediate results (if exists) if(aSnippet.count()) { - const bool bHandleLine(bIsLine && aLineTargetCallback); - const bool bHandleGap(!bIsLine && aGapTargetCallback); + const bool bHandleLine(bIsLine && rLineTargetCallback); + const bool bHandleGap(!bIsLine && rGapTargetCallback); if(bHandleLine) { - implHandleSnippet(aSnippet, aLineTargetCallback, aFirstLine, aLastLine); + implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine); } if(bHandleGap) { - implHandleSnippet(aSnippet, aGapTargetCallback, aFirstGap, aLastGap); + implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap); } } - if(bIsClosed && aLineTargetCallback) + if(bIsClosed && rLineTargetCallback) { - implHandleFirstLast(aLineTargetCallback, aFirstLine, aLastLine); + implHandleFirstLast(rLineTargetCallback, aFirstLine, aLastLine); } - if(bIsClosed && aGapTargetCallback) + if(bIsClosed && rGapTargetCallback) { - implHandleFirstLast(aGapTargetCallback, aFirstGap, aLastGap); + implHandleFirstLast(rGapTargetCallback, aFirstGap, aLastGap); } } @@ -1530,36 +1526,36 @@ namespace basegfx::utils const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision()); const sal_uInt32 nPointCount(aCandidate.count()); - if(nPointCount) - { - const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1); - B2DPoint aCurrent(aCandidate.getB2DPoint(0)); + if(!nPointCount) + return false; - if(nEdgeCount) - { - // edges - for(sal_uInt32 a(0); a < nEdgeCount; a++) - { - const sal_uInt32 nNextIndex((a + 1) % nPointCount); - const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1); + B2DPoint aCurrent(aCandidate.getB2DPoint(0)); - if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance)) - { - return true; - } - - // prepare next step - aCurrent = aNext; - } - } - else + if(nEdgeCount) + { + // edges + for(sal_uInt32 a(0); a < nEdgeCount; a++) { - // no edges, but points -> not closed. Check single point. Just - // use isInEpsilonRange with twice the same point, it handles those well - if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance)) + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex)); + + if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance)) { return true; } + + // prepare next step + aCurrent = aNext; + } + } + else + { + // no edges, but points -> not closed. Check single point. Just + // use isInEpsilonRange with twice the same point, it handles those well + if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance)) + { + return true; } } @@ -1933,29 +1929,29 @@ namespace basegfx::utils OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)"); const sal_uInt32 nPointCount(rCandidate.count()); - if(nPointCount > 2) + if(nPointCount <= 2) + return false; + + B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1)); + B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nPointCount; a++) { - B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1)); - B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); + const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); + const B2DVector aPrevVec(aPrevPoint - aCurrPoint); + const B2DVector aNextVec(aNextPoint - aCurrPoint); + const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec)); - for(sal_uInt32 a(0); a < nPointCount; a++) + if(aOrientation == B2VectorOrientation::Neutral) { - const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); - const B2DVector aPrevVec(aPrevPoint - aCurrPoint); - const B2DVector aNextVec(aNextPoint - aCurrPoint); - const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec)); - - if(aOrientation == B2VectorOrientation::Neutral) - { - // current has neutral orientation - return true; - } - else - { - // prepare next - aPrevPoint = aCurrPoint; - aCurrPoint = aNextPoint; - } + // current has neutral orientation + return true; + } + else + { + // prepare next + aPrevPoint = aCurrPoint; + aCurrPoint = aNextPoint; } } @@ -2015,37 +2011,37 @@ namespace basegfx::utils OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)"); const sal_uInt32 nPointCount(rCandidate.count()); - if(nPointCount > 2) + if(nPointCount <= 2) + return true; + + const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1)); + B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); + B2DVector aCurrVec(aPrevPoint - aCurrPoint); + B2VectorOrientation aOrientation(B2VectorOrientation::Neutral); + + for(sal_uInt32 a(0); a < nPointCount; a++) { - const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1)); - B2DPoint aCurrPoint(rCandidate.getB2DPoint(0)); - B2DVector aCurrVec(aPrevPoint - aCurrPoint); - B2VectorOrientation aOrientation(B2VectorOrientation::Neutral); + const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); + const B2DVector aNextVec(aNextPoint - aCurrPoint); + const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec)); - for(sal_uInt32 a(0); a < nPointCount; a++) + if(aOrientation == B2VectorOrientation::Neutral) { - const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount)); - const B2DVector aNextVec(aNextPoint - aCurrPoint); - const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec)); - - if(aOrientation == B2VectorOrientation::Neutral) - { - // set start value, maybe neutral again - aOrientation = aCurrentOrientation; - } - else + // set start value, maybe neutral again + aOrientation = aCurrentOrientation; + } + else + { + if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation) { - if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation) - { - // different orientations found, that's it - return false; - } + // different orientations found, that's it + return false; } - - // prepare next - aCurrPoint = aNextPoint; - aCurrVec = -aNextVec; } + + // prepare next + aCurrPoint = aNextPoint; + aCurrVec = -aNextVec; } return true; @@ -2823,7 +2819,7 @@ namespace basegfx::utils { OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)"); - if(fTools::lessOrEqual(t, 0.0) || rOld1 == rOld2) + if(t <= 0.0 || rOld1 == rOld2) { return rOld1; } @@ -3330,6 +3326,8 @@ namespace basegfx::utils if(0 != nCount) { + aRetval.reserve(nCount); + const css::awt::Point* pPointSequence = rPointSequenceSource.getConstArray(); const css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceSource.getConstArray(); @@ -3573,6 +3571,105 @@ namespace basegfx::utils } } +B2DPolygon createSimplifiedPolygon(const B2DPolygon& rCandidate, const double fTolerance) +{ + const sal_uInt32 nPointCount(rCandidate.count()); + if (nPointCount < 3 || rCandidate.areControlPointsUsed()) + return rCandidate; + + // The solution does not use recursion directly, since this could lead to a stack overflow if + // nPointCount is very large. Instead, an own stack is used. This does not contain points, but + // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note + // whether a point of rCandidate belongs to the output polygon or not. + std::vector<bool> bIsKeptVec(nPointCount, false); + bIsKeptVec[0] = true; + bIsKeptVec[nPointCount - 1] = true; + sal_uInt32 nKept = 2; + std::stack<std::pair<sal_uInt32, sal_uInt32>> aUnfinishedRangesStack; + + // The RDP algorithm draws a straight line from the first point to the last point of a range. + // Then, from the inner points of the range, the point that has the largest distance to the line + // is determined. If the distance is greater than the tolerance, this point is kept and the lower + // and upper sub-segments of the range are treated in the same way. If the distance is smaller + // than the tolerance, none of the inner points will be kept. + sal_uInt32 nLowIndex = 0; + sal_uInt32 nHighIndex = nPointCount - 1; + bool bContinue = true; + do + { + bContinue = false; + if (nHighIndex - nLowIndex < 2) // maximal two points, range is finished. + { + // continue with sibling upper range if any + if (!aUnfinishedRangesStack.empty()) + { + std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top(); + aUnfinishedRangesStack.pop(); + nLowIndex = aIndexPair.first; + nHighIndex = aIndexPair.second; + bContinue = true; + } + } + else // the range needs examine the max distance + { + // Get maximal distance of inner points of the range to the straight line from start to + // end point of the range. + // For calculation of the distance we use the Hesse normal form of the straight line. + B2DPoint aLowPoint = rCandidate.getB2DPoint(nLowIndex); + B2DPoint aHighPoint = rCandidate.getB2DPoint(nHighIndex); + B2DVector aNormalVec + = basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint) - B2DVector(aLowPoint)); + double fLineOriginDistance = aNormalVec.scalar(B2DVector(aLowPoint)); + double fMaxDist = 0; + sal_uInt32 nMaxPointIndex = nLowIndex; + for (sal_uInt32 i = nLowIndex + 1; i < nHighIndex; i++) + { + double fDistance + = aNormalVec.scalar(B2DVector(rCandidate.getB2DPoint(i))) - fLineOriginDistance; + if (std::fabs(fDistance) > fMaxDist) + { + fMaxDist = std::fabs(fDistance); + nMaxPointIndex = i; + } + } + + if (fMaxDist >= fTolerance) + { + // We need to divide the current range into two sub ranges. + bIsKeptVec[nMaxPointIndex] = true; + nKept++; + // continue with lower sub range and push upper sub range to stack + aUnfinishedRangesStack.push(std::make_pair(nMaxPointIndex, nHighIndex)); + nHighIndex = nMaxPointIndex; + bContinue = true; + } + else + { + // We do not keep any of the inner points of the current range. + // continue with sibling upper range if any + if (!aUnfinishedRangesStack.empty()) + { + std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top(); + aUnfinishedRangesStack.pop(); + nLowIndex = aIndexPair.first; + nHighIndex = aIndexPair.second; + bContinue = true; + } + } + } + } while (bContinue); + + // Put all points which are marked as "keep" into the result polygon + B2DPolygon aResultPolygon; + aResultPolygon.reserve(nKept); + for (sal_uInt32 i = 0; i < nPointCount; i++) + { + if (bIsKeptVec[i]) + aResultPolygon.append(rCandidate.getB2DPoint(i)); + } + return aResultPolygon; +} + } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |