diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/gdi/region.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/gdi/region.cxx')
-rw-r--r-- | vcl/source/gdi/region.cxx | 1778 |
1 files changed, 1778 insertions, 0 deletions
diff --git a/vcl/source/gdi/region.cxx b/vcl/source/gdi/region.cxx new file mode 100644 index 000000000..4ebabaa5b --- /dev/null +++ b/vcl/source/gdi/region.cxx @@ -0,0 +1,1778 @@ +/* -*- 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: */ |