fe6cce01c8
...where the get member function is defined on a std::__shared_ptr base class, so loplugin:simplifypointertobool used to miss those until now. (While e.g. using libc++ on macOS found those cases.)366d08f2f6
"new loplugin:simplifypointertobool" was mistaken in breaking isSmartPointerType(const clang::Type* t) out of isSmartPointerType(const Expr* e);c874294ad9
"Fix detection of std::unique_ptr/shared_ptr in loplugin:redundantpointerops" had introduced that indivisible two-step algorithm on purpose. The amount of additional hits (on Linux) apparently asked for turning loplugin:simplifypointertobool into a rewriting plugin. Which in turn showed that the naive adivce to just "drop the get()" is not sufficient in places that are not contextually converted to bool, as those places need to be wrapped in a bool(...) functional cast now. If the expression was already wrapped in parentheses, those could be reused as part of the functional cast, but implementing that showed that such cases are not yet found at all by the existing loplugin:simplifypointertobool. Lets leave that TODO for another commit. Besides the changes to compilerplugins/ itself, this change has been generated fully automatically with the rewriting plugin on Linux. Change-Id: I83107d6f634fc9ac232986f49044d7017df83e2a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94888 Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk> Reviewed-by: Stephan Bergmann <sbergman@redhat.com> Tested-by: Jenkins
1778 lines
49 KiB
C++
1778 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>
|
|
|
|
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>() );
|
|
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 long nTop (::std::min(aStart.Y(), aEnd.Y()));
|
|
const 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)
|
|
{
|
|
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)
|
|
: mpB2DPolyPolygon(),
|
|
mpPolyPolygon(),
|
|
mpRegionBand(),
|
|
mbIsNull(bIsNull)
|
|
{
|
|
}
|
|
|
|
Region::Region(const tools::Rectangle& rRect)
|
|
: mpB2DPolyPolygon(),
|
|
mpPolyPolygon(),
|
|
mpRegionBand(),
|
|
mbIsNull(false)
|
|
{
|
|
mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
|
|
}
|
|
|
|
Region::Region(const tools::Polygon& rPolygon)
|
|
: mpB2DPolyPolygon(),
|
|
mpPolyPolygon(),
|
|
mpRegionBand(),
|
|
mbIsNull(false)
|
|
{
|
|
|
|
if(rPolygon.GetSize())
|
|
{
|
|
ImplCreatePolyPolyRegion(rPolygon);
|
|
}
|
|
}
|
|
|
|
Region::Region(const tools::PolyPolygon& rPolyPoly)
|
|
: mpB2DPolyPolygon(),
|
|
mpPolyPolygon(),
|
|
mpRegionBand(),
|
|
mbIsNull(false)
|
|
{
|
|
|
|
if(rPolyPoly.Count())
|
|
{
|
|
ImplCreatePolyPolyRegion(rPolyPoly);
|
|
}
|
|
}
|
|
|
|
Region::Region(const basegfx::B2DPolyPolygon& rPolyPoly)
|
|
: mpB2DPolyPolygon(),
|
|
mpPolyPolygon(),
|
|
mpRegionBand(),
|
|
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)
|
|
{
|
|
// polypolygon empty? -> empty region
|
|
const tools::Rectangle aRect(rPolyPoly.GetBoundRect());
|
|
|
|
if(!aRect.IsEmpty())
|
|
{
|
|
// width OR height == 1 ? => Rectangular region
|
|
if((1 == aRect.GetWidth()) || (1 == aRect.GetHeight()) || rPolyPoly.IsRect())
|
|
{
|
|
mpRegionBand = std::make_shared<RegionBand>(aRect);
|
|
}
|
|
else
|
|
{
|
|
mpPolyPolygon = std::make_shared<tools::PolyPolygon>(rPolyPoly);
|
|
}
|
|
|
|
mbIsNull = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon& rPolyPoly )
|
|
{
|
|
if(rPolyPoly.count() && !rPolyPoly.getB2DRange().isEmpty())
|
|
{
|
|
mpB2DPolyPolygon = std::make_shared<basegfx::B2DPolyPolygon>(rPolyPoly);
|
|
mbIsNull = false;
|
|
}
|
|
}
|
|
|
|
void vcl::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::utils::createTranslateB2DHomMatrix(nHorzMove, nVertMove));
|
|
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getPolyPolygon())
|
|
{
|
|
tools::PolyPolygon aPoly(*getPolyPolygon());
|
|
|
|
aPoly.Move(nHorzMove, nVertMove);
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
|
|
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 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));
|
|
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
|
|
mpPolyPolygon.reset();
|
|
mpRegionBand.reset();
|
|
}
|
|
else if(getPolyPolygon())
|
|
{
|
|
tools::PolyPolygon aPoly(*getPolyPolygon());
|
|
|
|
aPoly.Scale(fScaleX, fScaleY);
|
|
mpB2DPolyPolygon.reset();
|
|
mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
|
|
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 (!)");
|
|
}
|
|
}
|
|
|
|
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 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 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));
|
|
|
|
mpB2DPolyPolygon.reset(aPoly.count() ? new basegfx::B2DPolyPolygon(aPoly) : nullptr);
|
|
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();
|
|
mpPolyPolygon.reset(aPoly.Count() ? new tools::PolyPolygon(aPoly) : nullptr);
|
|
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 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 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
|
|
const RegionBand* pCurrent = getRegionBand();
|
|
|
|
if(!pCurrent)
|
|
{
|
|
// empty? -> done!
|
|
return;
|
|
}
|
|
|
|
std::shared_ptr<RegionBand> pNew( std::make_shared<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 already in the list
|
|
pNew->InsertBands(nTop, nBottom);
|
|
|
|
// process exclude
|
|
pNew->Exclude(nLeft, nTop, nRight, nBottom);
|
|
|
|
// cleanup
|
|
if(!pNew->OptimizeBandList())
|
|
{
|
|
pNew.reset();
|
|
}
|
|
|
|
mpRegionBand = std::move(pNew);
|
|
}
|
|
|
|
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 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 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;
|
|
}
|
|
|
|
const basegfx::B2DPolyPolygon aClip(
|
|
basegfx::utils::clipPolyPolygonOnPolyPolygon(
|
|
aOtherPolyPoly,
|
|
aThisPolyPoly,
|
|
true,
|
|
false));
|
|
*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 = 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(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 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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<tools::PolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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 = std::make_shared<basegfx::B2DPolyPolygon>(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::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 existence
|
|
const RegionBand* pRegionBand = GetAsRegionBand();
|
|
|
|
if(pRegionBand)
|
|
{
|
|
return pRegionBand->IsInside(rPoint);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool vcl::Region::IsOver( 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();
|
|
mpRegionBand.reset(rRect.IsEmpty() ? nullptr : new RegionBand(rRect));
|
|
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)
|
|
{
|
|
VersionCompat aCompat(rIStrm, StreamMode::READ);
|
|
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 };
|
|
RegionType meStreamedType = static_cast<RegionType>(nTmp16);
|
|
|
|
switch(meStreamedType)
|
|
{
|
|
case REGION_NULL:
|
|
{
|
|
rRegion.SetNull();
|
|
break;
|
|
}
|
|
|
|
case REGION_EMPTY:
|
|
{
|
|
rRegion.SetEmpty();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
RegionBand* pNewRegionBand = new RegionBand();
|
|
bool bSuccess = pNewRegionBand->load(rIStrm);
|
|
rRegion.mpRegionBand.reset(pNewRegionBand);
|
|
|
|
bool bHasPolyPolygon(false);
|
|
if (aCompat.GetVersion() >= 2)
|
|
{
|
|
rIStrm.ReadCharAsBool( bHasPolyPolygon );
|
|
|
|
if (bHasPolyPolygon)
|
|
{
|
|
tools::PolyPolygon* pNewPoly = new tools::PolyPolygon();
|
|
ReadPolyPolygon( rIStrm, *pNewPoly );
|
|
rRegion.mpPolyPolygon.reset(pNewPoly);
|
|
}
|
|
}
|
|
|
|
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);
|
|
VersionCompat aCompat(rOStrm, StreamMode::WRITE, 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]) )
|
|
{
|
|
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->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: */
|