1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <tools/vcompat.hxx>
22 #include <tools/stream.hxx>
23 #include <osl/diagnose.h>
24 #include <sal/log.hxx>
25 #include <vcl/canvastools.hxx>
26 #include <vcl/region.hxx>
27 #include <regionband.hxx>
29 #include <basegfx/polygon/b2dpolypolygontools.hxx>
30 #include <basegfx/polygon/b2dpolygontools.hxx>
31 #include <basegfx/polygon/b2dpolygonclipper.hxx>
32 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
33 #include <basegfx/range/b2drange.hxx>
34 #include <basegfx/matrix/b2dhommatrixtools.hxx>
35 #include <tools/poly.hxx>
36 #include <comphelper/configuration.hxx>
40 /** Return <TRUE/> when the given polygon is rectilinear and oriented so that
41 all sides are either horizontal or vertical.
43 bool ImplIsPolygonRectilinear (const tools::PolyPolygon
& rPolyPoly
)
45 // Iterate over all polygons.
46 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
47 for (sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; ++nPoly
)
49 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject(nPoly
);
51 // Iterate over all edges of the current polygon.
52 const sal_uInt16 nSize
= aPoly
.GetSize();
56 Point
aPoint (aPoly
.GetPoint(0));
57 const Point
aLastPoint (aPoint
);
58 for (sal_uInt16 nPoint
= 1; nPoint
< nSize
; ++nPoint
)
60 const Point
aNextPoint (aPoly
.GetPoint(nPoint
));
61 // When there is at least one edge that is neither vertical nor
62 // horizontal then the entire polygon is not rectilinear (and
63 // oriented along primary axes.)
64 if (aPoint
.X() != aNextPoint
.X() && aPoint
.Y() != aNextPoint
.Y())
69 // Compare closing edge.
70 if (aLastPoint
.X() != aPoint
.X() && aLastPoint
.Y() != aPoint
.Y())
76 /** Convert a rectilinear polygon (that is oriented along the primary axes)
77 to a list of bands. For this special form of polygon we can use an
78 optimization that prevents the creation of one band per y value.
79 However, it still is possible that some temporary bands are created that
80 later can be optimized away.
82 A set of zero, one, or more polygons, nested or not, that are
83 converted into a list of bands.
85 A new RegionBand object is returned that contains the bands that
86 represent the given poly-polygon.
88 std::shared_ptr
<RegionBand
> ImplRectilinearPolygonToBands(const tools::PolyPolygon
& rPolyPoly
)
90 OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly
));
92 // Create a new RegionBand object as container of the bands.
93 std::shared_ptr
<RegionBand
> pRegionBand( std::make_shared
<RegionBand
>() );
94 tools::Long nLineId
= 0;
96 // Iterate over all polygons.
97 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
98 for (sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; ++nPoly
)
100 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject(nPoly
);
102 // Iterate over all edges of the current polygon.
103 const sal_uInt16 nSize
= aPoly
.GetSize();
106 // Avoid fetching every point twice (each point is the start point
107 // of one and the end point of another edge.)
108 Point
aStart (aPoly
.GetPoint(0));
110 for (sal_uInt16 nPoint
= 1; nPoint
<= nSize
; ++nPoint
, aStart
=aEnd
)
112 // We take the implicit closing edge into account by mapping
114 aEnd
= aPoly
.GetPoint(nPoint
%nSize
);
115 if (aStart
.Y() == aEnd
.Y())
117 // Horizontal lines are ignored.
121 // At this point the line has to be vertical.
122 OSL_ASSERT(aStart
.X() == aEnd
.X());
124 // Sort y-coordinates to simplify the algorithm and store the
125 // direction separately. The direction is calculated as it is
126 // in other places (but seems to be the wrong way.)
127 const tools::Long
nTop (::std::min(aStart
.Y(), aEnd
.Y()));
128 const tools::Long
nBottom (::std::max(aStart
.Y(), aEnd
.Y()));
129 const LineType
eLineType (aStart
.Y() > aEnd
.Y() ? LineType::Descending
: LineType::Ascending
);
131 // Make sure that the current line is covered by bands.
132 pRegionBand
->ImplAddMissingBands(nTop
,nBottom
);
134 // Find top-most band that may contain nTop.
135 ImplRegionBand
* pBand
= pRegionBand
->ImplGetFirstRegionBand();
136 while (pBand
!=nullptr && pBand
->mnYBottom
< nTop
)
137 pBand
= pBand
->mpNextBand
;
138 ImplRegionBand
* pTopBand
= pBand
;
139 // If necessary split the band at nTop so that nTop is contained
140 // in the lower band.
142 // Prevent the current band from becoming 0 pixel high
143 && pBand
->mnYTop
<nTop
144 // this allows the lowest pixel of the band to be split off
145 && pBand
->mnYBottom
>=nTop
146 // do not split a band that is just one pixel high
147 && pBand
->mnYTop
<pBand
->mnYBottom
-1)
149 // Split the top band.
150 pTopBand
= pBand
->SplitBand(nTop
);
153 // Advance to band that may contain nBottom.
154 while (pBand
!=nullptr && pBand
->mnYBottom
< nBottom
)
155 pBand
= pBand
->mpNextBand
;
156 // The lowest band may have to be split at nBottom so that
157 // nBottom itself remains in the upper band.
159 // allow the current band becoming 1 pixel high
160 && pBand
->mnYTop
<=nBottom
161 // prevent splitting off a band that is 0 pixel high
162 && pBand
->mnYBottom
>nBottom
163 // do not split a band that is just one pixel high
164 && pBand
->mnYTop
<pBand
->mnYBottom
-1)
166 // Split the bottom band.
167 pBand
->SplitBand(nBottom
+1);
170 // Note that we remember the top band (in pTopBand) but not the
171 // bottom band. The later can be determined by comparing y
174 // Add the x-value as point to all bands in the nTop->nBottom range.
175 for (pBand
=pTopBand
; pBand
!=nullptr&&pBand
->mnYTop
<=nBottom
; pBand
=pBand
->mpNextBand
)
176 pBand
->InsertPoint(aStart
.X(), nLineId
++, true, eLineType
);
183 /** Convert a general polygon (one for which ImplIsPolygonRectilinear()
184 returns <FALSE/>) to bands.
186 std::shared_ptr
<RegionBand
> ImplGeneralPolygonToBands(const tools::PolyPolygon
& rPolyPoly
, const tools::Rectangle
& rPolygonBoundingBox
)
188 tools::Long nLineID
= 0;
190 // initialisation and creation of Bands
191 std::shared_ptr
<RegionBand
> pRegionBand( std::make_shared
<RegionBand
>() );
192 pRegionBand
->CreateBandRange(rPolygonBoundingBox
.Top(), rPolygonBoundingBox
.Bottom());
195 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
197 for ( sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++ )
199 // get reference to current polygon
200 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject( nPoly
);
201 const sal_uInt16 nSize
= aPoly
.GetSize();
203 // not enough points ( <= 2 )? -> nothing to do!
208 for ( sal_uInt16 nPoint
= 1; nPoint
< nSize
; nPoint
++ )
210 pRegionBand
->InsertLine( aPoly
.GetPoint(nPoint
-1), aPoly
.GetPoint(nPoint
), nLineID
++ );
213 // close polygon with line from first point to last point, if necessary
214 const Point rLastPoint
= aPoly
.GetPoint(nSize
-1);
215 const Point rFirstPoint
= aPoly
.GetPoint(0);
217 if ( rLastPoint
!= rFirstPoint
)
219 pRegionBand
->InsertLine( rLastPoint
, rFirstPoint
, nLineID
++ );
225 } // end of anonymous namespace
229 bool vcl::Region::IsEmpty() const
231 return !mbIsNull
&& !mpB2DPolyPolygon
&& !mpPolyPolygon
&& !mpRegionBand
;
235 static std::shared_ptr
<RegionBand
> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon
& rPolyPolygon
)
237 std::shared_ptr
<RegionBand
> pRetval
;
239 if(rPolyPolygon
.Count())
241 // ensure to subdivide when bezier segments are used, it's going to
242 // be expanded to rectangles
243 tools::PolyPolygon aPolyPolygon
;
245 rPolyPolygon
.AdaptiveSubdivide(aPolyPolygon
);
247 if(aPolyPolygon
.Count())
249 const tools::Rectangle
aRect(aPolyPolygon
.GetBoundRect());
253 if(ImplIsPolygonRectilinear(aPolyPolygon
))
255 // For rectilinear polygons there is an optimized band conversion.
256 pRetval
= ImplRectilinearPolygonToBands(aPolyPolygon
);
260 pRetval
= ImplGeneralPolygonToBands(aPolyPolygon
, aRect
);
263 // Convert points into seps.
266 pRetval
->processPoints();
268 // Optimize list of bands. Adjacent bands with identical lists
269 // of seps are joined.
270 if(!pRetval
->OptimizeBandList())
282 tools::PolyPolygon
vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
284 tools::PolyPolygon aRetval
;
288 RectangleVector aRectangles
;
289 GetRegionRectangles(aRectangles
);
291 for (auto const& rectangle
: aRectangles
)
293 aRetval
.Insert( tools::Polygon(rectangle
) );
298 OSL_ENSURE(false, "Called with no local RegionBand (!)");
304 basegfx::B2DPolyPolygon
vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
306 tools::PolyPolygon
aPoly(ImplCreatePolyPolygonFromRegionBand());
308 return aPoly
.getB2DPolyPolygon();
311 Region::Region(bool bIsNull
)
316 Region::Region(const tools::Rectangle
& rRect
)
319 if (!rRect
.IsEmpty())
320 mpRegionBand
= std::make_shared
<RegionBand
>(rRect
);
323 Region::Region(const tools::Polygon
& rPolygon
)
327 if(rPolygon
.GetSize())
329 ImplCreatePolyPolyRegion(tools::PolyPolygon(rPolygon
));
333 Region::Region(const tools::PolyPolygon
& rPolyPoly
)
337 if(rPolyPoly
.Count())
339 ImplCreatePolyPolyRegion(rPolyPoly
);
343 Region::Region(const basegfx::B2DPolyPolygon
& rPolyPoly
)
347 if(rPolyPoly
.count())
349 ImplCreatePolyPolyRegion(rPolyPoly
);
353 Region::Region(const vcl::Region
&) = default;
355 Region::Region(vcl::Region
&& rRegion
) noexcept
356 : mpB2DPolyPolygon(std::move(rRegion
.mpB2DPolyPolygon
)),
357 mpPolyPolygon(std::move(rRegion
.mpPolyPolygon
)),
358 mpRegionBand(std::move(rRegion
.mpRegionBand
)),
359 mbIsNull(rRegion
.mbIsNull
)
361 rRegion
.mbIsNull
= true;
364 Region::~Region() = default;
366 void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon
& rPolyPoly
)
368 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
373 // polypolygon empty? -> empty region
374 const tools::Rectangle
aRect(rPolyPoly
.GetBoundRect());
379 // width OR height == 1 ? => Rectangular region
380 if((1 == aRect
.GetWidth()) || (1 == aRect
.GetHeight()) || rPolyPoly
.IsRect())
382 mpRegionBand
= std::make_shared
<RegionBand
>(aRect
);
386 mpPolyPolygon
= rPolyPoly
;
392 void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon
& rPolyPoly
)
394 if(rPolyPoly
.count() && !rPolyPoly
.getB2DRange().isEmpty())
396 mpB2DPolyPolygon
= rPolyPoly
;
401 void vcl::Region::Move( tools::Long nHorzMove
, tools::Long nVertMove
)
403 if(IsNull() || IsEmpty())
405 // empty or null need no move
409 if(!nHorzMove
&& !nVertMove
)
415 if(getB2DPolyPolygon())
417 basegfx::B2DPolyPolygon
aPoly(*getB2DPolyPolygon());
419 aPoly
.transform(basegfx::utils::createTranslateB2DHomMatrix(nHorzMove
, nVertMove
));
421 mpB2DPolyPolygon
= aPoly
;
423 mpB2DPolyPolygon
.reset();
424 mpPolyPolygon
.reset();
425 mpRegionBand
.reset();
427 else if(getPolyPolygon())
429 tools::PolyPolygon
aPoly(*getPolyPolygon());
431 aPoly
.Move(nHorzMove
, nVertMove
);
432 mpB2DPolyPolygon
.reset();
434 mpPolyPolygon
= aPoly
;
436 mpPolyPolygon
.reset();
437 mpRegionBand
.reset();
439 else if(getRegionBand())
441 std::shared_ptr
<RegionBand
> pNew
= std::make_shared
<RegionBand
>(*getRegionBand());
443 pNew
->Move(nHorzMove
, nVertMove
);
444 mpB2DPolyPolygon
.reset();
445 mpPolyPolygon
.reset();
446 mpRegionBand
= std::move(pNew
);
450 OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
454 void vcl::Region::Scale( double fScaleX
, double fScaleY
)
456 if(IsNull() || IsEmpty())
458 // empty or null need no scale
462 if(basegfx::fTools::equalZero(fScaleX
) && basegfx::fTools::equalZero(fScaleY
))
468 if(getB2DPolyPolygon())
470 basegfx::B2DPolyPolygon
aPoly(*getB2DPolyPolygon());
472 aPoly
.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX
, fScaleY
));
474 mpB2DPolyPolygon
= aPoly
;
476 mpB2DPolyPolygon
.reset();
477 mpPolyPolygon
.reset();
478 mpRegionBand
.reset();
480 else if(getPolyPolygon())
482 tools::PolyPolygon
aPoly(*getPolyPolygon());
484 aPoly
.Scale(fScaleX
, fScaleY
);
485 mpB2DPolyPolygon
.reset();
487 mpPolyPolygon
= aPoly
;
489 mpPolyPolygon
.reset();
490 mpRegionBand
.reset();
492 else if(getRegionBand())
494 std::shared_ptr
<RegionBand
> pNew
= std::make_shared
<RegionBand
>(*getRegionBand());
496 pNew
->Scale(fScaleX
, fScaleY
);
497 mpB2DPolyPolygon
.reset();
498 mpPolyPolygon
.reset();
499 mpRegionBand
= std::move(pNew
);
503 OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
507 void vcl::Region::Union( const tools::Rectangle
& rRect
)
511 // empty rectangle will not expand the existing union, nothing to do
517 // no local data, the union will be equal to source. Create using rectangle
522 if(HasPolyPolygonOrB2DPolyPolygon())
524 // get this B2DPolyPolygon, solve on polygon base
525 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
527 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
529 if(!aThisPolyPoly
.count())
531 // no local polygon, use the rectangle as new region
536 // get the other B2DPolyPolygon and use logical Or-Operation
537 const basegfx::B2DPolygon
aRectPoly(
538 basegfx::utils::createPolygonFromRect(
539 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
540 const basegfx::B2DPolyPolygon
aClip(
541 basegfx::utils::solvePolygonOperationOr(
543 basegfx::B2DPolyPolygon(aRectPoly
)));
544 *this = vcl::Region(aClip
);
550 // only region band mode possibility left here or null/empty
551 const RegionBand
* pCurrent
= getRegionBand();
555 // no region band, create using the rectangle
560 std::shared_ptr
<RegionBand
> pNew
= std::make_shared
<RegionBand
>(*pCurrent
);
562 // get justified rectangle
563 const tools::Long
nLeft(std::min(rRect
.Left(), rRect
.Right()));
564 const tools::Long
nTop(std::min(rRect
.Top(), rRect
.Bottom()));
565 const tools::Long
nRight(std::max(rRect
.Left(), rRect
.Right()));
566 const tools::Long
nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
568 // insert bands if the boundaries are not already in the list
569 pNew
->InsertBands(nTop
, nBottom
);
572 pNew
->Union(nLeft
, nTop
, nRight
, nBottom
);
575 if(!pNew
->OptimizeBandList())
580 mpRegionBand
= std::move(pNew
);
583 void vcl::Region::Intersect( const tools::Rectangle
& rRect
)
585 if ( rRect
.IsEmpty() )
587 // empty rectangle will create empty region
594 // null region (everything) intersect with rect will give rect
601 // no content, cannot get more empty
605 if(HasPolyPolygonOrB2DPolyPolygon())
607 // if polygon data prefer double precision, the other will be lost (if buffered)
608 if(getB2DPolyPolygon())
610 const basegfx::B2DPolyPolygon
aPoly(
611 basegfx::utils::clipPolyPolygonOnRange(
612 *getB2DPolyPolygon(),
622 mpB2DPolyPolygon
= aPoly
;
624 mpB2DPolyPolygon
.reset();
625 mpPolyPolygon
.reset();
626 mpRegionBand
.reset();
628 else // if(getPolyPolygon())
630 tools::PolyPolygon
aPoly(*getPolyPolygon());
632 // use the PolyPolygon::Clip method for rectangles, this is
633 // fairly simple (does not even use GPC) and saves us from
634 // unnecessary banding
637 mpB2DPolyPolygon
.reset();
639 mpPolyPolygon
= aPoly
;
641 mpPolyPolygon
.reset();
642 mpRegionBand
.reset();
648 // only region band mode possibility left here or null/empty
649 const RegionBand
* pCurrent
= getRegionBand();
653 // region is empty -> nothing to do!
657 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*pCurrent
));
659 // get justified rectangle
660 const tools::Long
nLeft(std::min(rRect
.Left(), rRect
.Right()));
661 const tools::Long
nTop(std::min(rRect
.Top(), rRect
.Bottom()));
662 const tools::Long
nRight(std::max(rRect
.Left(), rRect
.Right()));
663 const tools::Long
nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
665 // insert bands if the boundaries are not already in the list
666 pNew
->InsertBands(nTop
, nBottom
);
669 pNew
->Intersect(nLeft
, nTop
, nRight
, nBottom
);
672 if(!pNew
->OptimizeBandList())
677 mpRegionBand
= std::move(pNew
);
680 void vcl::Region::Exclude( const tools::Rectangle
& rRect
)
682 if ( rRect
.IsEmpty() )
684 // excluding nothing will do no change
690 // cannot exclude from empty, done
696 // error; cannot exclude from null region since this is not representable
698 OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
702 if( HasPolyPolygonOrB2DPolyPolygon() )
704 // get this B2DPolyPolygon
705 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
707 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
709 if(!aThisPolyPoly
.count())
711 // when local polygon is empty, nothing can be excluded
715 // get the other B2DPolyPolygon
716 const basegfx::B2DPolygon
aRectPoly(
717 basegfx::utils::createPolygonFromRect(
718 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
719 const basegfx::B2DPolyPolygon
aOtherPolyPoly(aRectPoly
);
720 const basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly
, aOtherPolyPoly
);
722 *this = vcl::Region(aClip
);
727 // only region band mode possibility left here or null/empty
734 std::shared_ptr
<RegionBand
>& pNew
= mpRegionBand
;
735 // only make a copy if someone else is also using it
736 if (pNew
.use_count() > 1)
737 pNew
= std::make_shared
<RegionBand
>(*pNew
);
739 // get justified rectangle
740 const tools::Long
nLeft(std::min(rRect
.Left(), rRect
.Right()));
741 const tools::Long
nTop(std::min(rRect
.Top(), rRect
.Bottom()));
742 const tools::Long
nRight(std::max(rRect
.Left(), rRect
.Right()));
743 const tools::Long
nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
745 // insert bands if the boundaries are not already in the list
746 pNew
->InsertBands(nTop
, nBottom
);
749 pNew
->Exclude(nLeft
, nTop
, nRight
, nBottom
);
752 if(!pNew
->OptimizeBandList())
756 void vcl::Region::XOr( const tools::Rectangle
& rRect
)
758 if ( rRect
.IsEmpty() )
760 // empty rectangle will not change local content
766 // rRect will be the xored-form (local off, rect on)
773 // error; cannot exclude from null region since this is not representable
775 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
779 if( HasPolyPolygonOrB2DPolyPolygon() )
781 // get this B2DPolyPolygon
782 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
784 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
786 if(!aThisPolyPoly
.count())
788 // no local content, XOr will be equal to rectangle
793 // get the other B2DPolyPolygon
794 const basegfx::B2DPolygon
aRectPoly(
795 basegfx::utils::createPolygonFromRect(
796 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
797 const basegfx::B2DPolyPolygon
aOtherPolyPoly(aRectPoly
);
798 const basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationXor(aThisPolyPoly
, aOtherPolyPoly
);
800 *this = vcl::Region(aClip
);
805 // only region band mode possibility left here or null/empty
806 const RegionBand
* pCurrent
= getRegionBand();
810 // rRect will be the xored-form (local off, rect on)
815 // only region band mode possibility left here or null/empty
816 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*getRegionBand()));
818 // get justified rectangle
819 const tools::Long
nLeft(std::min(rRect
.Left(), rRect
.Right()));
820 const tools::Long
nTop(std::min(rRect
.Top(), rRect
.Bottom()));
821 const tools::Long
nRight(std::max(rRect
.Left(), rRect
.Right()));
822 const tools::Long
nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
824 // insert bands if the boundaries are not already in the list
825 pNew
->InsertBands(nTop
, nBottom
);
828 pNew
->XOr(nLeft
, nTop
, nRight
, nBottom
);
831 if(!pNew
->OptimizeBandList())
836 mpRegionBand
= std::move(pNew
);
839 void vcl::Region::Union( const vcl::Region
& rRegion
)
841 if(rRegion
.IsEmpty())
843 // no extension at all
849 // extending with null region -> null region
850 *this = vcl::Region(true);
856 // local is empty, union will give source region
863 // already fully expanded (is null region), cannot be extended
867 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
869 // get this B2DPolyPolygon
870 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
872 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
874 if(!aThisPolyPoly
.count())
876 // when no local content, union will be equal to rRegion
881 // get the other B2DPolyPolygon
882 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
883 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly
);
885 // use logical OR operation
886 basegfx::B2DPolyPolygon
aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly
, aOtherPolyPoly
));
888 *this = vcl::Region( aClip
);
892 // only region band mode possibility left here or null/empty
893 const RegionBand
* pCurrent
= getRegionBand();
897 // local is empty, union will give source region
902 const RegionBand
* pSource
= rRegion
.getRegionBand();
906 // no extension at all
910 // prepare source and target
911 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*pCurrent
));
914 pNew
->Union(*pSource
);
917 if(!pNew
->OptimizeBandList())
922 mpRegionBand
= std::move(pNew
);
925 void vcl::Region::Intersect( const vcl::Region
& rRegion
)
927 // same instance data? -> nothing to do!
928 if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion
.getB2DPolyPolygon())
933 if(getPolyPolygon() && getPolyPolygon() == rRegion
.getPolyPolygon())
938 if(getRegionBand() && getRegionBand() == rRegion
.getRegionBand())
945 // source region is null-region, intersect will not change local region
951 // when local region is null-region, intersect will be equal to source
956 if(rRegion
.IsEmpty())
958 // source region is empty, intersection will always be empty
965 // local region is empty, cannot get more empty than that. Nothing to do
969 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
971 // get this B2DPolyPolygon
972 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
974 if(!aThisPolyPoly
.count())
976 // local region is empty, cannot get more empty than that. Nothing to do
980 // get the other B2DPolyPolygon
981 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
983 if(!aOtherPolyPoly
.count())
985 // source region is empty, intersection will always be empty
990 static size_t gPointLimit
= !comphelper::IsFuzzing() ? SAL_MAX_SIZE
: 8192;
991 size_t nPointLimit(gPointLimit
);
992 const basegfx::B2DPolyPolygon
aClip(
993 basegfx::utils::clipPolyPolygonOnPolyPolygon(
999 *this = vcl::Region( aClip
);
1003 // only region band mode possibility left here or null/empty
1004 const RegionBand
* pCurrent
= getRegionBand();
1008 // local region is empty, cannot get more empty than that. Nothing to do
1012 const RegionBand
* pSource
= rRegion
.getRegionBand();
1016 // source region is empty, intersection will always be empty
1021 // both RegionBands exist and are not empty
1022 if(pCurrent
->getRectangleCount() + 2 < pSource
->getRectangleCount())
1024 // when we have less rectangles, turn around the call
1025 vcl::Region aTempRegion
= rRegion
;
1026 aTempRegion
.Intersect( *this );
1027 *this = aTempRegion
;
1031 // prepare new regionBand
1032 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*pCurrent
));
1034 // intersect with source
1035 pNew
->Intersect(*pSource
);
1038 if(!pNew
->OptimizeBandList())
1043 mpRegionBand
= std::move(pNew
);
1047 void vcl::Region::Exclude( const vcl::Region
& rRegion
)
1049 if ( rRegion
.IsEmpty() )
1051 // excluding nothing will do no change
1055 if ( rRegion
.IsNull() )
1057 // excluding everything will create empty region
1064 // cannot exclude from empty, done
1070 // error; cannot exclude from null region since this is not representable
1072 OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
1076 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
1078 // get this B2DPolyPolygon
1079 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
1081 if(!aThisPolyPoly
.count())
1083 // cannot exclude from empty, done
1087 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
1089 // get the other B2DPolyPolygon
1090 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
1091 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly
);
1093 basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly
, aOtherPolyPoly
);
1094 *this = vcl::Region( aClip
);
1098 // only region band mode possibility left here or null/empty
1099 const RegionBand
* pCurrent
= getRegionBand();
1103 // cannot exclude from empty, done
1107 const RegionBand
* pSource
= rRegion
.getRegionBand();
1111 // excluding nothing will do no change
1115 // prepare source and target
1116 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*pCurrent
));
1118 // union with source
1119 const bool bSuccess(pNew
->Exclude(*pSource
));
1127 mpRegionBand
= std::move(pNew
);
1130 bool vcl::Region::XOr( const vcl::Region
& rRegion
)
1132 if ( rRegion
.IsEmpty() )
1134 // empty region will not change local content
1138 if ( rRegion
.IsNull() )
1140 // error; cannot exclude null region from local since this is not representable
1142 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
1148 // rRect will be the xored-form (local off, rect on)
1155 // error: cannot exclude from null region since this is not representable
1157 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
1161 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
1163 // get this B2DPolyPolygon
1164 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
1166 if(!aThisPolyPoly
.count())
1168 // rRect will be the xored-form (local off, rect on)
1173 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
1175 // get the other B2DPolyPolygon
1176 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
1177 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly
);
1179 basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationXor( aThisPolyPoly
, aOtherPolyPoly
);
1180 *this = vcl::Region( aClip
);
1184 // only region band mode possibility left here or null/empty
1185 const RegionBand
* pCurrent
= getRegionBand();
1189 // rRect will be the xored-form (local off, rect on)
1194 const RegionBand
* pSource
= rRegion
.getRegionBand();
1198 // empty region will not change local content
1202 // prepare source and target
1203 std::shared_ptr
<RegionBand
> pNew( std::make_shared
<RegionBand
>(*pCurrent
));
1205 // union with source
1206 pNew
->XOr(*pSource
);
1209 if(!pNew
->OptimizeBandList())
1214 mpRegionBand
= std::move(pNew
);
1219 tools::Rectangle
vcl::Region::GetBoundRect() const
1223 // no internal data? -> region is empty!
1224 return tools::Rectangle();
1229 // error; null region has no BoundRect
1230 // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
1231 return tools::Rectangle();
1234 // prefer double precision source
1235 if(getB2DPolyPolygon())
1237 const basegfx::B2DRange
aRange(basegfx::utils::getRange(*getB2DPolyPolygon()));
1239 if(aRange
.isEmpty())
1241 // emulate PolyPolygon::GetBoundRect() when empty polygon
1242 return tools::Rectangle();
1246 // #i122149# corrected rounding, no need for ceil() and floor() here
1247 return tools::Rectangle(
1248 basegfx::fround(aRange
.getMinX()), basegfx::fround(aRange
.getMinY()),
1249 basegfx::fround(aRange
.getMaxX()), basegfx::fround(aRange
.getMaxY()));
1253 if(getPolyPolygon())
1255 return getPolyPolygon()->GetBoundRect();
1260 return getRegionBand()->GetBoundRect();
1263 return tools::Rectangle();
1266 tools::PolyPolygon
vcl::Region::GetAsPolyPolygon() const
1268 if(getPolyPolygon())
1270 return *getPolyPolygon();
1273 if(getB2DPolyPolygon())
1275 // the polygon needs to be converted, buffer the down conversion
1276 const tools::PolyPolygon
aPolyPolgon(*getB2DPolyPolygon());
1277 const_cast< vcl::Region
* >(this)->mpPolyPolygon
= aPolyPolgon
;
1279 return *getPolyPolygon();
1284 // the BandRegion needs to be converted, buffer the conversion
1285 const tools::PolyPolygon
aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
1286 const_cast< vcl::Region
* >(this)->mpPolyPolygon
= aPolyPolgon
;
1288 return *getPolyPolygon();
1291 return tools::PolyPolygon();
1294 basegfx::B2DPolyPolygon
vcl::Region::GetAsB2DPolyPolygon() const
1296 if(getB2DPolyPolygon())
1298 return *getB2DPolyPolygon();
1301 if(getPolyPolygon())
1303 // the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
1304 const basegfx::B2DPolyPolygon
aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
1305 const_cast< vcl::Region
* >(this)->mpB2DPolyPolygon
= aB2DPolyPolygon
;
1307 return *getB2DPolyPolygon();
1312 // the BandRegion needs to be converted, buffer the conversion
1313 const basegfx::B2DPolyPolygon
aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
1314 const_cast< vcl::Region
* >(this)->mpB2DPolyPolygon
= aB2DPolyPolygon
;
1316 return *getB2DPolyPolygon();
1319 return basegfx::B2DPolyPolygon();
1322 const RegionBand
* vcl::Region::GetAsRegionBand() const
1324 if(!getRegionBand())
1326 if(getB2DPolyPolygon())
1328 // convert B2DPolyPolygon to RegionBand, buffer it and return it
1329 const_cast< vcl::Region
* >(this)->mpRegionBand
= ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
1331 else if(getPolyPolygon())
1333 // convert B2DPolyPolygon to RegionBand, buffer it and return it
1334 const_cast< vcl::Region
* >(this)->mpRegionBand
= ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
1338 return getRegionBand();
1341 bool vcl::Region::Contains( const Point
& rPoint
) const
1345 // no point can be in empty region
1351 // all points are inside null-region
1355 // Too expensive (?)
1356 //if(mpImplRegion->getRegionPolyPoly())
1358 // return mpImplRegion->getRegionPolyPoly()->Contains( rPoint );
1361 // ensure RegionBand existence
1362 const RegionBand
* pRegionBand
= GetAsRegionBand();
1366 return pRegionBand
->Contains(rPoint
);
1372 bool vcl::Region::Overlaps( const tools::Rectangle
& rRect
) const
1376 // nothing can be over something empty
1382 // everything is over null region
1386 // Can we optimize this ??? - is used in StarDraw for brushes pointers
1387 // Why we have no IsOver for Regions ???
1388 // create region from rectangle and intersect own region
1389 vcl::Region
aRegion(rRect
);
1390 aRegion
.Intersect( *this );
1392 // rectangle is over if include is not empty
1393 return !aRegion
.IsEmpty();
1396 bool vcl::Region::IsRectangle() const
1398 if( IsEmpty() || IsNull() )
1401 if( getB2DPolyPolygon() )
1402 return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
1404 if( getPolyPolygon() )
1405 return getPolyPolygon()->IsRect();
1407 if( getRegionBand() )
1408 return (getRegionBand()->getRectangleCount() == 1);
1413 void vcl::Region::SetNull()
1415 // reset all content
1416 mpB2DPolyPolygon
.reset();
1417 mpPolyPolygon
.reset();
1418 mpRegionBand
.reset();
1422 void vcl::Region::SetEmpty()
1424 // reset all content
1425 mpB2DPolyPolygon
.reset();
1426 mpPolyPolygon
.reset();
1427 mpRegionBand
.reset();
1431 Region
& vcl::Region::operator=( const vcl::Region
& ) = default;
1433 Region
& vcl::Region::operator=( vcl::Region
&& rRegion
) noexcept
1435 mpB2DPolyPolygon
= std::move(rRegion
.mpB2DPolyPolygon
);
1436 mpPolyPolygon
= std::move(rRegion
.mpPolyPolygon
);
1437 mpRegionBand
= std::move(rRegion
.mpRegionBand
);
1438 mbIsNull
= rRegion
.mbIsNull
;
1439 rRegion
.mbIsNull
= true;
1444 Region
& vcl::Region::operator=( const tools::Rectangle
& rRect
)
1446 mpB2DPolyPolygon
.reset();
1447 mpPolyPolygon
.reset();
1448 if (!rRect
.IsEmpty())
1449 mpRegionBand
= std::make_shared
<RegionBand
>(rRect
);
1451 mpRegionBand
.reset();
1457 bool vcl::Region::operator==( const vcl::Region
& rRegion
) const
1459 if(IsNull() && rRegion
.IsNull())
1461 // both are null region
1465 if(IsEmpty() && rRegion
.IsEmpty())
1471 if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion
.getB2DPolyPolygon())
1473 // same instance data? -> equal
1477 if(getPolyPolygon() && getPolyPolygon() == rRegion
.getPolyPolygon())
1479 // same instance data? -> equal
1483 if(getRegionBand() && getRegionBand() == rRegion
.getRegionBand())
1485 // same instance data? -> equal
1489 if(IsNull() || IsEmpty())
1494 if(rRegion
.IsNull() || rRegion
.IsEmpty())
1499 if(rRegion
.getB2DPolyPolygon() || getB2DPolyPolygon())
1501 // one of both has a B2DPolyPolygon based region, ensure both have it
1502 // by evtl. conversion
1503 GetAsB2DPolyPolygon();
1504 rRegion
.GetAsB2DPolyPolygon();
1506 return *rRegion
.getB2DPolyPolygon() == *getB2DPolyPolygon();
1509 if(rRegion
.getPolyPolygon() || getPolyPolygon())
1511 // one of both has a B2DPolyPolygon based region, ensure both have it
1512 // by evtl. conversion
1514 rRegion
.GetAsPolyPolygon();
1516 return *rRegion
.getPolyPolygon() == *getPolyPolygon();
1519 // both are not empty or null (see above) and if content supported polygon
1520 // data the comparison is already done. Only both on RegionBand base can be left,
1522 if(rRegion
.getRegionBand() && getRegionBand())
1524 return *rRegion
.getRegionBand() == *getRegionBand();
1527 // should not happen, but better deny equality
1531 SvStream
& ReadRegion(SvStream
& rIStrm
, vcl::Region
& rRegion
)
1533 VersionCompatRead
aCompat(rIStrm
);
1534 sal_uInt16
nVersion(0);
1535 sal_uInt16
nTmp16(0);
1537 // clear region to be loaded
1540 // get version of streamed region
1541 rIStrm
.ReadUInt16( nVersion
);
1543 // get type of region
1544 rIStrm
.ReadUInt16( nTmp16
);
1546 enum RegionType
{ REGION_NULL
, REGION_EMPTY
, REGION_RECTANGLE
, REGION_COMPLEX
};
1547 auto eStreamedType
= nTmp16
;
1549 switch (eStreamedType
)
1565 std::shared_ptr
<RegionBand
> xNewRegionBand(std::make_shared
<RegionBand
>());
1566 bool bSuccess
= xNewRegionBand
->load(rIStrm
);
1567 rRegion
.mpRegionBand
= xNewRegionBand
;
1569 bool bHasPolyPolygon(false);
1570 if (aCompat
.GetVersion() >= 2)
1572 rIStrm
.ReadCharAsBool( bHasPolyPolygon
);
1574 if (bHasPolyPolygon
)
1576 tools::PolyPolygon aNewPoly
;
1577 ReadPolyPolygon(rIStrm
, aNewPoly
);
1578 const auto nPolygons
= aNewPoly
.Count();
1579 if (nPolygons
> 128)
1581 SAL_WARN("vcl.gdi", "suspiciously high no of polygons in clip:" << nPolygons
);
1582 if (comphelper::IsFuzzing())
1585 rRegion
.mpPolyPolygon
= aNewPoly
;
1589 if (!bSuccess
&& !bHasPolyPolygon
)
1591 SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon
);
1602 SvStream
& WriteRegion( SvStream
& rOStrm
, const vcl::Region
& rRegion
)
1604 const sal_uInt16
nVersion(2);
1605 VersionCompatWrite
aCompat(rOStrm
, nVersion
);
1608 rOStrm
.WriteUInt16( nVersion
);
1611 enum RegionType
{ REGION_NULL
, REGION_EMPTY
, REGION_RECTANGLE
, REGION_COMPLEX
};
1612 RegionType
aRegionType(REGION_COMPLEX
);
1613 bool bEmpty(rRegion
.IsEmpty());
1615 if(!bEmpty
&& rRegion
.getB2DPolyPolygon() && 0 == rRegion
.getB2DPolyPolygon()->count())
1617 OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
1621 if(!bEmpty
&& rRegion
.getPolyPolygon() && 0 == rRegion
.getPolyPolygon()->Count())
1623 OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
1629 aRegionType
= REGION_EMPTY
;
1631 else if(rRegion
.IsNull())
1633 aRegionType
= REGION_NULL
;
1635 else if(rRegion
.getRegionBand() && rRegion
.getRegionBand()->isSingleRectangle())
1637 aRegionType
= REGION_RECTANGLE
;
1640 rOStrm
.WriteUInt16( aRegionType
);
1643 const RegionBand
* pRegionBand
= rRegion
.getRegionBand();
1647 pRegionBand
->save(rOStrm
);
1651 // for compatibility, write an empty RegionBand (will only write
1652 // the end marker STREAMENTRY_END, but this *is* needed)
1653 const RegionBand aRegionBand
;
1655 aRegionBand
.save(rOStrm
);
1658 // write polypolygon if available
1659 const bool bHasPolyPolygon(rRegion
.HasPolyPolygonOrB2DPolyPolygon());
1660 rOStrm
.WriteBool( bHasPolyPolygon
);
1665 tools::PolyPolygon aNoCurvePolyPolygon
;
1666 rRegion
.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon
);
1668 WritePolyPolygon( rOStrm
, aNoCurvePolyPolygon
);
1674 void vcl::Region::GetRegionRectangles(RectangleVector
& rTarget
) const
1676 // clear returnvalues
1679 // ensure RegionBand existence
1680 const RegionBand
* pRegionBand
= GetAsRegionBand();
1684 pRegionBand
->GetRegionRectangles(rTarget
);
1688 static bool ImplPolygonRectTest( const tools::Polygon
& rPoly
, tools::Rectangle
* pRectOut
= nullptr )
1690 bool bIsRect
= false;
1691 const Point
* pPoints
= rPoly
.GetConstPointAry();
1692 sal_uInt16 nPoints
= rPoly
.GetSize();
1694 if( nPoints
== 4 || (nPoints
== 5 && pPoints
[0] == pPoints
[4]) )
1696 tools::Long nX1
= pPoints
[0].X(), nX2
= pPoints
[2].X(), nY1
= pPoints
[0].Y(), nY2
= pPoints
[2].Y();
1698 if( ( (pPoints
[1].X() == nX1
&& pPoints
[3].X() == nX2
) && (pPoints
[1].Y() == nY2
&& pPoints
[3].Y() == nY1
) )
1699 || ( (pPoints
[1].X() == nX2
&& pPoints
[3].X() == nX1
) && (pPoints
[1].Y() == nY1
&& pPoints
[3].Y() == nY2
) ) )
1731 pRectOut
->SetLeft( nX1
);
1732 pRectOut
->SetRight( nX2
);
1733 pRectOut
->SetTop( nY1
);
1734 pRectOut
->SetBottom( nY2
);
1742 vcl::Region
vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon
& rPolyPoly
)
1744 //return vcl::Region( rPolyPoly );
1746 // check if it's worth extracting the XOr'ing the Rectangles
1747 // empiricism shows that break even between XOr'ing rectangles separately
1748 // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
1749 int nPolygonRects
= 0, nPolygonPolygons
= 0;
1750 int nPolygons
= rPolyPoly
.Count();
1752 for( int i
= 0; i
< nPolygons
; i
++ )
1754 const tools::Polygon
& rPoly
= rPolyPoly
[i
];
1756 if( ImplPolygonRectTest( rPoly
) )
1766 if( nPolygonPolygons
> nPolygonRects
)
1768 return vcl::Region( rPolyPoly
);
1771 vcl::Region aResult
;
1772 tools::Rectangle aRect
;
1774 for( int i
= 0; i
< nPolygons
; i
++ )
1776 const tools::Polygon
& rPoly
= rPolyPoly
[i
];
1778 if( ImplPolygonRectTest( rPoly
, &aRect
) )
1780 aResult
.XOr( aRect
);
1784 aResult
.XOr( vcl::Region(rPoly
) );
1791 } /* namespace vcl */
1793 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */