/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Best approximation of circle with Bazier curve. // Euclidean error between two curves is 0.00735
constexpr double fKappa = 4.0 / 3.0 * (M_SQRT2 - 1);
constexpr int EDGE_LEFT = 1;
constexpr int EDGE_TOP = 2;
constexpr int EDGE_RIGHT = 4;
constexpr int EDGE_BOTTOM = 8;
constexpr int EDGE_HORZ = EDGE_RIGHT | EDGE_LEFT;
constexpr int EDGE_VERT = EDGE_TOP | EDGE_BOTTOM;
constexpr double SMALL_DVALUE = 0.0000001; #define FSQRT2 1.4142135623730950488016887242097
// constructor to convert from basegfx::B2DPolygon // #i76891# Needed to change from adding all control points (even for unused // edges) and creating a fixed-size Polygon in the first run to creating the // minimal Polygon. This requires a temporary Point- and Flag-Array for curves // and a memcopy at ImplPolygon creation, but contains no zero-controlpoints // for straight edges.
ImplPolygon::ImplPolygon(const basegfx::B2DPolygon& rPolygon)
: mnPoints(0)
{ constbool bCurve(rPolygon.areControlPointsUsed()); constbool bClosed(rPolygon.isClosed());
sal_uInt32 nB2DLocalCount(rPolygon.count());
if(bCurve)
{ // #127979# Reduce source point count hard to the limit of the tools Polygon if(nB2DLocalCount > ((0x0000ffff / 3) - 1))
{
OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
nB2DLocalCount = ((0x0000ffff / 3) - 1);
}
if(nLoopCount)
{ // calculate maximum array size and allocate; prepare insert index const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
ImplInitSize(static_cast< sal_uInt16 >(nMaxTargetCount), true);
// prepare insert index and current point
sal_uInt32 nArrayInsert(0);
basegfx::B2DCubicBezier aBezier;
aBezier.setStartPoint(rPolygon.getB2DPoint(0));
for(sal_uInt32 a(0); a < nLoopCount; a++)
{ // add current point (always) and remember StartPointIndex for evtl. later corrections const Point aStartPoint(
basegfx::fround<tools::Long>(aBezier.getStartPoint().getX()),
basegfx::fround<tools::Long>(aBezier.getStartPoint().getY())); const sal_uInt32 nStartPointIndex(nArrayInsert);
mxPointAry[nStartPointIndex] = aStartPoint;
mxFlagAry[nStartPointIndex] = PolyFlags::Normal;
nArrayInsert++;
if(aBezier.isBezier())
{ // if one is used, add always two control points due to the old schema
mxPointAry[nArrayInsert] = Point(basegfx::fround<tools::Long>(aBezier.getControlPointA().getX()),
basegfx::fround<tools::Long>(aBezier.getControlPointA().getY()));
mxFlagAry[nArrayInsert] = PolyFlags::Control;
nArrayInsert++;
// test continuity with previous control point to set flag value if(aBezier.getControlPointA() != aBezier.getStartPoint() && (bClosed || a))
{ const basegfx::B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
// prepare next polygon step
aBezier.setStartPoint(aBezier.getEndPoint());
}
if(bClosed)
{ // add first point again as closing point due to old definition
mxPointAry[nArrayInsert] = mxPointAry[0];
mxFlagAry[nArrayInsert] = PolyFlags::Normal;
nArrayInsert++;
} else
{ // add last point as closing point const basegfx::B2DPoint aClosingPoint(rPolygon.getB2DPoint(nB2DLocalCount - 1)); const Point aEnd(basegfx::fround<tools::Long>(aClosingPoint.getX()),
basegfx::fround<tools::Long>(aClosingPoint.getY()));
mxPointAry[nArrayInsert] = aEnd;
mxFlagAry[nArrayInsert] = PolyFlags::Normal;
nArrayInsert++;
}
DBG_ASSERT(nArrayInsert <= nMaxTargetCount, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)");
if(nArrayInsert != nMaxTargetCount)
{
ImplSetSize(static_cast< sal_uInt16 >(nArrayInsert));
}
}
} else
{ // #127979# Reduce source point count hard to the limit of the tools Polygon if(nB2DLocalCount > (0x0000ffff - 1))
{
OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
nB2DLocalCount = (0x0000ffff - 1);
}
if(nB2DLocalCount)
{ // point list creation const sal_uInt32 nTargetCount(nB2DLocalCount + (bClosed ? 1 : 0));
ImplInitSize(static_cast< sal_uInt16 >(nTargetCount));
sal_uInt16 nIndex(0);
for(sal_uInt32 a(0); a < nB2DLocalCount; a++)
{
basegfx::B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
Point aPoint(basegfx::fround<tools::Long>(aB2DPoint.getX()),
basegfx::fround<tools::Long>(aB2DPoint.getY()));
mxPointAry[nIndex++] = aPoint;
}
if(bClosed)
{ // add first point as closing point
mxPointAry[nIndex] = mxPointAry[0];
}
}
}
}
if (nNewSize)
{ const std::size_t nNewSz(static_cast<std::size_t>(nNewSize)*sizeof(Point));
xNewAry.reset(new Point[nNewSize]);
if ( bResize )
{ // Copy the old points if ( mnPoints < nNewSize )
{ // New points are already implicitly initialized to zero const std::size_t nOldSz(mnPoints * sizeof(Point)); if (mxPointAry)
memcpy(xNewAry.get(), mxPointAry.get(), nOldSz);
} else
{ if (mxPointAry)
memcpy(xNewAry.get(), mxPointAry.get(), nNewSz);
}
}
}
mxPointAry = std::move(xNewAry);
// take FlagArray into account, if applicable if( mxFlagAry )
{
std::unique_ptr<PolyFlags[]> xNewFlagAry;
class ImplEdgePointFilter : public ImplPointFilter
{
Point maFirstPoint;
Point maLastPoint;
ImplPointFilter& mrNextFilter; const tools::Long mnLow; const tools::Long mnHigh; constint mnEdge; int mnLastOutside; bool mbFirst;
// we do only want to create the flag array if there // is at least one flag different to PolyFlags::Normal if ( eFlags != PolyFlags::Normal )
{
mpImplPolygon->ImplCreateFlagArray();
mpImplPolygon->mxFlagAry[ nPos ] = eFlags;
}
}
/** Recursively subdivide cubic bezier curve via deCasteljau.
@param rPoints Output vector, where the subdivided polylines are written to.
@param d Squared difference of curve to a straight line
@param P* Exactly four points, interpreted as support and control points of a cubic bezier curve. Must be in device coordinates, since stop criterion is based on the following assumption: the device has a finite resolution, it is thus sufficient to stop subdivision if the curve does not deviate more than one pixel from a straight line.
// What is calculated here is an upper bound to the distance from // a line through b_0 and b_3 (P1 and P4 in our notation) and the // curve. We can drop 0 and n from the running indices, since the // argument of max becomes zero for those cases. constdouble fJ1x( P2x - P1x - 1.0/3.0*(P4x - P1x) ); constdouble fJ1y( P2y - P1y - 1.0/3.0*(P4y - P1y) ); constdouble fJ2x( P3x - P1x - 2.0/3.0*(P4x - P1x) ); constdouble fJ2y( P3y - P1y - 2.0/3.0*(P4y - P1y) ); constdouble distance2( ::std::max( fJ1x*fJ1x + fJ1y*fJ1y,
fJ2x*fJ2x + fJ2y*fJ2y) );
// stop if error measure does not improve anymore. This is a // safety guard against floating point inaccuracies. // stop at recursion level 128. This is a safety guard against // floating point inaccuracies. // stop if distance from line is guaranteed to be bounded by d if( old_d2 > d2 &&
recursionDepth < maxRecursionDepth &&
distance2 >= d2 &&
rPoints.size() < SAL_MAX_UINT16 )
{ // deCasteljau bezier arc, split at t=0.5 // Foley/vanDam, p. 508 constdouble L1x( P1x ), L1y( P1y ); constdouble L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 ); constdouble Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 ); constdouble L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 ); constdouble R4x( P4x ), R4y( P4y ); constdouble R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 ); constdouble R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 ); constdouble R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 ); constdouble L4x( R1x ), L4y( R1y );
// subdivide further
++recursionDepth;
ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y);
ImplAdaptiveSubdivide(rPoints, distance2, recursionDepth, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y);
} else
{ // requested resolution reached. // Add end points to output iterator. // order is preserved, since this is so to say depth first traversal.
rPoints.push_back(Point(basegfx::fround<tools::Long>(P1x), basegfx::fround<tools::Long>(P1y)));
}
}
if (aPoints.size() >= SAL_MAX_UINT16)
{
OSL_ENSURE(aPoints.size() < SAL_MAX_UINT16, "Polygon::AdaptiveSubdivision created polygon too many points;" " using original polygon instead");
// The resulting polygon can not hold all the points // that we have created so far. Stop the subdivision // and return a copy of the unmodified polygon.
rResult = *this; return;
}
}
// fill result polygon
rResult = tools::Polygon( static_cast<sal_uInt16>(aPoints.size()) ); // ensure sufficient size for copy
::std::copy(aPoints.begin(), aPoints.end(), rResult.mpImplPolygon->mxPointAry.get());
}
}
void Polygon::Clip( const tools::Rectangle& rRect )
{ // This algorithm is broken for bezier curves, they would get converted to lines. // Use PolyPolygon::Clip().
assert( !HasFlags());
// #105251# Normalize rect before edge filtering
tools::Rectangle aJustifiedRect( rRect );
aJustifiedRect.Normalize();
for ( sal_uInt16 i = 0; i < nSourceSize; i++ )
aVertFilter.Input( mpImplPolygon->mxPointAry[i] ); if ( aVertFilter.IsPolygon() )
aVertFilter.LastPoint(); else
aPolygon.LastPoint();
mpImplPolygon = ImplType(aPolygon.get());
}
tools::Rectangle Polygon::GetBoundRect() const
{ // Removing the assert. Bezier curves have the attribute that each single // curve segment defined by four points can not exit the four-point polygon // defined by that points. This allows to say that the curve segment can also // never leave the Range of its defining points. // The result is that Polygon::GetBoundRect() may not create the minimal // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes), // but will always create a valid BoundRect, at least as long as this method // 'blindly' travels over all points, including control points.
// DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" );
// Write number of points
rOStream.WriteUInt16( nPoints );
for (sal_uInt16 i = 0; i < nPoints; i++)
{
rOStream.WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].X())
.WriteInt32(rPoly.mpImplPolygon->mxPointAry[i].Y());
}
// #i115917# Patch from osnola (modified, thanks for showing the problem)
// The correction is needed because an integer polygon with control points // is converted to double precision. When C1 or C2 is used the involved vectors // may not have the same directions/lengths since these come from integer coordinates // and may have been snapped to different nearest integer coordinates. The snap error // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless, // it needs to be corrected to be able to detect the continuity in this points // correctly.
// We only have the integer data here (already in double precision form, but no mantissa // used), so the best correction is to use:
// for C1: The longest vector since it potentially has best preserved the original vector. // Even better the sum of the vectors, weighted by their length. This gives the // normal vector addition to get the vector itself, lengths need to be preserved. // for C2: The mediated vector(s) since both should be the same, but mirrored
// extract the point and vectors const basegfx::B2DPoint aPoint(roPolygon.getB2DPoint(nIndex)); const basegfx::B2DVector aNext(roPolygon.getNextControlPoint(nIndex) - aPoint); const basegfx::B2DVector aPrev(aPoint - roPolygon.getPrevControlPoint(nIndex));
// calculate common direction vector, normalize const basegfx::B2DVector aDirection(aNext + aPrev); constdouble fDirectionLen = aDirection.getLength(); if (fDirectionLen == 0.0) return;
if (PolyFlags::Smooth == nCFlag)
{ // C1: apply common direction vector, preserve individual lengths constdouble fInvDirectionLen(1.0 / fDirectionLen);
roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + (aDirection * (aNext.getLength() * fInvDirectionLen))));
roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - (aDirection * (aPrev.getLength() * fInvDirectionLen))));
} else// PolyFlags::Symmetric
{ // C2: get mediated length. Taking half of the unnormalized direction would be // an approximation, but not correct. constdouble fMedLength((aNext.getLength() + aPrev.getLength()) * (0.5 / fDirectionLen)); const basegfx::B2DVector aScaledDirection(aDirection * fMedLength);
// Bring Direction to correct length and apply
roPolygon.setNextControlPoint(nIndex, basegfx::B2DPoint(aPoint + aScaledDirection));
roPolygon.setPrevControlPoint(nIndex, basegfx::B2DPoint(aPoint - aScaledDirection));
}
}
// convert to basegfx::B2DPolygon and return
basegfx::B2DPolygon Polygon::getB2DPolygon() const
{
basegfx::B2DPolygon aRetval; const sal_uInt16 nCount(mpImplPolygon->mnPoints);
if (nCount)
{ if (mpImplPolygon->mxFlagAry)
{ // handling for curves. Add start point const Point aStartPoint(mpImplPolygon->mxPointAry[0]);
PolyFlags nPointFlag(mpImplPolygon->mxFlagAry[0]);
aRetval.append(basegfx::B2DPoint(aStartPoint.X(), aStartPoint.Y()));
Point aControlA, aControlB;
for(sal_uInt16 a(1); a < nCount;)
{ bool bControlA(false); bool bControlB(false);
impCorrectContinuity(aRetval, aRetval.count() - 2, nPointFlag);
} else
{ // no bezier edge, add end point
aRetval.append(basegfx::B2DPoint(aEndPoint.X(), aEndPoint.Y()));
}
nPointFlag = mpImplPolygon->mxFlagAry[a++];
}
}
// if exist, remove double first/last points, set closed and correct control points
basegfx::utils::checkClosed(aRetval);
if(aRetval.isClosed())
{ // closeWithGeometryChange did really close, so last point(s) were removed. // Correct the continuity in the changed point
impCorrectContinuity(aRetval, 0, mpImplPolygon->mxFlagAry[0]);
}
} else
{ // extra handling for non-curves (most-used case) for speedup for(sal_uInt16 a(0); a < nCount; a++)
{ // get point and add const Point aPoint(mpImplPolygon->mxPointAry[a]);
aRetval.append(basegfx::B2DPoint(aPoint.X(), aPoint.Y()));
}
// set closed flag
basegfx::utils::checkClosed(aRetval);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.