/* -*- 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: */
↑ V530 The return value of function 'GetAsPolyPolygon' is required to be utilized.
↑ V530 The return value of function 'GetAsPolyPolygon' is required to be utilized.