1793 lines
49 KiB
C++
1793 lines
49 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 <memory>
|
|
#include <tools/vcompat.hxx>
|
|
#include <tools/stream.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <sal/log.hxx>
|
|
#include <vcl/canvastools.hxx>
|
|
#include <vcl/region.hxx>
|
|
#include <regionband.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>
|
|
#include <tools/poly.hxx>
|
|
#include <comphelper/configuration.hxx>
|
|
|
|
namespace
|
|
{
|
|
/** Return <TRUE/> when the given polygon is rectilinear and oriented so that
|
|
all sides are either horizontal or vertical.
|
|
*/
|
|
bool ImplIsPolygonRectilinear (const tools::PolyPolygon& rPolyPoly)
|
|
{
|
|
// Iterate over all polygons.
|
|
const sal_uInt16 nPolyCount = rPolyPoly.Count();
|
|
for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
|
|
{
|
|
const tools::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.
|
|
*/
|
|
std::shared_ptr<RegionBand> ImplRectilinearPolygonToBands(const tools::PolyPolygon& rPolyPoly)
|
|
{
|
|
OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly));
|
|
|
|
// Create a new RegionBand object as container of the bands.
|
|
std::shared_ptr<RegionBand> pRegionBand( std::make_shared<RegionBand>() );
|
|
tools::Long nLineId = 0;
|
|
|
|
// Iterate over all polygons.
|
|
const sal_uInt16 nPolyCount = rPolyPoly.Count();
|
|
for (sal_uInt16 nPoly = 0; nPoly < nPolyCount; ++nPoly)
|
|
{
|
|
const tools::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 separately. The direction is calculated as it is
|
|
// in other places (but seems to be the wrong way.)
|
|
const tools::Long nTop (::std::min(aStart.Y(), aEnd.Y()));
|
|
const tools::Long nBottom (::std::max(aStart.Y(), aEnd.Y()));
|
|
const LineType eLineType (aStart.Y() > aEnd.Y() ? LineType::Descending : LineType::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!=nullptr && 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!=nullptr
|
|
// 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-1)
|
|
{
|
|
// Split the top band.
|
|
pTopBand = pBand->SplitBand(nTop);
|
|
}
|
|
|
|
// Advance to band that may contain nBottom.
|
|
while (pBand!=nullptr && 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!=nullptr
|
|
// 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-1)
|
|
{
|
|
// 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!=nullptr&&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.
|
|
*/
|
|
std::shared_ptr<RegionBand> ImplGeneralPolygonToBands(const tools::PolyPolygon& rPolyPoly, const tools::Rectangle& rPolygonBoundingBox)
|
|
{
|
|
tools::Long nLineID = 0;
|
|
|
|
// initialisation and creation of Bands
|
|
std::shared_ptr<RegionBand> pRegionBand( std::make_shared<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 tools::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 necessary
|
|
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
|
|
|
|
namespace vcl {
|
|
|
|
bool vcl::Region::IsEmpty() const
|
|
{
|
|
return !mbIsNull && !mpB2DPolyPolygon && !mpPolyPolygon && !mpRegionBand;
|
|
}
|
|
|
|
|
|
static std::shared_ptr<RegionBand> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon& rPolyPolygon)
|
|
{
|
|
std::shared_ptr<RegionBand> pRetval;
|
|
|
|
if(rPolyPolygon.Count())
|
|
{
|
|
// ensure to subdivide when bezier segments are used, it's going to
|
|
// be expanded to rectangles
|
|
tools::PolyPolygon aPolyPolygon;
|
|
|
|
rPolyPolygon.AdaptiveSubdivide(aPolyPolygon);
|
|
|
|
if(aPolyPolygon.Count())
|
|
{
|
|
const tools::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())
|
|
{
|
|
pRetval.reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pRetval;
|
|
}
|
|
|
|
tools::PolyPolygon vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
|
|
{
|
|
tools::PolyPolygon aRetval;
|
|
|
|
if(getRegionBand())
|
|
{
|
|
RectangleVector aRectangles;
|
|
GetRegionRectangles(aRectangles);
|
|
|
|
for (auto const& rectangle : aRectangles)
|
|
{
|
|
aRetval.Insert( tools::Polygon(rectangle) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE(false, "Called with no local RegionBand (!)");
|
|
}
|
|
|
|
return aRetval;
|
|
}
|
|
|
|
basegfx::B2DPolyPolygon vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
|
|
{
|
|
tools::PolyPolygon aPoly(ImplCreatePolyPolygonFromRegionBand());
|
|
|
|
return aPoly.getB2DPolyPolygon();
|
|
}
|
|
|
|
Region::Region(bool bIsNull)
|
|
: mbIsNull(bIsNull)
|
|
{
|
|
}
|
|
|
|
Region::Region(const tools::Rectangle& rRect)
|
|
: mbIsNull(false)
|
|
{
|
|
if (!rRect.IsEmpty())
|
|
mpRegionBand = std::make_shared<RegionBand>(rRect);
|
|
}
|
|
|
|
Region::Region(const tools::Polygon& rPolygon)
|
|
: mbIsNull(false)
|
|
{
|
|
|
|
if(rPolygon.GetSize())
|
|
{
|
|
ImplCreatePolyPolyRegion(tools::PolyPolygon(rPolygon));
|
|
}
|
|
}
|
|
|
|
Region::Region(const tools::PolyPolygon& rPolyPoly)
|
|
: mbIsNull(false)
|
|
{
|
|
|
|
if(rPolyPoly.Count())
|
|
{
|
|
ImplCreatePolyPolyRegion(rPolyPoly);
|
|
}
|
|
}
|
|
|
|
Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
|
|
: mbIsNull(false)
|
|
{
|
|
|
|
if(rPolyPoly.count())
|
|
{
|
|
ImplCreatePolyPolyRegion(rPolyPoly);
|
|
}
|
|
}
|
|
|
|
Region::Region(const vcl::Region&) = default;
|
|
|
|
Region::Region(vcl::Region&& rRegion) noexcept
|
|
: mpB2DPolyPolygon(std::move(rRegion.mpB2DPolyPolygon)),
|
|
mpPolyPolygon(std::move(rRegion.mpPolyPolygon)),
|
|
mpRegionBand(std::move(rRegion.mpRegionBand)),
|
|
mbIsNull(rRegion.mbIsNull)
|
|
{
|
|
rRegion.mbIsNull = true;
|
|
}
|
|
|
|
Region::~Region() = default;
|
|
|
|
void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon& rPolyPoly )
|
|
{
|
|
const sal_uInt16 nPolyCount = rPolyPoly.Count();
|
|
|
|
if(!nPolyCount)
|
|
return;
|
|
|
|
// polypolygon empty? -> empty region
|
|
const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
|
|
|
|
if(aRect.IsEmpty())
|
|
return;
|
|
|
|
// width OR height == 1 ? => Rectangular region
|
|
if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
|
|
{
|
|
mpRegionBand = std::make_shared<RegionBand>(aRect);
|
|
}
|
|
else
|
|
{
|
|
mpPolyPolygon = rPolyPoly;
|
|
}
|
|
|
|
mbIsNull = false;
|
|
}
|
|
|
|
void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
|
|
{
|
|
if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
|
|
{
|
|
mpB2DPolyPolygon = rPolyPoly;
|
|
mbIsNull = false;
|
|
}
|
|
}
|
|
|
|
void vcl::Region::Move( tools::Long nHorzMove, tools::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::utils::createTranslateB2DHomMatrix(nHorzMove, nVertMove));
|
|
if (aPoly.count())
|
|
mpB2DPolyPolygon = aPoly;
|
|
else
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getPolyPolygon())
|
|
{
|
|
tools::PolyPolygon aPoly(*getPolyPolygon());
|
|
|
|
aPoly.Move(nHorzMove, nVertMove);
|
|
mpB2DPolyPolygon.reset();
|
|
if (aPoly.Count())
|
|
mpPolyPolygon = aPoly;
|
|
else
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getRegionBand())
|
|
{
|
|
std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
|
|
|
|
pNew->Move(nHorzMove, nVertMove);
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
|
|
}
|
|
}
|
|
|
|
void vcl::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::utils::createScaleB2DHomMatrix(fScaleX, fScaleY));
|
|
if (aPoly.count())
|
|
mpB2DPolyPolygon = aPoly;
|
|
else
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getPolyPolygon())
|
|
{
|
|
tools::PolyPolygon aPoly(*getPolyPolygon());
|
|
|
|
aPoly.Scale(fScaleX, fScaleY);
|
|
mpB2DPolyPolygon.reset();
|
|
if (aPoly.Count())
|
|
mpPolyPolygon = aPoly;
|
|
else
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getRegionBand())
|
|
{
|
|
std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*getRegionBand());
|
|
|
|
pNew->Scale(fScaleX, fScaleY);
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
else
|
|
{
|
|
OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
|
|
}
|
|
}
|
|
|
|
void vcl::Region::Union( const tools::Rectangle& rRect )
|
|
{
|
|
if(rRect.IsEmpty())
|
|
{
|
|
// empty rectangle will not expand the existing union, nothing to do
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// no local data, the union will be equal to source. Create using rectangle
|
|
*this = rRect;
|
|
return;
|
|
}
|
|
|
|
if(HasPolyPolygonOrB2DPolyPolygon())
|
|
{
|
|
// get this B2DPolyPolygon, solve on polygon base
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
aThisPolyPoly = basegfx::utils::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::utils::createPolygonFromRect(
|
|
vcl::unotools::b2DRectangleFromRectangle(rRect)));
|
|
const basegfx::B2DPolyPolygon aClip(
|
|
basegfx::utils::solvePolygonOperationOr(
|
|
aThisPolyPoly,
|
|
basegfx::B2DPolyPolygon(aRectPoly)));
|
|
*this = vcl::Region(aClip);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
std::shared_ptr<RegionBand> pNew = std::make_shared<RegionBand>(*pCurrent);
|
|
|
|
// get justified rectangle
|
|
const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
|
|
const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
|
|
const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
|
|
const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
|
|
|
|
// insert bands if the boundaries are not already in the list
|
|
pNew->InsertBands(nTop, nBottom);
|
|
|
|
// process union
|
|
pNew->Union(nLeft, nTop, nRight, nBottom);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
void vcl::Region::Intersect( const tools::Rectangle& rRect )
|
|
{
|
|
if ( rRect.IsEmpty() )
|
|
{
|
|
// empty rectangle will create empty region
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// null region (everything) intersect with rect will give rect
|
|
*this = rRect;
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// no content, cannot get more empty
|
|
return;
|
|
}
|
|
|
|
if(HasPolyPolygonOrB2DPolyPolygon())
|
|
{
|
|
// if polygon data prefer double precision, the other will be lost (if buffered)
|
|
if(getB2DPolyPolygon())
|
|
{
|
|
const basegfx::B2DPolyPolygon aPoly(
|
|
basegfx::utils::clipPolyPolygonOnRange(
|
|
*getB2DPolyPolygon(),
|
|
basegfx::B2DRange(
|
|
rRect.Left(),
|
|
rRect.Top(),
|
|
rRect.Right() + 1,
|
|
rRect.Bottom() + 1),
|
|
true,
|
|
false));
|
|
|
|
if (aPoly.count())
|
|
mpB2DPolyPolygon = aPoly;
|
|
else
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else // if(getPolyPolygon())
|
|
{
|
|
tools::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();
|
|
if (aPoly.Count())
|
|
mpPolyPolygon = aPoly;
|
|
else
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// only region band mode possibility left here or null/empty
|
|
const RegionBand* pCurrent = getRegionBand();
|
|
|
|
if(!pCurrent)
|
|
{
|
|
// region is empty -> nothing to do!
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
|
|
|
|
// get justified rectangle
|
|
const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
|
|
const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
|
|
const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
|
|
const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
|
|
|
|
// insert bands if the boundaries are not already in the list
|
|
pNew->InsertBands(nTop, nBottom);
|
|
|
|
// process intersect
|
|
pNew->Intersect(nLeft, nTop, nRight, nBottom);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
void vcl::Region::Exclude( const tools::Rectangle& rRect )
|
|
{
|
|
if ( rRect.IsEmpty() )
|
|
{
|
|
// excluding nothing will do no change
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// cannot exclude from empty, done
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// error; cannot exclude from null region since this is not representable
|
|
// in the data
|
|
OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
|
|
return;
|
|
}
|
|
|
|
if( HasPolyPolygonOrB2DPolyPolygon() )
|
|
{
|
|
// get this B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
|
|
|
|
if(!aThisPolyPoly.count())
|
|
{
|
|
// when local polygon is empty, nothing can be excluded
|
|
return;
|
|
}
|
|
|
|
// get the other B2DPolyPolygon
|
|
const basegfx::B2DPolygon aRectPoly(
|
|
basegfx::utils::createPolygonFromRect(
|
|
vcl::unotools::b2DRectangleFromRectangle(rRect)));
|
|
const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
|
|
const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly, aOtherPolyPoly);
|
|
|
|
*this = vcl::Region(aClip);
|
|
|
|
return;
|
|
}
|
|
|
|
// only region band mode possibility left here or null/empty
|
|
if(!mpRegionBand)
|
|
{
|
|
// empty? -> done!
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<RegionBand>& pNew = mpRegionBand;
|
|
// only make a copy if someone else is also using it
|
|
if (pNew.use_count() > 1)
|
|
pNew = std::make_shared<RegionBand>(*pNew);
|
|
|
|
// get justified rectangle
|
|
const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
|
|
const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
|
|
const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
|
|
const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
|
|
|
|
// insert bands if the boundaries are not already in the list
|
|
pNew->InsertBands(nTop, nBottom);
|
|
|
|
// process exclude
|
|
pNew->Exclude(nLeft, nTop, nRight, nBottom);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
pNew.reset();
|
|
}
|
|
|
|
void vcl::Region::XOr( const tools::Rectangle& rRect )
|
|
{
|
|
if ( rRect.IsEmpty() )
|
|
{
|
|
// empty rectangle will not change local content
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// rRect will be the xored-form (local off, rect on)
|
|
*this = rRect;
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// error; cannot exclude from null region since this is not representable
|
|
// in the data
|
|
OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
|
|
return;
|
|
}
|
|
|
|
if( HasPolyPolygonOrB2DPolyPolygon() )
|
|
{
|
|
// get this B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
|
|
|
|
if(!aThisPolyPoly.count())
|
|
{
|
|
// no local content, XOr will be equal to rectangle
|
|
*this = rRect;
|
|
return;
|
|
}
|
|
|
|
// get the other B2DPolyPolygon
|
|
const basegfx::B2DPolygon aRectPoly(
|
|
basegfx::utils::createPolygonFromRect(
|
|
vcl::unotools::b2DRectangleFromRectangle(rRect)));
|
|
const basegfx::B2DPolyPolygon aOtherPolyPoly(aRectPoly);
|
|
const basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor(aThisPolyPoly, aOtherPolyPoly);
|
|
|
|
*this = vcl::Region(aClip);
|
|
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// only region band mode possibility left here or null/empty
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*getRegionBand()));
|
|
|
|
// get justified rectangle
|
|
const tools::Long nLeft(std::min(rRect.Left(), rRect.Right()));
|
|
const tools::Long nTop(std::min(rRect.Top(), rRect.Bottom()));
|
|
const tools::Long nRight(std::max(rRect.Left(), rRect.Right()));
|
|
const tools::Long nBottom(std::max(rRect.Top(), rRect.Bottom()));
|
|
|
|
// insert bands if the boundaries are not already in the list
|
|
pNew->InsertBands(nTop, nBottom);
|
|
|
|
// process xor
|
|
pNew->XOr(nLeft, nTop, nRight, nBottom);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
void vcl::Region::Union( const vcl::Region& rRegion )
|
|
{
|
|
if(rRegion.IsEmpty())
|
|
{
|
|
// no extension at all
|
|
return;
|
|
}
|
|
|
|
if(rRegion.IsNull())
|
|
{
|
|
// extending with null region -> null region
|
|
*this = vcl::Region(true);
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// local is empty, union will give source region
|
|
*this = rRegion;
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// already fully expanded (is null region), cannot be extended
|
|
return;
|
|
}
|
|
|
|
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
|
|
{
|
|
// get this B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
aThisPolyPoly = basegfx::utils::prepareForPolygonOperation(aThisPolyPoly);
|
|
|
|
if(!aThisPolyPoly.count())
|
|
{
|
|
// when no local content, union will be equal to rRegion
|
|
*this = rRegion;
|
|
return;
|
|
}
|
|
|
|
// get the other B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
|
|
aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly);
|
|
|
|
// use logical OR operation
|
|
basegfx::B2DPolyPolygon aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly, aOtherPolyPoly));
|
|
|
|
*this = vcl::Region( aClip );
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
const RegionBand* pSource = rRegion.getRegionBand();
|
|
|
|
if(!pSource)
|
|
{
|
|
// no extension at all
|
|
return;
|
|
}
|
|
|
|
// prepare source and target
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
|
|
|
|
// union with source
|
|
pNew->Union(*pSource);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
void vcl::Region::Intersect( const vcl::Region& rRegion )
|
|
{
|
|
// same instance data? -> nothing to do!
|
|
if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion.getB2DPolyPolygon())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(getPolyPolygon() && getPolyPolygon() == rRegion.getPolyPolygon())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(getRegionBand() && getRegionBand() == rRegion.getRegionBand())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(rRegion.IsNull())
|
|
{
|
|
// source region is null-region, intersect will not change local region
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// when local region is null-region, intersect will be equal to source
|
|
*this = rRegion;
|
|
return;
|
|
}
|
|
|
|
if(rRegion.IsEmpty())
|
|
{
|
|
// source region is empty, intersection will always be empty
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// local region is empty, cannot get more empty than that. Nothing to do
|
|
return;
|
|
}
|
|
|
|
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
|
|
{
|
|
// get this B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
if(!aThisPolyPoly.count())
|
|
{
|
|
// local region is empty, cannot get more empty than that. Nothing to do
|
|
return;
|
|
}
|
|
|
|
// get the other B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
|
|
|
|
if(!aOtherPolyPoly.count())
|
|
{
|
|
// source region is empty, intersection will always be empty
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
static size_t gPointLimit = !comphelper::IsFuzzing() ? SAL_MAX_SIZE : 8192;
|
|
size_t nPointLimit(gPointLimit);
|
|
const basegfx::B2DPolyPolygon aClip(
|
|
basegfx::utils::clipPolyPolygonOnPolyPolygon(
|
|
aOtherPolyPoly,
|
|
aThisPolyPoly,
|
|
true,
|
|
false,
|
|
&nPointLimit));
|
|
*this = vcl::Region( aClip );
|
|
return;
|
|
}
|
|
|
|
// only region band mode possibility left here or null/empty
|
|
const RegionBand* pCurrent = getRegionBand();
|
|
|
|
if(!pCurrent)
|
|
{
|
|
// local region is empty, cannot get more empty than that. Nothing to do
|
|
return;
|
|
}
|
|
|
|
const RegionBand* pSource = rRegion.getRegionBand();
|
|
|
|
if(!pSource)
|
|
{
|
|
// source region is empty, intersection will always be empty
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
// both RegionBands exist and are not empty
|
|
if(pCurrent->getRectangleCount() + 2 < pSource->getRectangleCount())
|
|
{
|
|
// when we have less rectangles, turn around the call
|
|
vcl::Region aTempRegion = rRegion;
|
|
aTempRegion.Intersect( *this );
|
|
*this = std::move(aTempRegion);
|
|
}
|
|
else
|
|
{
|
|
// prepare new regionBand
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
|
|
|
|
// intersect with source
|
|
pNew->Intersect(*pSource);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
}
|
|
|
|
void vcl::Region::Exclude( const vcl::Region& rRegion )
|
|
{
|
|
if ( rRegion.IsEmpty() )
|
|
{
|
|
// excluding nothing will do no change
|
|
return;
|
|
}
|
|
|
|
if ( rRegion.IsNull() )
|
|
{
|
|
// excluding everything will create empty region
|
|
SetEmpty();
|
|
return;
|
|
}
|
|
|
|
if(IsEmpty())
|
|
{
|
|
// cannot exclude from empty, done
|
|
return;
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// error; cannot exclude from null region since this is not representable
|
|
// in the data
|
|
OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
|
|
return;
|
|
}
|
|
|
|
if( rRegion.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
|
|
{
|
|
// get this B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aThisPolyPoly(GetAsB2DPolyPolygon());
|
|
|
|
if(!aThisPolyPoly.count())
|
|
{
|
|
// cannot exclude from empty, done
|
|
return;
|
|
}
|
|
|
|
aThisPolyPoly = basegfx::utils::prepareForPolygonOperation( aThisPolyPoly );
|
|
|
|
// get the other B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
|
|
aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
|
|
|
|
basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly, aOtherPolyPoly );
|
|
*this = vcl::Region( aClip );
|
|
return;
|
|
}
|
|
|
|
// only region band mode possibility left here or null/empty
|
|
const RegionBand* pCurrent = getRegionBand();
|
|
|
|
if(!pCurrent)
|
|
{
|
|
// cannot exclude from empty, done
|
|
return;
|
|
}
|
|
|
|
const RegionBand* pSource = rRegion.getRegionBand();
|
|
|
|
if(!pSource)
|
|
{
|
|
// excluding nothing will do no change
|
|
return;
|
|
}
|
|
|
|
// prepare source and target
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
|
|
|
|
// union with source
|
|
const bool bSuccess(pNew->Exclude(*pSource));
|
|
|
|
// cleanup
|
|
if(!bSuccess)
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
bool vcl::Region::XOr( const vcl::Region& rRegion )
|
|
{
|
|
if ( rRegion.IsEmpty() )
|
|
{
|
|
// empty region will not change local content
|
|
return true;
|
|
}
|
|
|
|
if ( rRegion.IsNull() )
|
|
{
|
|
// error; cannot 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: cannot 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::utils::prepareForPolygonOperation( aThisPolyPoly );
|
|
|
|
// get the other B2DPolyPolygon
|
|
basegfx::B2DPolyPolygon aOtherPolyPoly(rRegion.GetAsB2DPolyPolygon());
|
|
aOtherPolyPoly = basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly );
|
|
|
|
basegfx::B2DPolyPolygon aClip = basegfx::utils::solvePolygonOperationXor( aThisPolyPoly, aOtherPolyPoly );
|
|
*this = vcl::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
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<RegionBand>(*pCurrent));
|
|
|
|
// union with source
|
|
pNew->XOr(*pSource);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
|
|
return true;
|
|
}
|
|
|
|
tools::Rectangle vcl::Region::GetBoundRect() const
|
|
{
|
|
if(IsEmpty())
|
|
{
|
|
// no internal data? -> region is empty!
|
|
return tools::Rectangle();
|
|
}
|
|
|
|
if(IsNull())
|
|
{
|
|
// error; null region has no BoundRect
|
|
// OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
|
|
return tools::Rectangle();
|
|
}
|
|
|
|
// prefer double precision source
|
|
if(getB2DPolyPolygon())
|
|
{
|
|
const basegfx::B2DRange aRange(basegfx::utils::getRange(*getB2DPolyPolygon()));
|
|
|
|
if(aRange.isEmpty())
|
|
{
|
|
// emulate PolyPolygon::GetBoundRect() when empty polygon
|
|
return tools::Rectangle();
|
|
}
|
|
else
|
|
{
|
|
// #i122149# corrected rounding, no need for ceil() and floor() here
|
|
return tools::Rectangle(
|
|
basegfx::fround<tools::Long>(aRange.getMinX()), basegfx::fround<tools::Long>(aRange.getMinY()),
|
|
basegfx::fround<tools::Long>(aRange.getMaxX()), basegfx::fround<tools::Long>(aRange.getMaxY()));
|
|
}
|
|
}
|
|
|
|
if(getPolyPolygon())
|
|
{
|
|
return getPolyPolygon()->GetBoundRect();
|
|
}
|
|
|
|
if(getRegionBand())
|
|
{
|
|
return getRegionBand()->GetBoundRect();
|
|
}
|
|
|
|
return tools::Rectangle();
|
|
}
|
|
|
|
tools::PolyPolygon vcl::Region::GetAsPolyPolygon() const
|
|
{
|
|
if(getPolyPolygon())
|
|
{
|
|
return *getPolyPolygon();
|
|
}
|
|
|
|
if(getB2DPolyPolygon())
|
|
{
|
|
// the polygon needs to be converted, buffer the down conversion
|
|
const tools::PolyPolygon aPolyPolgon(*getB2DPolyPolygon());
|
|
const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
|
|
|
|
return *getPolyPolygon();
|
|
}
|
|
|
|
if(getRegionBand())
|
|
{
|
|
// the BandRegion needs to be converted, buffer the conversion
|
|
const tools::PolyPolygon aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
|
|
const_cast< vcl::Region* >(this)->mpPolyPolygon = aPolyPolgon;
|
|
|
|
return *getPolyPolygon();
|
|
}
|
|
|
|
return tools::PolyPolygon();
|
|
}
|
|
|
|
basegfx::B2DPolyPolygon vcl::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< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
|
|
|
|
return *getB2DPolyPolygon();
|
|
}
|
|
|
|
if(getRegionBand())
|
|
{
|
|
// the BandRegion needs to be converted, buffer the conversion
|
|
const basegfx::B2DPolyPolygon aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
|
|
const_cast< vcl::Region* >(this)->mpB2DPolyPolygon = aB2DPolyPolygon;
|
|
|
|
return *getB2DPolyPolygon();
|
|
}
|
|
|
|
return basegfx::B2DPolyPolygon();
|
|
}
|
|
|
|
const RegionBand* vcl::Region::GetAsRegionBand() const
|
|
{
|
|
if(!getRegionBand())
|
|
{
|
|
if(getB2DPolyPolygon())
|
|
{
|
|
// convert B2DPolyPolygon to RegionBand, buffer it and return it
|
|
const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
|
|
}
|
|
else if(getPolyPolygon())
|
|
{
|
|
// convert B2DPolyPolygon to RegionBand, buffer it and return it
|
|
const_cast< vcl::Region* >(this)->mpRegionBand = ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
|
|
}
|
|
}
|
|
|
|
return getRegionBand();
|
|
}
|
|
|
|
bool vcl::Region::Contains( 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()->Contains( rPoint );
|
|
//}
|
|
|
|
// ensure RegionBand existence
|
|
const RegionBand* pRegionBand = GetAsRegionBand();
|
|
|
|
if(pRegionBand)
|
|
{
|
|
return pRegionBand->Contains(rPoint);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool vcl::Region::Overlaps( const tools::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
|
|
vcl::Region aRegion(rRect);
|
|
aRegion.Intersect( *this );
|
|
|
|
// rectangle is over if include is not empty
|
|
return !aRegion.IsEmpty();
|
|
}
|
|
|
|
bool vcl::Region::IsRectangle() const
|
|
{
|
|
if( IsEmpty() || IsNull() )
|
|
return false;
|
|
|
|
if( getB2DPolyPolygon() )
|
|
return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
|
|
|
|
if( getPolyPolygon() )
|
|
return getPolyPolygon()->IsRect();
|
|
|
|
if( getRegionBand() )
|
|
return (getRegionBand()->getRectangleCount() == 1);
|
|
|
|
return false;
|
|
}
|
|
|
|
void vcl::Region::SetNull()
|
|
{
|
|
// reset all content
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
mbIsNull = true;
|
|
}
|
|
|
|
void vcl::Region::SetEmpty()
|
|
{
|
|
// reset all content
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
mbIsNull = false;
|
|
}
|
|
|
|
Region& vcl::Region::operator=( const vcl::Region& ) = default;
|
|
|
|
Region& vcl::Region::operator=( vcl::Region&& rRegion ) noexcept
|
|
{
|
|
mpB2DPolyPolygon = std::move(rRegion.mpB2DPolyPolygon);
|
|
mpPolyPolygon = std::move(rRegion.mpPolyPolygon);
|
|
mpRegionBand = std::move(rRegion.mpRegionBand);
|
|
mbIsNull = rRegion.mbIsNull;
|
|
rRegion.mbIsNull = true;
|
|
|
|
return *this;
|
|
}
|
|
|
|
Region& vcl::Region::operator=( const tools::Rectangle& rRect )
|
|
{
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset();
|
|
if (!rRect.IsEmpty())
|
|
mpRegionBand = std::make_shared<RegionBand>(rRect);
|
|
else
|
|
mpRegionBand.reset();
|
|
mbIsNull = false;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool vcl::Region::operator==( const vcl::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
|
|
GetAsB2DPolyPolygon();
|
|
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
|
|
GetAsPolyPolygon();
|
|
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& ReadRegion(SvStream& rIStrm, vcl::Region& rRegion)
|
|
{
|
|
VersionCompatRead aCompat(rIStrm);
|
|
sal_uInt16 nVersion(0);
|
|
sal_uInt16 nTmp16(0);
|
|
|
|
// clear region to be loaded
|
|
rRegion.SetEmpty();
|
|
|
|
// get version of streamed region
|
|
rIStrm.ReadUInt16( nVersion );
|
|
|
|
// get type of region
|
|
rIStrm.ReadUInt16( nTmp16 );
|
|
|
|
enum RegionType { REGION_NULL, REGION_EMPTY, REGION_RECTANGLE, REGION_COMPLEX };
|
|
auto eStreamedType = nTmp16;
|
|
|
|
switch (eStreamedType)
|
|
{
|
|
case REGION_NULL:
|
|
{
|
|
rRegion.SetNull();
|
|
break;
|
|
}
|
|
|
|
case REGION_EMPTY:
|
|
{
|
|
rRegion.SetEmpty();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
std::shared_ptr<RegionBand> xNewRegionBand(std::make_shared<RegionBand>());
|
|
bool bSuccess = xNewRegionBand->load(rIStrm);
|
|
rRegion.mpRegionBand = std::move(xNewRegionBand);
|
|
|
|
bool bHasPolyPolygon(false);
|
|
if (aCompat.GetVersion() >= 2)
|
|
{
|
|
rIStrm.ReadCharAsBool( bHasPolyPolygon );
|
|
|
|
if (bHasPolyPolygon)
|
|
{
|
|
tools::PolyPolygon aNewPoly;
|
|
ReadPolyPolygon(rIStrm, aNewPoly);
|
|
const auto nPolygons = aNewPoly.Count();
|
|
if (nPolygons > 128)
|
|
{
|
|
SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons);
|
|
if (comphelper::IsFuzzing())
|
|
aNewPoly.Clear();
|
|
}
|
|
rRegion.mpPolyPolygon = aNewPoly;
|
|
}
|
|
}
|
|
|
|
if (!bSuccess && !bHasPolyPolygon)
|
|
{
|
|
SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon);
|
|
rRegion.SetNull();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rIStrm;
|
|
}
|
|
|
|
SvStream& WriteRegion( SvStream& rOStrm, const vcl::Region& rRegion )
|
|
{
|
|
const sal_uInt16 nVersion(2);
|
|
VersionCompatWrite aCompat(rOStrm, nVersion);
|
|
|
|
// put version
|
|
rOStrm.WriteUInt16( 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.WriteUInt16( 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 bool bHasPolyPolygon(rRegion.HasPolyPolygonOrB2DPolyPolygon());
|
|
rOStrm.WriteBool( bHasPolyPolygon );
|
|
|
|
if(bHasPolyPolygon)
|
|
{
|
|
// #i105373#
|
|
tools::PolyPolygon aNoCurvePolyPolygon;
|
|
rRegion.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon);
|
|
|
|
WritePolyPolygon( rOStrm, aNoCurvePolyPolygon );
|
|
}
|
|
|
|
return rOStrm;
|
|
}
|
|
|
|
void vcl::Region::GetRegionRectangles(RectangleVector& rTarget) const
|
|
{
|
|
// clear returnvalues
|
|
rTarget.clear();
|
|
|
|
// ensure RegionBand existence
|
|
const RegionBand* pRegionBand = GetAsRegionBand();
|
|
|
|
if(pRegionBand)
|
|
{
|
|
pRegionBand->GetRegionRectangles(rTarget);
|
|
}
|
|
}
|
|
|
|
static bool ImplPolygonRectTest( const tools::Polygon& rPoly, tools::Rectangle* pRectOut = nullptr )
|
|
{
|
|
bool bIsRect = false;
|
|
const Point* pPoints = rPoly.GetConstPointAry();
|
|
sal_uInt16 nPoints = rPoly.GetSize();
|
|
|
|
if( nPoints == 4 || (nPoints == 5 && pPoints[0] == pPoints[4]) )
|
|
{
|
|
tools::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 )
|
|
{
|
|
tools::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->SetLeft( nX1 );
|
|
pRectOut->SetRight( nX2 );
|
|
pRectOut->SetTop( nY1 );
|
|
pRectOut->SetBottom( nY2 );
|
|
}
|
|
}
|
|
}
|
|
|
|
return bIsRect;
|
|
}
|
|
|
|
vcl::Region vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon& rPolyPoly )
|
|
{
|
|
//return vcl::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( int i = 0; i < nPolygons; i++ )
|
|
{
|
|
const tools::Polygon& rPoly = rPolyPoly[i];
|
|
|
|
if( ImplPolygonRectTest( rPoly ) )
|
|
{
|
|
nPolygonRects++;
|
|
}
|
|
else
|
|
{
|
|
nPolygonPolygons++;
|
|
}
|
|
}
|
|
|
|
if( nPolygonPolygons > nPolygonRects )
|
|
{
|
|
return vcl::Region( rPolyPoly );
|
|
}
|
|
|
|
vcl::Region aResult;
|
|
tools::Rectangle aRect;
|
|
|
|
for( int i = 0; i < nPolygons; i++ )
|
|
{
|
|
const tools::Polygon& rPoly = rPolyPoly[i];
|
|
|
|
if( ImplPolygonRectTest( rPoly, &aRect ) )
|
|
{
|
|
aResult.XOr( aRect );
|
|
}
|
|
else
|
|
{
|
|
aResult.XOr( vcl::Region(rPoly) );
|
|
}
|
|
}
|
|
|
|
return aResult;
|
|
}
|
|
|
|
} /* namespace vcl */
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|