blob: 6653bf5951d2afd31eb6ef48e2966de033744227 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include <limits.h>
#include <tools/vcompat.hxx>
#include <tools/stream.hxx>
#include <vcl/region.hxx>
#include <regionband.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygonclipper.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
//////////////////////////////////////////////////////////////////////////////
DBG_NAME( Region )
DBG_NAMEEX( Polygon )
DBG_NAMEEX( PolyPolygon )
//////////////////////////////////////////////////////////////////////////////
namespace
{
/** Return <TRUE/> when the given polygon is rectiliner and oriented so that
all sides are either horizontal or vertical.
*/
bool ImplIsPolygonRectilinear (const PolyPolygon& rPolyPoly)
{
// Iterate over all polygons.
const sal_uInt16 nPolyCount = rPolyPoly.Count();
for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
{
const Polygon& aPoly = rPolyPoly.GetObject(nPoly);
// Iterate over all edges of the current polygon.
const sal_uInt16 nSize = aPoly.GetSize();
if (nSize < 2)
continue;
Point aPoint (aPoly.GetPoint(0));
const Point aLastPoint (aPoint);
for (sal_uInt16 nPoint = 1; nPoint < nSize; ++nPoint)
{
const Point aNextPoint (aPoly.GetPoint(nPoint));
// When there is at least one edge that is neither vertical nor
// horizontal then the entire polygon is not rectilinear (and
// oriented along primary axes.)
if (aPoint.X() != aNextPoint.X() && aPoint.Y() != aNextPoint.Y())
return false;
aPoint = aNextPoint;
}
// Compare closing edge.
if (aLastPoint.X() != aPoint.X() && aLastPoint.Y() != aPoint.Y())
return false;
}
return true;
}
/** Convert a rectilinear polygon (that is oriented along the primary axes)
to a list of bands. For this special form of polygon we can use an
optimization that prevents the creation of one band per y value.
However, it still is possible that some temporary bands are created that
later can be optimized away.
@param rPolyPolygon
A set of zero, one, or more polygons, nested or not, that are
converted into a list of bands.
@return
A new RegionBand object is returned that contains the bands that
represent the given poly-polygon.
*/
RegionBand* ImplRectilinearPolygonToBands(const PolyPolygon& rPolyPoly)
{
OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly));
// Create a new RegionBand object as container of the bands.
RegionBand* pRegionBand = new RegionBand();
long nLineId = 0L;
// Iterate over all polygons.
const sal_uInt16 nPolyCount = rPolyPoly.Count();
for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
{
const Polygon& aPoly = rPolyPoly.GetObject(nPoly);
// Iterate over all edges of the current polygon.
const sal_uInt16 nSize = aPoly.GetSize();
if (nSize < 2)
continue;
// Avoid fetching every point twice (each point is the start point
// of one and the end point of another edge.)
Point aStart (aPoly.GetPoint(0));
Point aEnd;
for (sal_uInt16 nPoint = 1; nPoint <= nSize; ++nPoint, aStart=aEnd)
{
// We take the implicit closing edge into account by mapping
// index nSize to 0.
aEnd = aPoly.GetPoint(nPoint%nSize);
if (aStart.Y() == aEnd.Y())
{
// Horizontal lines are ignored.
continue;
}
// At this point the line has to be vertical.
OSL_ASSERT(aStart.X() == aEnd.X());
// Sort y-coordinates to simplify the algorithm and store the
// direction seperately. The direction is calculated as it is
// in other places (but seems to be the wrong way.)
const long nTop (::std::min(aStart.Y(), aEnd.Y()));
const long nBottom (::std::max(aStart.Y(), aEnd.Y()));
const LineType eLineType (aStart.Y() > aEnd.Y() ? LINE_DESCENDING : LINE_ASCENDING);
// Make sure that the current line is covered by bands.
pRegionBand->ImplAddMissingBands(nTop,nBottom);
// Find top-most band that may contain nTop.
ImplRegionBand* pBand = pRegionBand->ImplGetFirstRegionBand();
while (pBand!=NULL && pBand->mnYBottom < nTop)
pBand = pBand->mpNextBand;
ImplRegionBand* pTopBand = pBand;
// If necessary split the band at nTop so that nTop is contained
// in the lower band.
if (pBand!=NULL
// Prevent the current band from becoming 0 pixel high
&& pBand->mnYTop<nTop
// this allows the lowest pixel of the band to be split off
&& pBand->mnYBottom>=nTop
// do not split a band that is just one pixel high
&& pBand->mnYTop<pBand->mnYBottom)
{
// Split the top band.
pTopBand = pBand->SplitBand(nTop);
}
// Advance to band that may contain nBottom.
while (pBand!=NULL && pBand->mnYBottom < nBottom)
pBand = pBand->mpNextBand;
// The lowest band may have to be split at nBottom so that
// nBottom itself remains in the upper band.
if (pBand!=NULL
// allow the current band becoming 1 pixel high
&& pBand->mnYTop<=nBottom
// prevent splitting off a band that is 0 pixel high
&& pBand->mnYBottom>nBottom
// do not split a band that is just one pixel high
&& pBand->mnYTop<pBand->mnYBottom)
{
// Split the bottom band.
pBand->SplitBand(nBottom+1);
}
// Note that we remember the top band (in pTopBand) but not the
// bottom band. The later can be determined by comparing y
// coordinates.
// Add the x-value as point to all bands in the nTop->nBottom range.
for (pBand=pTopBand; pBand!=NULL&&pBand->mnYTop<=nBottom; pBand=pBand->mpNextBand)
pBand->InsertPoint(aStart.X(), nLineId++, true, eLineType);
}
}
return pRegionBand;
}
/** Convert a general polygon (one for which ImplIsPolygonRectilinear()
returns <FALSE/>) to bands.
*/
RegionBand* ImplGeneralPolygonToBands(const PolyPolygon& rPolyPoly, const Rectangle& rPolygonBoundingBox)
{
long nLineID = 0L;
// initialisation and creation of Bands
RegionBand* pRegionBand = new RegionBand();
pRegionBand->CreateBandRange(rPolygonBoundingBox.Top(), rPolygonBoundingBox.Bottom());
// insert polygons
const sal_uInt16 nPolyCount = rPolyPoly.Count();
for ( sal_uInt16 nPoly = 0; nPoly < nPolyCount; nPoly++ )
{
// get reference to current polygon
const Polygon& aPoly = rPolyPoly.GetObject( nPoly );
const sal_uInt16 nSize = aPoly.GetSize();
// not enough points ( <= 2 )? -> nothing to do!
if ( nSize <= 2 )
continue;
// band the polygon
for ( sal_uInt16 nPoint = 1; nPoint < nSize; nPoint++ )
{
pRegionBand->InsertLine( aPoly.GetPoint(nPoint-1), aPoly.GetPoint(nPoint), nLineID++ );
}
// close polygon with line from first point to last point, if neccesary
const Point rLastPoint = aPoly.GetPoint(nSize-1);
const Point rFirstPoint = aPoly.GetPoint(0);
if ( rLastPoint != rFirstPoint )
{
pRegionBand->InsertLine( rLastPoint, rFirstPoint, nLineID++ );
}
}
return pRegionBand;
}
} // end of anonymous namespace
//////////////////////////////////////////////////////////////////////////////
bool Region::IsEmpty() const
{
return !mbIsNull && !mpB2DPolyPolygon.get() && !mpPolyPolygon.get() && !mpRegionBand.get();
}
bool Region::IsNull() const
{
return mbIsNull;
}
RegionBand* ImplCreateRegionBandFromPolyPolygon(const PolyPolygon& rPolyPolygon)
{
RegionBand* pRetval = 0;
if(rPolyPolygon.Count())
{
// ensure to subdivide when bezier segemnts are used, it's going to
// be expanded to rectangles
PolyPolygon aPolyPolygon;
rPolyPolygon.AdaptiveSubdivide(aPolyPolygon);
if(aPolyPolygon.Count())
{
const Rectangle aRect(aPolyPolygon.GetBoundRect());
if(!aRect.IsEmpty())
{
if(ImplIsPolygonRectilinear(aPolyPolygon))
{
// For rectilinear polygons there is an optimized band conversion.
pRetval = ImplRectilinearPolygonToBands(aPolyPolygon);
}
else
{
pRetval = ImplGeneralPolygonToBands(aPolyPolygon, aRect);
}
// Convert points into seps.
if(pRetval)
{
pRetval->processPoints();
// Optimize list of bands. Adjacent bands with identical lists
// of seps are joined.
if(!pRetval->OptimizeBandList())
{
delete pRetval;
pRetval = 0;
}
}
}
}
}
return pRetval;
}
PolyPolygon Region::ImplCreatePolyPolygonFromRegionBand() const
{
PolyPolygon aRetval;
if(getRegionBand())
{
RectangleVector aRectangles;
GetRegionRectangles(aRectangles);
for(RectangleVector::const_iterator aRectIter(aRectangles.begin()); aRectIter != aRectangles.end(); aRectIter++)
{
aRetval.Insert(Polygon(*aRectIter));
}
}
else
{
OSL_ENSURE(false, "Called with no local RegionBand (!)");
}
return aRetval;
}
basegfx::B2DPolyPolygon Region::ImplCreateB2DPolyPolygonFromRegionBand() const
{
PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand());
return aPoly.getB2DPolyPolygon();
}
Region::Region(bool bIsNull)
: mpB2DPolyPolygon(),
mpPolyPolygon(),
mpRegionBand(),
mbIsNull(bIsNull)
{
}
Region::Region(const Rectangle& rRect)
: mpB2DPolyPolygon(),
mpPolyPolygon(),
mpRegionBand(),
mbIsNull(false)
{
mpRegionBand.reset(rRect.IsEmpty() ? 0 : new RegionBand(rRect));
}
Region::Region(const Polygon& rPolygon)
: mpB2DPolyPolygon(),
mpPolyPolygon(),
mpRegionBand(),
mbIsNull(false)
{
DBG_CHKOBJ( &rPolygon, Polygon, NULL );
if(rPolygon.GetSize())
{
ImplCreatePolyPolyRegion(rPolygon);
}
}
Region::Region(const PolyPolygon& rPolyPoly)
: mpB2DPolyPolygon(),
mpPolyPolygon(),
mpRegionBand(),
mbIsNull(false)
{
DBG_CHKOBJ( &rPolyPoly, PolyPolygon, NULL );
if(rPolyPoly.Count())
{
ImplCreatePolyPolyRegion(rPolyPoly);
}
}
Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
: mpB2DPolyPolygon(),
mpPolyPolygon(),
mpRegionBand(),
mbIsNull(false)
{
DBG_CHKOBJ( &rPolyPoly, PolyPolygon, NULL );
if(rPolyPoly.count())
{
ImplCreatePolyPolyRegion(rPolyPoly);
}
}
Region::Region(const Region& rRegion)
: mpB2DPolyPolygon(rRegion.mpB2DPolyPolygon),
mpPolyPolygon(rRegion.mpPolyPolygon),
mpRegionBand(rRegion.mpRegionBand),
mbIsNull(rRegion.mbIsNull)
{
}
Region::~Region()
{
}
void Region::ImplCreatePolyPolyRegion( const PolyPolygon& rPolyPoly )
{
const sal_uInt16 nPolyCount = rPolyPoly.Count();
if(nPolyCount)
{
// polypolygon empty? -> empty region
const Rectangle aRect(rPolyPoly.GetBoundRect());
if(!aRect.IsEmpty())
{
// width OR height == 1 ? => Rectangular region
if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
{
mpRegionBand.reset(new RegionBand(aRect));
}
else
{
mpPolyPolygon.reset(new PolyPolygon(rPolyPoly));
}
mbIsNull = false;
}
}
}
void Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
{
if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
{
mpB2DPolyPolygon.reset(new basegfx::B2DPolyPolygon(rPolyPoly));
mbIsNull = false;
}
}
void Region::Move( long nHorzMove, long nVertMove )
{
if(IsNull() || IsEmpty())
{
// empty or null need no move
return;
}
if(!nHorzMove && !nVertMove)
{
// no move defined
return;
}
if(getB2DPolyPolygon())
{
basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
aPoly.transform(basegfx::tools::createTranslateB2DHomMatrix(nHorzMove, nVertMove));
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : 0);
mpPolyPolygon.reset();
mpRegionBand.reset();
}
else if(getPolyPolygon())
{
PolyPolygon aPoly(*getPolyPolygon());
aPoly.Move(nHorzMove, nVertMove);
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset(aPoly.Count() ? new PolyPolygon(aPoly) : 0);
mpRegionBand.reset();
}
else if(getRegionBand())
{
RegionBand* pNew = new RegionBand(*getRegionBand());
pNew->Move(nHorzMove, nVertMove);
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset();
mpRegionBand.reset(pNew);
}
else
{
OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
}
}
void Region::Scale( double fScaleX, double fScaleY )
{
if(IsNull() || IsEmpty())
{
// empty or null need no scale
return;
}
if(basegfx::fTools::equalZero(fScaleX) && basegfx::fTools::equalZero(fScaleY))
{
// no scale defined
return;
}
if(getB2DPolyPolygon())
{
basegfx::B2DPolyPolygon aPoly(*getB2DPolyPolygon());
aPoly.transform(basegfx::tools::createScaleB2DHomMatrix(fScaleX, fScaleY));
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : 0);
mpPolyPolygon.reset();
mpRegionBand.reset();
}
else if(getPolyPolygon())
{
PolyPolygon aPoly(*getPolyPolygon());
aPoly.Scale(fScaleX, fScaleY);
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset(aPoly.Count() ? new PolyPolygon(aPoly) : 0);
mpRegionBand.reset();
}
else if(getRegionBand())
{
RegionBand* pNew = new RegionBand(*getRegionBand());
pNew->Scale(fScaleX, fScaleY);
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset();
mpRegionBand.reset(pNew);
}
else
{
OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
}
}
bool Region::Union( const Rectangle& rRect )
{
if(rRect.IsEmpty())
{
// empty rectangle will not expand the existing union, nothing to do
return true;
}
if(IsEmpty())
{
// no local data, the union will be equal to source. Create using rectangle
*this = rRect;
return true;
}
if(HasPolyPolygonOrB2DPolyPolygon())
{
// get this B2DPolyPolygon, solve on polygon base
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation(aThisPolyPoly);
if(!aThisPolyPoly.count())
{
// no local polygon, use the rectangle as new region
*this = rRect;
}
else
{
// get the other B2DPolyPolygon and use logical Or-Operation
const basegfx::B2DPolygon aRectPoly(
basegfx::tools::createPolygonFromRect(
basegfx::B2DRectangle(
rRect.Left(),
rRect.Top(),
rRect.Right(),
rRect.Bottom())));
const basegfx::B2DPolyPolygon aClip(
basegfx::tools::solvePolygonOperationOr(
aThisPolyPoly,
basegfx::B2DPolyPolygon(aRectPoly)));
*this = Region(aClip);
}
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// no region band, create using the rectangle
*this = rRect;
return true;
}
RegionBand* pNew = new RegionBand(*pCurrent);
// get justified rectangle
const long nLeft(std::min(rRect.Left(), rRect.Right()));
const long nTop(std::min(rRect.Top(), rRect.Bottom()));
const long nRight(std::max(rRect.Left(), rRect.Right()));
const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
// insert bands if the boundaries are not allready in the list
pNew->InsertBands(nTop, nBottom);
// process union
pNew->Union(nLeft, nTop, nRight, nBottom);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::Intersect( const Rectangle& rRect )
{
if ( rRect.IsEmpty() )
{
// empty rectangle will create empty region
SetEmpty();
return true;
}
if(IsNull())
{
// null region (everything) intersect with rect will give rect
*this = rRect;
return true;
}
if(IsEmpty())
{
// no content, cannot get more empty
return true;
}
if(HasPolyPolygonOrB2DPolyPolygon())
{
// if polygon data prefer double precision, the other will be lost (if buffered)
if(getB2DPolyPolygon())
{
const basegfx::B2DPolyPolygon aPoly(
basegfx::tools::clipPolyPolygonOnRange(
*getB2DPolyPolygon(),
basegfx::B2DRange(
rRect.Left(),
rRect.Top(),
rRect.Right() + 1,
rRect.Bottom() + 1),
true,
false));
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : 0);
mpPolyPolygon.reset();
mpRegionBand.reset();
}
else // if(getPolyPolygon())
{
PolyPolygon aPoly(*getPolyPolygon());
// use the PolyPolygon::Clip method for rectangles, this is
// fairly simple (does not even use GPC) and saves us from
// unnecessary banding
aPoly.Clip(rRect);
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset(aPoly.Count() ? new PolyPolygon(aPoly) : 0);
mpRegionBand.reset();
}
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// region is empty -> nothing to do!
return true;
}
RegionBand* pNew = new RegionBand(*pCurrent);
// get justified rectangle
const long nLeft(std::min(rRect.Left(), rRect.Right()));
const long nTop(std::min(rRect.Top(), rRect.Bottom()));
const long nRight(std::max(rRect.Left(), rRect.Right()));
const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
// insert bands if the boundaries are not allready in the list
pNew->InsertBands(nTop, nBottom);
// process intersect
pNew->Intersect(nLeft, nTop, nRight, nBottom);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::Exclude( const Rectangle& rRect )
{
if ( rRect.IsEmpty() )
{
// excluding nothing will do no change
return true;
}
if(IsEmpty())
{
// cannot exclude from empty, done
return true;
}
if(IsNull())
{
// error; cannnot exclude from null region since this is not representable
// in the data
OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
return true;
}
if( HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation(aThisPolyPoly);
if(!aThisPolyPoly.count())
{
// when local polygon is empty, nothing can be excluded
return true;
}
// get the other B2DPolyPolygon
const basegfx::B2DPolygon aRectPoly(
basegfx::tools::createPolygonFromRect(
basegfx::B2DRectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())));
const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
const basegfx::B2DPolyPolygon aClip = basegfx::tools::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);
*this = Region(aClip);
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// empty? -> done!
return true;
}
RegionBand* pNew = new RegionBand(*pCurrent);
// get justified rectangle
const long nLeft(std::min(rRect.Left(), rRect.Right()));
const long nTop(std::min(rRect.Top(), rRect.Bottom()));
const long nRight(std::max(rRect.Left(), rRect.Right()));
const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
// insert bands if the boundaries are not allready in the list
pNew->InsertBands(nTop, nBottom);
// process exclude
pNew->Exclude(nLeft, nTop, nRight, nBottom);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::XOr( const Rectangle& rRect )
{
if ( rRect.IsEmpty() )
{
// empty rectangle will not change local content
return true;
}
if(IsEmpty())
{
// rRect will be the xored-form (local off, rect on)
*this = rRect;
return true;
}
if(IsNull())
{
// error; cannnot exclude from null region since this is not representable
// in the data
OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
return true;
}
if( HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation( aThisPolyPoly );
if(!aThisPolyPoly.count())
{
// no local content, XOr will be equal to rectangle
*this = rRect;
return true;
}
// get the other B2DPolyPolygon
const basegfx::B2DPolygon aRectPoly(
basegfx::tools::createPolygonFromRect(
basegfx::B2DRectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())));
const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
const basegfx::B2DPolyPolygon aClip = basegfx::tools::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);
*this = Region(aClip);
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// rRect will be the xored-form (local off, rect on)
*this = rRect;
return true;
}
// only region band mode possibility left here or null/empty
RegionBand* pNew = new RegionBand(*getRegionBand());
// get justified rectangle
const long nLeft(std::min(rRect.Left(), rRect.Right()));
const long nTop(std::min(rRect.Top(), rRect.Bottom()));
const long nRight(std::max(rRect.Left(), rRect.Right()));
const long nBottom(std::max(rRect.Top(), rRect.Bottom()));
// insert bands if the boundaries are not allready in the list
pNew->InsertBands(nTop, nBottom);
// process xor
pNew->XOr(nLeft, nTop, nRight, nBottom);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::Union( const Region& rRegion )
{
if(rRegion.IsEmpty())
{
// no extension at all
return true;
}
if(rRegion.IsNull())
{
// extending with null region -> null region
*this = Region(true);
return true;
}
if(IsEmpty())
{
// local is empty, union will give source region
*this = rRegion;
return true;
}
if(IsNull())
{
// already fully expanded (is null region), cannot be extended
return true;
}
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation(aThisPolyPoly);
if(!aThisPolyPoly.count())
{
// when no local content, union will be equal to rRegion
*this = rRegion;
return true;
}
// get the other B2DPolyPolygon
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
aOtherPolyPoly = basegfx::tools::prepareForPolygonOperation(aOtherPolyPoly);
// use logical OR operation
basegfx::B2DPolyPolygon aClip(basegfx::tools::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly));
*this = Region( aClip );
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// local is empty, union will give source region
*this = rRegion;
return true;
}
const RegionBand* pSource = rRegion.getRegionBand();
if(!pSource)
{
// no extension at all
return true;
}
// prepare source and target
RegionBand* pNew = new RegionBand(*pCurrent);
// union with source
pNew->Union(*pSource);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::Intersect( const Region& rRegion )
{
// same instance data? -> nothing to do!
if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
{
return true;
}
if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
{
return true;
}
if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
{
return true;
}
if(rRegion.IsNull())
{
// source region is null-region, intersect will not change local region
return true;
}
if(IsNull())
{
// when local region is null-region, intersect will be equal to source
*this = rRegion;
return true;
}
if(rRegion.IsEmpty())
{
// source region is empty, intersection will always be empty
SetEmpty();
return true;
}
if(IsEmpty())
{
// local region is empty, cannot get more emty than that. Nothing to do
return true;
}
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
if(!aThisPolyPoly.count())
{
// local region is empty, cannot get more emty than that. Nothing to do
return true;
}
// get the other B2DPolyPolygon
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
if(!aOtherPolyPoly.count())
{
// source region is empty, intersection will always be empty
SetEmpty();
return true;
}
const basegfx::B2DPolyPolygon aClip(
basegfx::tools::clipPolyPolygonOnPolyPolygon(
aOtherPolyPoly,
aThisPolyPoly,
true,
false));
*this = Region( aClip );
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// local region is empty, cannot get more emty than that. Nothing to do
return true;
}
const RegionBand* pSource = rRegion.getRegionBand();
if(!pSource)
{
// source region is empty, intersection will always be empty
SetEmpty();
return true;
}
// both RegionBands exist and are not empty
if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount())
{
// when we have less rectangles, turn around the call
Region aTempRegion = rRegion;
aTempRegion.Intersect( *this );
*this = aTempRegion;
}
else
{
// prepare new regionBand
RegionBand* pNew = pCurrent ? new RegionBand(*pCurrent) : new RegionBand();
// intersect with source
pNew->Intersect(*pSource);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
}
return true;
}
bool Region::Exclude( const Region& rRegion )
{
if ( rRegion.IsEmpty() )
{
// excluding nothing will do no change
return true;
}
if ( rRegion.IsNull() )
{
// excluding everything will create empty region
SetEmpty();
return true;
}
if(IsEmpty())
{
// cannot exclude from empty, done
return true;
}
if(IsNull())
{
// error; cannnot exclude from null region since this is not representable
// in the data
OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
return true;
}
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
if(!aThisPolyPoly.count())
{
// cannot exclude from empty, done
return true;
}
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation( aThisPolyPoly );
// get the other B2DPolyPolygon
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
aOtherPolyPoly = basegfx::tools::prepareForPolygonOperation( aOtherPolyPoly );
basegfx::B2DPolyPolygon aClip = basegfx::tools::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly );
*this = Region( aClip );
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// cannot exclude from empty, done
return true;
}
const RegionBand* pSource = rRegion.getRegionBand();
if(!pSource)
{
// excluding nothing will do no change
return true;
}
// prepare source and target
RegionBand* pNew = new RegionBand(*pCurrent);
// union with source
const bool bSuccess(pNew->Exclude(*pSource));
// cleanup
if(!bSuccess)
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
bool Region::XOr( const Region& rRegion )
{
if ( rRegion.IsEmpty() )
{
// empty region will not change local content
return true;
}
if ( rRegion.IsNull() )
{
// error; cannnot exclude null region from local since this is not representable
// in the data
OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
return true;
}
if(IsEmpty())
{
// rRect will be the xored-form (local off, rect on)
*this = rRegion;
return true;
}
if(IsNull())
{
// error; cannnot exclude from null region since this is not representable
// in the data
OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
return false;
}
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
{
// get this B2DPolyPolygon
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
if(!aThisPolyPoly.count())
{
// rRect will be the xored-form (local off, rect on)
*this = rRegion;
return true;
}
aThisPolyPoly = basegfx::tools::prepareForPolygonOperation( aThisPolyPoly );
// get the other B2DPolyPolygon
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
aOtherPolyPoly = basegfx::tools::prepareForPolygonOperation( aOtherPolyPoly );
basegfx::B2DPolyPolygon aClip = basegfx::tools::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly );
*this = Region( aClip );
return true;
}
// only region band mode possibility left here or null/empty
const RegionBand* pCurrent = getRegionBand();
if(!pCurrent)
{
// rRect will be the xored-form (local off, rect on)
*this = rRegion;
return true;
}
const RegionBand* pSource = rRegion.getRegionBand();
if(!pSource)
{
// empty region will not change local content
return true;
}
// prepare source and target
RegionBand* pNew = new RegionBand(*pCurrent);
// union with source
pNew->XOr(*pSource);
// cleanup
if(!pNew->OptimizeBandList())
{
delete pNew;
pNew = 0;
}
mpRegionBand.reset(pNew);
return true;
}
Rectangle Region::GetBoundRect() const
{
if(IsEmpty())
{
// no internal data? -> region is empty!
return Rectangle();
}
if(IsNull())
{
// error; null region has no BoundRect
// OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimitied bound rect, not representable (!)");
return Rectangle();
}
// prefer double precision source
if(getB2DPolyPolygon())
{
const basegfx::B2DRange aRange(basegfx::tools::getRange(*getB2DPolyPolygon()));
if(aRange.isEmpty())
{
// emulate PolyPolygon::GetBoundRect() when empty polygon
return Rectangle();
}
else
{
// #122149# corrected rounding, no need for ceil() and floor() here
return Rectangle(
basegfx::fround(aRange.getMinX()), basegfx::fround(aRange.getMinY()),
basegfx::fround(aRange.getMaxX()), basegfx::fround(aRange.getMaxY()));
}
}
if(getPolyPolygon())
{
return getPolyPolygon()->GetBoundRect();
}
if(getRegionBand())
{
return getRegionBand()->GetBoundRect();
}
return Rectangle();
}
const PolyPolygon Region::GetAsPolyPolygon() const
{
if(getPolyPolygon())
{
return *getPolyPolygon();
}
if(getB2DPolyPolygon())
{
// the polygon needs to be converted, buffer the down converion
const PolyPolygon aPolyPolgon(*getB2DPolyPolygon());
const_cast< Region* >(this)->mpPolyPolygon.reset(new PolyPolygon(aPolyPolgon));
return *getPolyPolygon();
}
if(getRegionBand())
{
// the BandRegion needs to be converted, buffer the converion
const PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
const_cast< Region* >(this)->mpPolyPolygon.reset(new PolyPolygon(aPolyPolgon));
return *getPolyPolygon();
}
return PolyPolygon();
}
const basegfx::B2DPolyPolygon Region::GetAsB2DPolyPolygon() const
{
if(getB2DPolyPolygon())
{
return *getB2DPolyPolygon();
}
if(getPolyPolygon())
{
// the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
const basegfx::B2DPolyPolygon aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
const_cast< Region* >(this)->mpB2DPolyPolygon.reset(new basegfx::B2DPolyPolygon(aB2DPolyPolygon));
return *getB2DPolyPolygon();
}
if(getRegionBand())
{
// the BandRegion needs to be converted, buffer the converion
const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
const_cast< Region* >(this)->mpB2DPolyPolygon.reset(new basegfx::B2DPolyPolygon(aB2DPolyPolygon));
return *getB2DPolyPolygon();
}
return basegfx::B2DPolyPolygon();
}
const RegionBand* Region::GetAsRegionBand() const
{
if(!getRegionBand())
{
if(getB2DPolyPolygon())
{
// convert B2DPolyPolygon to RegionBand, buffer it and return it
const_cast< Region* >(this)->mpRegionBand.reset(ImplCreateRegionBandFromPolyPolygon(PolyPolygon(*getB2DPolyPolygon())));
}
else if(getPolyPolygon())
{
// convert B2DPolyPolygon to RegionBand, buffer it and return it
const_cast< Region* >(this)->mpRegionBand.reset(ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon()));
}
}
return getRegionBand();
}
bool Region::IsInside( const Point& rPoint ) const
{
if(IsEmpty())
{
// no point can be in empty region
return false;
}
if(IsNull())
{
// all points are inside null-region
return true;
}
// Too expensive (?)
//if(mpImplRegion->getRegionPolyPoly())
//{
// return mpImplRegion->getRegionPolyPoly()->IsInside( rPoint );
//}
// ensure RegionBand existance
const RegionBand* pRegionBand = GetAsRegionBand();
if(pRegionBand)
{
return pRegionBand->IsInside(rPoint);
}
return false;
}
bool Region::IsInside( const Rectangle& rRect ) const
{
if(IsEmpty())
{
// no rectangle can be in empty region
return false;
}
if(IsNull())
{
// rectangle always inside null-region
return true;
}
if ( rRect.IsEmpty() )
{
// is rectangle empty? -> not inside
return false;
}
// create region from rectangle and intersect own region
Region aRegion(rRect);
aRegion.Exclude(*this);
// rectangle is inside if exclusion is empty
return aRegion.IsEmpty();
}
// -----------------------------------------------------------------------
bool Region::IsOver( const Rectangle& rRect ) const
{
if(IsEmpty())
{
// nothing can be over something empty
return false;
}
if(IsNull())
{
// everything is over null region
return true;
}
// Can we optimize this ??? - is used in StarDraw for brushes pointers
// Why we have no IsOver for Regions ???
// create region from rectangle and intersect own region
Region aRegion(rRect);
aRegion.Intersect( *this );
// rectangle is over if include is not empty
return !aRegion.IsEmpty();
}
void Region::SetNull()
{
// reset all content
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset();
mpRegionBand.reset();
mbIsNull = true;
}
void Region::SetEmpty()
{
// reset all content
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset();
mpRegionBand.reset();
mbIsNull = false;
}
Region& Region::operator=( const Region& rRegion )
{
// reset all content
mpB2DPolyPolygon = rRegion.mpB2DPolyPolygon;
mpPolyPolygon = rRegion.mpPolyPolygon;
mpRegionBand = rRegion.mpRegionBand;
mbIsNull = rRegion.mbIsNull;
return *this;
}
Region& Region::operator=( const Rectangle& rRect )
{
mpB2DPolyPolygon.reset();
mpPolyPolygon.reset();
mpRegionBand.reset(rRect.IsEmpty() ? 0 : new RegionBand(rRect));
mbIsNull = false;
return *this;
}
bool Region::operator==( const Region& rRegion ) const
{
if(IsNull() && rRegion.IsNull())
{
// both are null region
return true;
}
if(IsEmpty() && rRegion.IsEmpty())
{
// both are empty
return true;
}
if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
{
// same instance data? -> equal
return true;
}
if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
{
// same instance data? -> equal
return true;
}
if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
{
// same instance data? -> equal
return true;
}
if(IsNull() || IsEmpty())
{
return false;
}
if(rRegion.IsNull() || rRegion.IsEmpty())
{
return false;
}
if(rRegion.getB2DPolyPolygon() || getB2DPolyPolygon())
{
// one of both has a B2DPolyPolygon based region, ensure both have it
// by evtl. conversion
const_cast< Region* >(this)->GetAsB2DPolyPolygon();
const_cast< Region& >(rRegion).GetAsB2DPolyPolygon();
return *rRegion.getB2DPolyPolygon() == *getB2DPolyPolygon();
}
if(rRegion.getPolyPolygon() || getPolyPolygon())
{
// one of both has a B2DPolyPolygon based region, ensure both have it
// by evtl. conversion
const_cast< Region* >(this)->GetAsPolyPolygon();
const_cast< Region& >(rRegion).GetAsPolyPolygon();
return *rRegion.getPolyPolygon() == *getPolyPolygon();
}
// both are not empty or null (see above) and if content supported polygon
// data the comparison is already done. Only both on RegionBand base can be left,
// but better check
if(rRegion.getRegionBand() && getRegionBand())
{
return *rRegion.getRegionBand() == *getRegionBand();
}
// should not happen, but better deny equality
return false;
}
SvStream& operator>>(SvStream& rIStrm, Region& rRegion)
{
VersionCompat aCompat(rIStrm, STREAM_READ);
sal_uInt16 nVersion(0);
sal_uInt16 nTmp16(0);
// clear region to be loaded
rRegion.SetEmpty();
// get version of streamed region
rIStrm >> nVersion;
// get type of region
rIStrm >> nTmp16;
enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
RegionType meStreamedType = (RegionType)nTmp16;
switch(meStreamedType)
{
case REGION_NULL:
{
rRegion.SetNull();
break;
}
case REGION_EMPTY:
{
rRegion.SetEmpty();
break;
}
default:
{
RegionBand* pNewRegionBand = new RegionBand();
pNewRegionBand->load(rIStrm);
rRegion.mpRegionBand.reset(pNewRegionBand);
if(aCompat.GetVersion() >= 2)
{
sal_Bool bHasPolyPolygon(sal_False);
rIStrm >> bHasPolyPolygon;
if(bHasPolyPolygon)
{
PolyPolygon* pNewPoly = new PolyPolygon();
rIStrm >> *pNewPoly;
rRegion.mpPolyPolygon.reset(pNewPoly);
}
}
break;
}
}
return rIStrm;
}
SvStream& operator<<( SvStream& rOStrm, const Region& rRegion )
{
const sal_uInt16 nVersion(2);
VersionCompat aCompat(rOStrm, STREAM_WRITE, nVersion);
// put version
rOStrm << nVersion;
// put type
enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
RegionType aRegionType(REGION_COMPLEX);
bool bEmpty(rRegion.IsEmpty());
if(!bEmpty && rRegion.getB2DPolyPolygon() && 0 == rRegion.getB2DPolyPolygon()->count())
{
OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
bEmpty = true;
}
if(!bEmpty && rRegion.getPolyPolygon() && 0 == rRegion.getPolyPolygon()->Count())
{
OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
bEmpty = true;
}
if(bEmpty)
{
aRegionType = REGION_EMPTY;
}
else if(rRegion.IsNull())
{
aRegionType = REGION_NULL;
}
else if(rRegion.getRegionBand() && rRegion.getRegionBand()->isSingleRectangle())
{
aRegionType = REGION_RECTANGLE;
}
rOStrm << (sal_uInt16)aRegionType;
// get RegionBand
const RegionBand* pRegionBand = rRegion.getRegionBand();
if(pRegionBand)
{
pRegionBand->save(rOStrm);
}
else
{
// for compatibility, write an empty RegionBand (will only write
// the end marker STREAMENTRY_END, but this *is* needed)
const RegionBand aRegionBand;
aRegionBand.save(rOStrm);
}
// write polypolygon if available
const sal_Bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon());
rOStrm << bHasPolyPolygon;
if(bHasPolyPolygon)
{
// #i105373#
PolyPolygon aNoCurvePolyPolygon;
rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
rOStrm << aNoCurvePolyPolygon;
}
return rOStrm;
}
void Region::GetRegionRectangles(RectangleVector& rTarget) const
{
// clear returnvalues
rTarget.clear();
// ensure RegionBand existance
const RegionBand* pRegionBand = GetAsRegionBand();
if(pRegionBand)
{
pRegionBand->GetRegionRectangles(rTarget);
}
}
static inline bool ImplPolygonRectTest( const Polygon& rPoly, Rectangle* pRectOut = NULL )
{
bool bIsRect = false;
const Point* pPoints = rPoly.GetConstPointAry();
sal_uInt16 nPoints = rPoly.GetSize();
if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) )
{
long nX1 = pPoints[0].X(), nX2 = pPoints[2].X(), nY1 = pPoints[0].Y(), nY2 = pPoints[2].Y();
if( ( (pPoints[1].X() == nX1 && pPoints[3].X() == nX2) && (pPoints[1].Y() == nY2 && pPoints[3].Y() == nY1) )
|| ( (pPoints[1].X() == nX2 && pPoints[3].X() == nX1) && (pPoints[1].Y() == nY1 && pPoints[3].Y() == nY2) ) )
{
bIsRect = true;
if( pRectOut )
{
long nSwap;
if( nX2 < nX1 )
{
nSwap = nX2;
nX2 = nX1;
nX1 = nSwap;
}
if( nY2 < nY1 )
{
nSwap = nY2;
nY2 = nY1;
nY1 = nSwap;
}
if( nX2 != nX1 )
{
nX2--;
}
if( nY2 != nY1 )
{
nY2--;
}
pRectOut->Left() = nX1;
pRectOut->Right() = nX2;
pRectOut->Top() = nY1;
pRectOut->Bottom() = nY2;
}
}
}
return bIsRect;
}
Region Region::GetRegionFromPolyPolygon( const PolyPolygon& rPolyPoly )
{
//return Region( rPolyPoly );
// check if it's worth extracting the XOr'ing the Rectangles
// empiricism shows that break even between XOr'ing rectangles separately
// and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
int nPolygonRects = 0, nPolygonPolygons = 0;
int nPolygons = rPolyPoly.Count();
for( sal_uInt16 i = 0; i < nPolygons; i++ )
{
const Polygon& rPoly = rPolyPoly[i];
if( ImplPolygonRectTest( rPoly ) )
{
nPolygonRects++;
}
else
{
nPolygonPolygons++;
}
}
if( nPolygonPolygons > nPolygonRects )
{
return Region( rPolyPoly );
}
Region aResult;
Rectangle aRect;
for( sal_uInt16 i = 0; i < nPolygons; i++ )
{
const Polygon& rPoly = rPolyPoly[i];
if( ImplPolygonRectTest( rPoly, &aRect ) )
{
aResult.XOr( aRect );
}
else
{
aResult.XOr( Region(rPoly) );
}
}
return aResult;
}
//////////////////////////////////////////////////////////////////////////////
// eof