Files
core/svx/source/xoutdev/_xpoly.cxx
Sarper Akdemir dc468362e4 tdf#94699: freehand drawing tool, ignore extremely offsetted control points
Now with the /org.openoffice.Office.Common/Misc/FreehandThresholdPixels config
option, when using low threshold pixels like 1px it is more apparent that
XPolygon::PointsToBezier will sometimes make the curve shoot off.

Try to counteract by ignoring control points offsetted more then the segment
length.

Better fix would be changing the PointsToBezier algorithm, but this seems to
hold OK for now.

Change-Id: I7ab0a67a33e25874ff55af932b3e087bbaad98fb
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181841
Tested-by: Jenkins
Reviewed-by: Sarper Akdemir <sarper.akdemir@allotropia.de>
2025-03-31 09:33:50 +02:00

958 lines
30 KiB
C++

/* -*- 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 .
*/
#include <sal/config.h>
#include <algorithm>
#include <tools/debug.hxx>
#include <tools/poly.hxx>
#include <tools/helpers.hxx>
#include <tools/gen.hxx>
#include <svx/xpoly.hxx>
#include <xpolyimp.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
ImpXPolygon::ImpXPolygon(sal_uInt16 nInitSize, sal_uInt16 _nResize)
: pOldPointAry(nullptr)
, bDeleteOldPoints(false)
, nSize(0)
, nResize(_nResize)
, nPoints(0)
{
Resize(nInitSize);
}
ImpXPolygon::ImpXPolygon( const ImpXPolygon& rImpXPoly )
: pOldPointAry(nullptr)
, bDeleteOldPoints(false)
, nSize(0)
, nResize(rImpXPoly.nResize)
, nPoints(0)
{
rImpXPoly.CheckPointDelete();
Resize( rImpXPoly.nSize );
// copy
nPoints = rImpXPoly.nPoints;
memcpy( pPointAry.get(), rImpXPoly.pPointAry.get(), nSize*sizeof( Point ) );
memcpy( pFlagAry.get(), rImpXPoly.pFlagAry.get(), nSize );
}
ImpXPolygon::~ImpXPolygon()
{
pPointAry.reset();
if ( bDeleteOldPoints )
{
delete[] pOldPointAry;
pOldPointAry = nullptr;
}
}
bool ImpXPolygon::operator==(const ImpXPolygon& rImpXPoly) const
{
return nPoints==rImpXPoly.nPoints &&
(nPoints==0 ||
(memcmp(pPointAry.get(), rImpXPoly.pPointAry.get(), nPoints*sizeof(Point))==0 &&
memcmp(pFlagAry.get(), rImpXPoly.pFlagAry.get(), nPoints)==0));
}
/** Change polygon size
*
* @param nNewSize the new size of the polygon
* @param bDeletePoints if FALSE, do not delete the point array directly but
* wait for the next call before doing so. This prevents
* errors with XPoly[n] = XPoly[0] where a resize might
* destroy the right side point array too early.
*/
void ImpXPolygon::Resize( sal_uInt16 nNewSize, bool bDeletePoints )
{
if( nNewSize == nSize )
return;
PolyFlags* pOldFlagAry = pFlagAry.release();
sal_uInt16 nOldSize = nSize;
CheckPointDelete();
pOldPointAry = pPointAry.release();
// Round the new size to a multiple of nResize, if
// the object was not newly created (nSize != 0)
if ( nSize != 0 && nNewSize > nSize )
{
DBG_ASSERT(nResize, "Trying to resize but nResize = 0 !");
nNewSize = nSize + ((nNewSize-nSize-1) / nResize + 1) * nResize;
}
// create point array
nSize = nNewSize;
pPointAry.reset( new Point[ nSize ] );
// create flag array
pFlagAry.reset( new PolyFlags[ nSize ] );
memset( pFlagAry.get(), 0, nSize );
// copy if needed
if (nOldSize)
{
if( nOldSize < nSize )
{
memcpy( pPointAry.get(), pOldPointAry, nOldSize*sizeof( Point ) );
memcpy( pFlagAry.get(), pOldFlagAry, nOldSize );
}
else
{
memcpy( pPointAry.get(), pOldPointAry, nSize*sizeof( Point ) );
memcpy( pFlagAry.get(), pOldFlagAry, nSize );
// adjust number of valid points
if( nPoints > nSize )
nPoints = nSize;
}
}
if ( bDeletePoints )
{
delete[] pOldPointAry;
pOldPointAry = nullptr;
}
else
bDeleteOldPoints = true;
delete[] pOldFlagAry;
}
void ImpXPolygon::InsertSpace( sal_uInt16 nPos, sal_uInt16 nCount )
{
CheckPointDelete();
if ( nPos > nPoints )
nPos = nPoints;
// if the polygon is too small then enlarge it
if( (nPoints + nCount) > nSize )
Resize( nPoints + nCount );
// If the insert is not at the last position, move everything after backwards
if( nPos < nPoints )
{
sal_uInt16 nMove = nPoints - nPos;
memmove( &pPointAry[nPos+nCount], &pPointAry[nPos],
nMove * sizeof(Point) );
memmove( &pFlagAry[nPos+nCount], &pFlagAry[nPos], nMove );
}
std::fill(pPointAry.get() + nPos, pPointAry.get() + nPos + nCount, Point());
memset( &pFlagAry [nPos], 0, nCount );
nPoints = nPoints + nCount;
}
void ImpXPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
{
CheckPointDelete();
if( (nPos + nCount) > nPoints )
return;
sal_uInt16 nMove = nPoints - nPos - nCount;
if( nMove )
{
memmove( &pPointAry[nPos], &pPointAry[nPos+nCount],
nMove * sizeof(Point) );
memmove( &pFlagAry[nPos], &pFlagAry[nPos+nCount], nMove );
}
std::fill(pPointAry.get() + (nPoints - nCount), pPointAry.get() + nPoints, Point());
memset( &pFlagAry [nPoints - nCount], 0, nCount );
nPoints = nPoints - nCount;
}
void ImpXPolygon::CheckPointDelete() const
{
if ( bDeleteOldPoints )
{
delete[] pOldPointAry;
const_cast< ImpXPolygon* >(this)->pOldPointAry = nullptr;
const_cast< ImpXPolygon* >(this)->bDeleteOldPoints = false;
}
}
XPolygon::XPolygon( sal_uInt16 nSize )
: m_pImpXPolygon( ImpXPolygon( nSize, 16 ) )
{
}
XPolygon::XPolygon( const XPolygon& ) = default;
XPolygon::XPolygon( XPolygon&& ) = default;
/// create a XPolygon out of a standard polygon
XPolygon::XPolygon( const tools::Polygon& rPoly )
: m_pImpXPolygon( rPoly.GetSize() )
{
sal_uInt16 nSize = rPoly.GetSize();
m_pImpXPolygon->nPoints = nSize;
for( sal_uInt16 i = 0; i < nSize; i++ )
{
m_pImpXPolygon->pPointAry[i] = rPoly[i];
m_pImpXPolygon->pFlagAry[i] = rPoly.GetFlags( i );
}
}
/// create a rectangle (also with rounded corners) as a Bézier polygon
XPolygon::XPolygon(const tools::Rectangle& rRect, tools::Long nRx, tools::Long nRy)
: m_pImpXPolygon( 17 )
{
tools::Long nWh = (rRect.GetWidth() - 1) / 2;
tools::Long nHh = (rRect.GetHeight() - 1) / 2;
if ( nRx > nWh ) nRx = nWh;
if ( nRy > nHh ) nRy = nHh;
// negate Rx => circle clockwise
nRx = -nRx;
// factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
tools::Long nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
tools::Long nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
sal_uInt16 nPos = 0;
if ( nRx && nRy )
{
Point aCenter;
for (sal_uInt16 nQuad = 0; nQuad < 4; nQuad++)
{
switch ( nQuad )
{
case 0: aCenter = rRect.TopLeft();
aCenter.AdjustX( -nRx );
aCenter.AdjustY(nRy );
break;
case 1: aCenter = rRect.TopRight();
aCenter.AdjustX(nRx );
aCenter.AdjustY(nRy );
break;
case 2: aCenter = rRect.BottomRight();
aCenter.AdjustX(nRx );
aCenter.AdjustY( -nRy );
break;
case 3: aCenter = rRect.BottomLeft();
aCenter.AdjustX( -nRx );
aCenter.AdjustY( -nRy );
break;
}
GenBezArc(aCenter, nRx, nRy, nXHdl, nYHdl, 0_deg100, 9000_deg100, nQuad, nPos);
m_pImpXPolygon->pFlagAry[nPos ] = PolyFlags::Smooth;
m_pImpXPolygon->pFlagAry[nPos+3] = PolyFlags::Smooth;
nPos += 4;
}
}
else
{
m_pImpXPolygon->pPointAry[nPos++] = rRect.TopLeft();
m_pImpXPolygon->pPointAry[nPos++] = rRect.TopRight();
m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomRight();
m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomLeft();
}
m_pImpXPolygon->pPointAry[nPos] = m_pImpXPolygon->pPointAry[0];
m_pImpXPolygon->nPoints = nPos + 1;
}
/// create an ellipse (curve) as Bézier polygon
XPolygon::XPolygon(const Point& rCenter, tools::Long nRx, tools::Long nRy,
Degree100 nStartAngle, Degree100 nEndAngle, bool bClose)
: m_pImpXPolygon( 17 )
{
nStartAngle %= 36000_deg100;
if ( nEndAngle > 36000_deg100 ) nEndAngle %= 36000_deg100;
bool bFull = (nStartAngle == 0_deg100 && nEndAngle == 36000_deg100);
// factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
tools::Long nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
tools::Long nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
sal_uInt16 nPos = 0;
bool bLoopEnd = false;
do
{
Degree100 nA1, nA2;
sal_uInt16 nQuad = nStartAngle.get() / 9000;
if ( nQuad == 4 ) nQuad = 0;
bLoopEnd = CheckAngles(nStartAngle, nEndAngle, nA1, nA2);
GenBezArc(rCenter, nRx, nRy, nXHdl, nYHdl, nA1, nA2, nQuad, nPos);
nPos += 3;
if ( !bLoopEnd )
m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
} while ( !bLoopEnd );
// if not a full circle then connect edges with center point if necessary
if ( !bFull && bClose )
m_pImpXPolygon->pPointAry[++nPos] = rCenter;
if ( bFull )
{
m_pImpXPolygon->pFlagAry[0 ] = PolyFlags::Smooth;
m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
}
m_pImpXPolygon->nPoints = nPos + 1;
}
XPolygon::~XPolygon() = default;
void XPolygon::SetPointCount( sal_uInt16 nPoints )
{
std::as_const(m_pImpXPolygon)->CheckPointDelete();
if( m_pImpXPolygon->nSize < nPoints )
m_pImpXPolygon->Resize( nPoints );
if ( nPoints < m_pImpXPolygon->nPoints )
{
sal_uInt16 nSize = m_pImpXPolygon->nPoints - nPoints;
std::fill(
m_pImpXPolygon->pPointAry.get() + nPoints, m_pImpXPolygon->pPointAry.get() + nPoints + nSize, Point());
memset( &m_pImpXPolygon->pFlagAry [nPoints], 0, nSize );
}
m_pImpXPolygon->nPoints = nPoints;
}
sal_uInt16 XPolygon::GetSize() const
{
m_pImpXPolygon->CheckPointDelete();
return m_pImpXPolygon->nSize;
}
sal_uInt16 XPolygon::GetPointCount() const
{
m_pImpXPolygon->CheckPointDelete();
return m_pImpXPolygon->nPoints;
}
void XPolygon::Insert( sal_uInt16 nPos, const Point& rPt, PolyFlags eFlags )
{
if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
m_pImpXPolygon->InsertSpace( nPos, 1 );
m_pImpXPolygon->pPointAry[nPos] = rPt;
m_pImpXPolygon->pFlagAry[nPos] = eFlags;
}
void XPolygon::Insert( sal_uInt16 nPos, const XPolygon& rXPoly )
{
if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
sal_uInt16 nPoints = rXPoly.GetPointCount();
m_pImpXPolygon->InsertSpace( nPos, nPoints );
memcpy( &(m_pImpXPolygon->pPointAry[nPos]),
rXPoly.m_pImpXPolygon->pPointAry.get(),
nPoints*sizeof( Point ) );
memcpy( &(m_pImpXPolygon->pFlagAry[nPos]),
rXPoly.m_pImpXPolygon->pFlagAry.get(),
nPoints );
}
void XPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
{
m_pImpXPolygon->Remove( nPos, nCount );
}
void XPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
{
if ( !nHorzMove && !nVertMove )
return;
// move points
sal_uInt16 nCount = m_pImpXPolygon->nPoints;
for ( sal_uInt16 i = 0; i < nCount; i++ )
{
Point* pPt = &(m_pImpXPolygon->pPointAry[i]);
pPt->AdjustX( nHorzMove );
pPt->AdjustY( nVertMove );
}
}
tools::Rectangle XPolygon::GetBoundRect() const
{
m_pImpXPolygon->CheckPointDelete();
tools::Rectangle aRetval;
if(m_pImpXPolygon->nPoints)
{
// #i37709#
// For historical reasons the control points are not part of the
// BoundRect. This makes it necessary to subdivide the polygon to
// get a relatively correct BoundRect. Numerically, this is not
// correct and never was.
const basegfx::B2DRange aPolygonRange(basegfx::utils::getRange(getB2DPolygon()));
aRetval = tools::Rectangle(basegfx::fround<tools::Long>(aPolygonRange.getMinX()),
basegfx::fround<tools::Long>(aPolygonRange.getMinY()),
basegfx::fround<tools::Long>(aPolygonRange.getMaxX()),
basegfx::fround<tools::Long>(aPolygonRange.getMaxY()));
}
return aRetval;
}
const Point& XPolygon::operator[]( sal_uInt16 nPos ) const
{
DBG_ASSERT(nPos < m_pImpXPolygon->nPoints, "Invalid index at const array access to XPolygon");
m_pImpXPolygon->CheckPointDelete();
return m_pImpXPolygon->pPointAry[nPos];
}
Point& XPolygon::operator[]( sal_uInt16 nPos )
{
std::as_const(m_pImpXPolygon)->CheckPointDelete();
if( nPos >= m_pImpXPolygon->nSize )
{
DBG_ASSERT(m_pImpXPolygon->nResize, "Invalid index at array access to XPolygon");
m_pImpXPolygon->Resize(nPos + 1, false);
}
if( nPos >= m_pImpXPolygon->nPoints )
m_pImpXPolygon->nPoints = nPos + 1;
return m_pImpXPolygon->pPointAry[nPos];
}
XPolygon& XPolygon::operator=( const XPolygon& ) = default;
XPolygon& XPolygon::operator=( XPolygon&& ) = default;
bool XPolygon::operator==( const XPolygon& rXPoly ) const
{
m_pImpXPolygon->CheckPointDelete();
return rXPoly.m_pImpXPolygon == m_pImpXPolygon;
}
/// get the flags for the point at the given position
PolyFlags XPolygon::GetFlags( sal_uInt16 nPos ) const
{
m_pImpXPolygon->CheckPointDelete();
return m_pImpXPolygon->pFlagAry[nPos];
}
/// set the flags for the point at the given position
void XPolygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags )
{
std::as_const(m_pImpXPolygon)->CheckPointDelete();
m_pImpXPolygon->pFlagAry[nPos] = eFlags;
}
/// short path to read the CONTROL flag directly (TODO: better explain what the sense behind this flag is!)
bool XPolygon::IsControl(sal_uInt16 nPos) const
{
return m_pImpXPolygon->pFlagAry[nPos] == PolyFlags::Control;
}
/// short path to read the SMOOTH and SYMMTR flag directly (TODO: better explain what the sense behind these flags is!)
bool XPolygon::IsSmooth(sal_uInt16 nPos) const
{
PolyFlags eFlag = m_pImpXPolygon->pFlagAry[nPos];
return ( eFlag == PolyFlags::Smooth || eFlag == PolyFlags::Symmetric );
}
/** calculate the euclidean distance between two points
*
* @param nP1 The first point
* @param nP2 The second point
*/
double XPolygon::CalcDistance(sal_uInt16 nP1, sal_uInt16 nP2)
{
const Point& rP1 = m_pImpXPolygon->pPointAry[nP1];
const Point& rP2 = m_pImpXPolygon->pPointAry[nP2];
double fDx = rP2.X() - rP1.X();
double fDy = rP2.Y() - rP1.Y();
return std::hypot(fDx, fDy);
}
void XPolygon::SubdivideBezier(sal_uInt16 nPos, bool bCalcFirst, double fT)
{
Point* pPoints = m_pImpXPolygon->pPointAry.get();
double fT2 = fT * fT;
double fT3 = fT * fT2;
double fU = 1.0 - fT;
double fU2 = fU * fU;
double fU3 = fU * fU2;
sal_uInt16 nIdx = nPos;
short nPosInc, nIdxInc;
if ( bCalcFirst )
{
nPos += 3;
nPosInc = -1;
nIdxInc = 0;
}
else
{
nPosInc = 1;
nIdxInc = 1;
}
pPoints[nPos].setX( static_cast<tools::Long>(fU3 * pPoints[nIdx ].X() +
fT * fU2 * pPoints[nIdx+1].X() * 3 +
fT2 * fU * pPoints[nIdx+2].X() * 3 +
fT3 * pPoints[nIdx+3].X()) );
pPoints[nPos].setY( static_cast<tools::Long>(fU3 * pPoints[nIdx ].Y() +
fT * fU2 * pPoints[nIdx+1].Y() * 3 +
fT2 * fU * pPoints[nIdx+2].Y() * 3 +
fT3 * pPoints[nIdx+3].Y()) );
nPos = nPos + nPosInc;
nIdx = nIdx + nIdxInc;
pPoints[nPos].setX( static_cast<tools::Long>(fU2 * pPoints[nIdx ].X() +
fT * fU * pPoints[nIdx+1].X() * 2 +
fT2 * pPoints[nIdx+2].X()) );
pPoints[nPos].setY( static_cast<tools::Long>(fU2 * pPoints[nIdx ].Y() +
fT * fU * pPoints[nIdx+1].Y() * 2 +
fT2 * pPoints[nIdx+2].Y()) );
nPos = nPos + nPosInc;
nIdx = nIdx + nIdxInc;
pPoints[nPos].setX( static_cast<tools::Long>(fU * pPoints[nIdx ].X() +
fT * pPoints[nIdx+1].X()) );
pPoints[nPos].setY( static_cast<tools::Long>(fU * pPoints[nIdx ].Y() +
fT * pPoints[nIdx+1].Y()) );
}
/// Generate a Bézier arc
void XPolygon::GenBezArc(const Point& rCenter, tools::Long nRx, tools::Long nRy,
tools::Long nXHdl, tools::Long nYHdl, Degree100 nStart, Degree100 nEnd,
sal_uInt16 nQuad, sal_uInt16 nFirst)
{
Point* pPoints = m_pImpXPolygon->pPointAry.get();
pPoints[nFirst ] = rCenter;
pPoints[nFirst+3] = rCenter;
if ( nQuad == 1 || nQuad == 2 )
{
nRx = -nRx; nXHdl = -nXHdl;
}
if ( nQuad == 0 || nQuad == 1 )
{
nRy = -nRy; nYHdl = -nYHdl;
}
if ( nQuad == 0 || nQuad == 2 )
{
pPoints[nFirst].AdjustX( nRx );
pPoints[nFirst+3].AdjustY( nRy );
}
else
{
pPoints[nFirst].AdjustY( nRy );
pPoints[nFirst+3].AdjustX( nRx );
}
pPoints[nFirst+1] = pPoints[nFirst];
pPoints[nFirst+2] = pPoints[nFirst+3];
if ( nQuad == 0 || nQuad == 2 )
{
pPoints[nFirst+1].AdjustY( nYHdl );
pPoints[nFirst+2].AdjustX( nXHdl );
}
else
{
pPoints[nFirst+1].AdjustX( nXHdl );
pPoints[nFirst+2].AdjustY( nYHdl );
}
if ( nStart > 0_deg100 )
SubdivideBezier(nFirst, false, static_cast<double>(nStart.get()) / 9000);
if ( nEnd < 9000_deg100 )
SubdivideBezier(nFirst, true, static_cast<double>((nEnd-nStart).get()) / (9000_deg100-nStart).get());
SetFlags(nFirst+1, PolyFlags::Control);
SetFlags(nFirst+2, PolyFlags::Control);
}
bool XPolygon::CheckAngles(Degree100& nStart, Degree100 nEnd, Degree100& nA1, Degree100& nA2)
{
if ( nStart == 36000_deg100 ) nStart = 0_deg100;
if ( nEnd == 0_deg100 ) nEnd = 36000_deg100;
Degree100 nStPrev = nStart;
Degree100 nMax((nStart.get() / 9000 + 1) * 9000);
Degree100 nMin = nMax - 9000_deg100;
if ( nEnd >= nMax || nEnd <= nStart ) nA2 = 9000_deg100;
else nA2 = nEnd - nMin;
nA1 = nStart - nMin;
nStart = nMax;
// returns true when the last segment was calculated
return (nStPrev < nEnd && nStart >= nEnd);
}
/** Calculate a smooth transition to connect two Bézier curves
*
* This is done by projecting the corresponding point onto a line between
* two other points.
*
* @param nCenter The point at the end or beginning of the curve.
* If nCenter is at the end of the polygon the point is moved
* to the opposite side.
* @param nDrag The moved point that specifies the relocation.
* @param nPnt The point to modify.
*/
void XPolygon::CalcSmoothJoin(sal_uInt16 nCenter, sal_uInt16 nDrag, sal_uInt16 nPnt)
{
// If nPoint is no control point, i.e. cannot be moved, then
// move nDrag instead on the line between nCenter and nPnt
if ( !IsControl(nPnt) )
std::swap( nDrag, nPnt );
Point* pPoints = m_pImpXPolygon->pPointAry.get();
Point aDiff = pPoints[nDrag] - pPoints[nCenter];
double fDiv = CalcDistance(nCenter, nDrag);
if ( fDiv )
{
double fRatio = CalcDistance(nCenter, nPnt) / fDiv;
// keep the length if SMOOTH
if ( GetFlags(nCenter) == PolyFlags::Smooth || !IsControl(nDrag) )
{
aDiff.setX( static_cast<tools::Long>(fRatio * aDiff.X()) );
aDiff.setY( static_cast<tools::Long>(fRatio * aDiff.Y()) );
}
pPoints[nPnt] = pPoints[nCenter] - aDiff;
}
}
/** Calculate tangent between two Bézier curves
*
* @param nCenter start or end point of the curves
* @param nPrev previous reference point
* @param nNext next reference point
*/
void XPolygon::CalcTangent(sal_uInt16 nCenter, sal_uInt16 nPrev, sal_uInt16 nNext)
{
double fAbsLen = CalcDistance(nNext, nPrev);
if ( !fAbsLen )
return;
const Point& rCenter = m_pImpXPolygon->pPointAry[nCenter];
Point& rNext = m_pImpXPolygon->pPointAry[nNext];
Point& rPrev = m_pImpXPolygon->pPointAry[nPrev];
Point aDiff = rNext - rPrev;
double fNextLen = CalcDistance(nCenter, nNext) / fAbsLen;
double fPrevLen = CalcDistance(nCenter, nPrev) / fAbsLen;
// same length for both sides if SYMMTR
if ( GetFlags(nCenter) == PolyFlags::Symmetric )
{
fPrevLen = (fNextLen + fPrevLen) / 2;
fNextLen = fPrevLen;
}
rNext.setX( rCenter.X() + static_cast<tools::Long>(fNextLen * aDiff.X()) );
rNext.setY( rCenter.Y() + static_cast<tools::Long>(fNextLen * aDiff.Y()) );
rPrev.setX( rCenter.X() - static_cast<tools::Long>(fPrevLen * aDiff.X()) );
rPrev.setY( rCenter.Y() - static_cast<tools::Long>(fPrevLen * aDiff.Y()) );
}
/// convert four polygon points into a Bézier curve
void XPolygon::PointsToBezier(sal_uInt16 nFirst)
{
double nFullLength, nPart1Length, nPart2Length;
double fX0, fY0, fX1, fY1, fX2, fY2, fX3, fY3;
double fTx1, fTx2, fTy1, fTy2;
double fT1, fU1, fT2, fU2, fV;
Point* pPoints = m_pImpXPolygon->pPointAry.get();
if ( nFirst > m_pImpXPolygon->nPoints - 4 || IsControl(nFirst) ||
IsControl(nFirst+1) || IsControl(nFirst+2) || IsControl(nFirst+3) )
return;
fTx1 = pPoints[nFirst+1].X();
fTy1 = pPoints[nFirst+1].Y();
fTx2 = pPoints[nFirst+2].X();
fTy2 = pPoints[nFirst+2].Y();
fX0 = pPoints[nFirst ].X();
fY0 = pPoints[nFirst ].Y();
fX3 = pPoints[nFirst+3].X();
fY3 = pPoints[nFirst+3].Y();
nPart1Length = CalcDistance(nFirst, nFirst+1);
nPart2Length = nPart1Length + CalcDistance(nFirst+1, nFirst+2);
nFullLength = nPart2Length + CalcDistance(nFirst+2, nFirst+3);
if ( nFullLength < 20 )
return;
if ( nPart2Length == nFullLength )
nPart2Length -= 1;
if ( nPart1Length == nFullLength )
nPart1Length = nPart2Length - 1;
if ( nPart1Length <= 0 )
nPart1Length = 1;
if ( nPart2Length <= 0 || nPart2Length == nPart1Length )
nPart2Length = nPart1Length + 1;
fT1 = nPart1Length / nFullLength;
fU1 = 1.0 - fT1;
fT2 = nPart2Length / nFullLength;
fU2 = 1.0 - fT2;
fV = 3 * (1.0 - (fT1 * fU2) / (fT2 * fU1));
fX1 = fTx1 / (fT1 * fU1 * fU1) - fTx2 * fT1 / (fT2 * fT2 * fU1 * fU2);
fX1 /= fV;
fX1 -= fX0 * ( fU1 / fT1 + fU2 / fT2) / 3;
fX1 += fX3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
fY1 = fTy1 / (fT1 * fU1 * fU1) - fTy2 * fT1 / (fT2 * fT2 * fU1 * fU2);
fY1 /= fV;
fY1 -= fY0 * ( fU1 / fT1 + fU2 / fT2) / 3;
fY1 += fY3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
fX2 = fTx2 / (fT2 * fT2 * fU2 * 3) - fX0 * fU2 * fU2 / ( fT2 * fT2 * 3);
fX2 -= fX1 * fU2 / fT2;
fX2 -= fX3 * fT2 / (fU2 * 3);
fY2 = fTy2 / (fT2 * fT2 * fU2 * 3) - fY0 * fU2 * fU2 / ( fT2 * fT2 * 3);
fY2 -= fY1 * fU2 / fT2;
fY2 -= fY3 * fT2 / (fU2 * 3);
Point aControlPoint1(static_cast<tools::Long>(fX1), static_cast<tools::Long>(fY1));
Point aControlPoint2(static_cast<tools::Long>(fX2), static_cast<tools::Long>(fY2));
auto fPointOffset1 = std::hypot(aControlPoint1.X() - pPoints[nFirst + 1].X(), aControlPoint1.Y() - pPoints[nFirst + 1].Y());
auto fPointOffset2 = std::hypot(aControlPoint2.X() - pPoints[nFirst + 2].X(), aControlPoint2.Y() - pPoints[nFirst + 2].Y());
// To prevent the curve from overshooting due to sharp direction changes in the given sequence of points,
// compare the control point offsets against the full segment length.
// Apply the calculated ControlPoints only if their offsets are within a reasonable range.
if( fPointOffset1 < nFullLength && fPointOffset2 < nFullLength )
{
pPoints[nFirst + 1] = aControlPoint1;
pPoints[nFirst + 2] = aControlPoint2;
}
SetFlags(nFirst+1, PolyFlags::Control);
SetFlags(nFirst+2, PolyFlags::Control);
}
/// scale in X- and/or Y-direction
void XPolygon::Scale(double fSx, double fSy)
{
std::as_const(m_pImpXPolygon)->CheckPointDelete();
sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
for (sal_uInt16 i = 0; i < nPntCnt; i++)
{
Point& rPnt = m_pImpXPolygon->pPointAry[i];
rPnt.setX( static_cast<tools::Long>(fSx * rPnt.X()) );
rPnt.setY( static_cast<tools::Long>(fSy * rPnt.Y()) );
}
}
/**
* Distort a polygon by scaling its coordinates relative to a reference
* rectangle into an arbitrary rectangle.
*
* Mapping between polygon corners and reference rectangle:
* 0: top left 0----1
* 1: top right | |
* 2: bottom right 3----2
* 3: bottom left
*/
void XPolygon::Distort(const tools::Rectangle& rRefRect,
const XPolygon& rDistortedRect)
{
std::as_const(m_pImpXPolygon)->CheckPointDelete();
tools::Long Xr, Wr;
tools::Long Yr, Hr;
Xr = rRefRect.Left();
Yr = rRefRect.Top();
Wr = rRefRect.GetWidth();
Hr = rRefRect.GetHeight();
if ( !Wr || !Hr )
return;
tools::Long X1, X2, X3, X4;
tools::Long Y1, Y2, Y3, Y4;
DBG_ASSERT(rDistortedRect.m_pImpXPolygon->nPoints >= 4,
"Distort: rectangle too small");
X1 = rDistortedRect[0].X();
Y1 = rDistortedRect[0].Y();
X2 = rDistortedRect[1].X();
Y2 = rDistortedRect[1].Y();
X3 = rDistortedRect[3].X();
Y3 = rDistortedRect[3].Y();
X4 = rDistortedRect[2].X();
Y4 = rDistortedRect[2].Y();
sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
for (sal_uInt16 i = 0; i < nPntCnt; i++)
{
double fTx, fTy, fUx, fUy;
Point& rPnt = m_pImpXPolygon->pPointAry[i];
fTx = static_cast<double>(rPnt.X() - Xr) / Wr;
fTy = static_cast<double>(rPnt.Y() - Yr) / Hr;
fUx = 1.0 - fTx;
fUy = 1.0 - fTy;
rPnt.setX( static_cast<tools::Long>( fUy * (fUx * X1 + fTx * X2) +
fTy * (fUx * X3 + fTx * X4) ) );
rPnt.setY( static_cast<tools::Long>( fUx * (fUy * Y1 + fTy * Y3) +
fTx * (fUy * Y2 + fTy * Y4) ) );
}
}
basegfx::B2DPolygon XPolygon::getB2DPolygon() const
{
// #i74631# use tools Polygon class for conversion to not have the code doubled
// here. This needs one more conversion but avoids different converters in
// the long run
const tools::Polygon aSource(GetPointCount(), m_pImpXPolygon->pPointAry.get(), m_pImpXPolygon->pFlagAry.get());
return aSource.getB2DPolygon();
}
XPolygon::XPolygon(const basegfx::B2DPolygon& rPolygon)
: m_pImpXPolygon( tools::Polygon( rPolygon ).GetSize() )
{
// #i74631# use tools Polygon class for conversion to not have the code doubled
// here. This needs one more conversion but avoids different converters in
// the long run
const tools::Polygon aSource(rPolygon);
sal_uInt16 nSize = aSource.GetSize();
m_pImpXPolygon->nPoints = nSize;
for( sal_uInt16 i = 0; i < nSize; i++ )
{
m_pImpXPolygon->pPointAry[i] = aSource[i];
m_pImpXPolygon->pFlagAry[i] = aSource.GetFlags( i );
}
}
// XPolyPolygon
XPolyPolygon::XPolyPolygon() = default;
XPolyPolygon::XPolyPolygon( const XPolyPolygon& ) = default;
XPolyPolygon::XPolyPolygon( XPolyPolygon&& ) = default;
XPolyPolygon::XPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
for(auto const& rCandidate : rPolyPolygon)
{
Insert(XPolygon(rCandidate));
}
}
XPolyPolygon::~XPolyPolygon() = default;
void XPolyPolygon::Insert( XPolygon&& rXPoly )
{
m_pImpXPolyPolygon->aXPolyList.emplace_back( std::move(rXPoly) );
}
/// insert all XPolygons of a XPolyPolygon
void XPolyPolygon::Insert( const XPolyPolygon& rXPolyPoly )
{
for ( size_t i = 0; i < rXPolyPoly.Count(); i++)
{
m_pImpXPolyPolygon->aXPolyList.emplace_back( rXPolyPoly[i] );
}
}
void XPolyPolygon::Remove( sal_uInt16 nPos )
{
m_pImpXPolyPolygon->aXPolyList.erase( m_pImpXPolyPolygon->aXPolyList.begin() + nPos );
}
const XPolygon& XPolyPolygon::GetObject( sal_uInt16 nPos ) const
{
return m_pImpXPolyPolygon->aXPolyList[ nPos ];
}
void XPolyPolygon::Clear()
{
m_pImpXPolyPolygon->aXPolyList.clear();
}
sal_uInt16 XPolyPolygon::Count() const
{
return static_cast<sal_uInt16>(m_pImpXPolyPolygon->aXPolyList.size());
}
tools::Rectangle XPolyPolygon::GetBoundRect() const
{
size_t nXPoly = m_pImpXPolyPolygon->aXPolyList.size();
tools::Rectangle aRect;
for ( size_t n = 0; n < nXPoly; n++ )
{
XPolygon const & rXPoly = m_pImpXPolyPolygon->aXPolyList[ n ];
aRect.Union( rXPoly.GetBoundRect() );
}
return aRect;
}
XPolygon& XPolyPolygon::operator[]( sal_uInt16 nPos )
{
return m_pImpXPolyPolygon->aXPolyList[ nPos ];
}
XPolyPolygon& XPolyPolygon::operator=( const XPolyPolygon& ) = default;
XPolyPolygon& XPolyPolygon::operator=( XPolyPolygon&& ) = default;
/**
* Distort a polygon by scaling its coordinates relative to a reference
* rectangle into an arbitrary rectangle.
*
* Mapping between polygon corners and reference rectangle:
* 0: top left 0----1
* 1: top right | |
* 2: bottom right 3----2
* 3: bottom left
*/
void XPolyPolygon::Distort(const tools::Rectangle& rRefRect,
const XPolygon& rDistortedRect)
{
for (size_t i = 0; i < Count(); i++)
m_pImpXPolyPolygon->aXPolyList[ i ].Distort(rRefRect, rDistortedRect);
}
basegfx::B2DPolyPolygon XPolyPolygon::getB2DPolyPolygon() const
{
basegfx::B2DPolyPolygon aRetval;
for(sal_uInt16 a(0); a < Count(); a++)
{
const XPolygon& rPoly = (*this)[a];
aRetval.append(rPoly.getB2DPolygon());
}
return aRetval;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */