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>
39 /** Return <TRUE/> when the given polygon is rectilinear and oriented so that
40 all sides are either horizontal or vertical.
42 bool ImplIsPolygonRectilinear (const tools::PolyPolygon
& rPolyPoly
)
44 // Iterate over all polygons.
45 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
46 for (sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; ++nPoly
)
48 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject(nPoly
);
50 // Iterate over all edges of the current polygon.
51 const sal_uInt16 nSize
= aPoly
.GetSize();
55 Point
aPoint (aPoly
.GetPoint(0));
56 const Point
aLastPoint (aPoint
);
57 for (sal_uInt16 nPoint
= 1; nPoint
< nSize
; ++nPoint
)
59 const Point
aNextPoint (aPoly
.GetPoint(nPoint
));
60 // When there is at least one edge that is neither vertical nor
61 // horizontal then the entire polygon is not rectilinear (and
62 // oriented along primary axes.)
63 if (aPoint
.X() != aNextPoint
.X() && aPoint
.Y() != aNextPoint
.Y())
68 // Compare closing edge.
69 if (aLastPoint
.X() != aPoint
.X() && aLastPoint
.Y() != aPoint
.Y())
75 /** Convert a rectilinear polygon (that is oriented along the primary axes)
76 to a list of bands. For this special form of polygon we can use an
77 optimization that prevents the creation of one band per y value.
78 However, it still is possible that some temporary bands are created that
79 later can be optimized away.
81 A set of zero, one, or more polygons, nested or not, that are
82 converted into a list of bands.
84 A new RegionBand object is returned that contains the bands that
85 represent the given poly-polygon.
87 std::unique_ptr
<RegionBand
> ImplRectilinearPolygonToBands(const tools::PolyPolygon
& rPolyPoly
)
89 OSL_ASSERT(ImplIsPolygonRectilinear (rPolyPoly
));
91 // Create a new RegionBand object as container of the bands.
92 std::unique_ptr
<RegionBand
> pRegionBand( std::make_unique
<RegionBand
>() );
95 // Iterate over all polygons.
96 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
97 for (sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; ++nPoly
)
99 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject(nPoly
);
101 // Iterate over all edges of the current polygon.
102 const sal_uInt16 nSize
= aPoly
.GetSize();
105 // Avoid fetching every point twice (each point is the start point
106 // of one and the end point of another edge.)
107 Point
aStart (aPoly
.GetPoint(0));
109 for (sal_uInt16 nPoint
= 1; nPoint
<= nSize
; ++nPoint
, aStart
=aEnd
)
111 // We take the implicit closing edge into account by mapping
113 aEnd
= aPoly
.GetPoint(nPoint
%nSize
);
114 if (aStart
.Y() == aEnd
.Y())
116 // Horizontal lines are ignored.
120 // At this point the line has to be vertical.
121 OSL_ASSERT(aStart
.X() == aEnd
.X());
123 // Sort y-coordinates to simplify the algorithm and store the
124 // direction separately. The direction is calculated as it is
125 // in other places (but seems to be the wrong way.)
126 const long nTop (::std::min(aStart
.Y(), aEnd
.Y()));
127 const long nBottom (::std::max(aStart
.Y(), aEnd
.Y()));
128 const LineType
eLineType (aStart
.Y() > aEnd
.Y() ? LineType::Descending
: LineType::Ascending
);
130 // Make sure that the current line is covered by bands.
131 pRegionBand
->ImplAddMissingBands(nTop
,nBottom
);
133 // Find top-most band that may contain nTop.
134 ImplRegionBand
* pBand
= pRegionBand
->ImplGetFirstRegionBand();
135 while (pBand
!=nullptr && pBand
->mnYBottom
< nTop
)
136 pBand
= pBand
->mpNextBand
;
137 ImplRegionBand
* pTopBand
= pBand
;
138 // If necessary split the band at nTop so that nTop is contained
139 // in the lower band.
141 // Prevent the current band from becoming 0 pixel high
142 && pBand
->mnYTop
<nTop
143 // this allows the lowest pixel of the band to be split off
144 && pBand
->mnYBottom
>=nTop
145 // do not split a band that is just one pixel high
146 && pBand
->mnYTop
<pBand
->mnYBottom
-1)
148 // Split the top band.
149 pTopBand
= pBand
->SplitBand(nTop
);
152 // Advance to band that may contain nBottom.
153 while (pBand
!=nullptr && pBand
->mnYBottom
< nBottom
)
154 pBand
= pBand
->mpNextBand
;
155 // The lowest band may have to be split at nBottom so that
156 // nBottom itself remains in the upper band.
158 // allow the current band becoming 1 pixel high
159 && pBand
->mnYTop
<=nBottom
160 // prevent splitting off a band that is 0 pixel high
161 && pBand
->mnYBottom
>nBottom
162 // do not split a band that is just one pixel high
163 && pBand
->mnYTop
<pBand
->mnYBottom
-1)
165 // Split the bottom band.
166 pBand
->SplitBand(nBottom
+1);
169 // Note that we remember the top band (in pTopBand) but not the
170 // bottom band. The later can be determined by comparing y
173 // Add the x-value as point to all bands in the nTop->nBottom range.
174 for (pBand
=pTopBand
; pBand
!=nullptr&&pBand
->mnYTop
<=nBottom
; pBand
=pBand
->mpNextBand
)
175 pBand
->InsertPoint(aStart
.X(), nLineId
++, true, eLineType
);
182 /** Convert a general polygon (one for which ImplIsPolygonRectilinear()
183 returns <FALSE/>) to bands.
185 std::unique_ptr
<RegionBand
> ImplGeneralPolygonToBands(const tools::PolyPolygon
& rPolyPoly
, const tools::Rectangle
& rPolygonBoundingBox
)
189 // initialisation and creation of Bands
190 std::unique_ptr
<RegionBand
> pRegionBand( std::make_unique
<RegionBand
>() );
191 pRegionBand
->CreateBandRange(rPolygonBoundingBox
.Top(), rPolygonBoundingBox
.Bottom());
194 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
196 for ( sal_uInt16 nPoly
= 0; nPoly
< nPolyCount
; nPoly
++ )
198 // get reference to current polygon
199 const tools::Polygon
& aPoly
= rPolyPoly
.GetObject( nPoly
);
200 const sal_uInt16 nSize
= aPoly
.GetSize();
202 // not enough points ( <= 2 )? -> nothing to do!
207 for ( sal_uInt16 nPoint
= 1; nPoint
< nSize
; nPoint
++ )
209 pRegionBand
->InsertLine( aPoly
.GetPoint(nPoint
-1), aPoly
.GetPoint(nPoint
), nLineID
++ );
212 // close polygon with line from first point to last point, if necessary
213 const Point rLastPoint
= aPoly
.GetPoint(nSize
-1);
214 const Point rFirstPoint
= aPoly
.GetPoint(0);
216 if ( rLastPoint
!= rFirstPoint
)
218 pRegionBand
->InsertLine( rLastPoint
, rFirstPoint
, nLineID
++ );
224 } // end of anonymous namespace
228 bool vcl::Region::IsEmpty() const
230 return !mbIsNull
&& !mpB2DPolyPolygon
.get() && !mpPolyPolygon
.get() && !mpRegionBand
.get();
234 static std::unique_ptr
<RegionBand
> ImplCreateRegionBandFromPolyPolygon(const tools::PolyPolygon
& rPolyPolygon
)
236 std::unique_ptr
<RegionBand
> pRetval
;
238 if(rPolyPolygon
.Count())
240 // ensure to subdivide when bezier segments are used, it's going to
241 // be expanded to rectangles
242 tools::PolyPolygon aPolyPolygon
;
244 rPolyPolygon
.AdaptiveSubdivide(aPolyPolygon
);
246 if(aPolyPolygon
.Count())
248 const tools::Rectangle
aRect(aPolyPolygon
.GetBoundRect());
252 if(ImplIsPolygonRectilinear(aPolyPolygon
))
254 // For rectilinear polygons there is an optimized band conversion.
255 pRetval
= ImplRectilinearPolygonToBands(aPolyPolygon
);
259 pRetval
= ImplGeneralPolygonToBands(aPolyPolygon
, aRect
);
262 // Convert points into seps.
265 pRetval
->processPoints();
267 // Optimize list of bands. Adjacent bands with identical lists
268 // of seps are joined.
269 if(!pRetval
->OptimizeBandList())
281 tools::PolyPolygon
vcl::Region::ImplCreatePolyPolygonFromRegionBand() const
283 tools::PolyPolygon aRetval
;
287 RectangleVector aRectangles
;
288 GetRegionRectangles(aRectangles
);
290 for (auto const& rectangle
: aRectangles
)
292 aRetval
.Insert( tools::Polygon(rectangle
) );
297 OSL_ENSURE(false, "Called with no local RegionBand (!)");
303 basegfx::B2DPolyPolygon
vcl::Region::ImplCreateB2DPolyPolygonFromRegionBand() const
305 tools::PolyPolygon
aPoly(ImplCreatePolyPolygonFromRegionBand());
307 return aPoly
.getB2DPolyPolygon();
310 Region::Region(bool bIsNull
)
311 : mpB2DPolyPolygon(),
318 Region::Region(const tools::Rectangle
& rRect
)
319 : mpB2DPolyPolygon(),
324 mpRegionBand
.reset(rRect
.IsEmpty() ? nullptr : new RegionBand(rRect
));
327 Region::Region(const tools::Polygon
& rPolygon
)
328 : mpB2DPolyPolygon(),
334 if(rPolygon
.GetSize())
336 ImplCreatePolyPolyRegion(rPolygon
);
340 Region::Region(const tools::PolyPolygon
& rPolyPoly
)
341 : mpB2DPolyPolygon(),
347 if(rPolyPoly
.Count())
349 ImplCreatePolyPolyRegion(rPolyPoly
);
353 Region::Region(const basegfx::B2DPolyPolygon
& rPolyPoly
)
354 : mpB2DPolyPolygon(),
360 if(rPolyPoly
.count())
362 ImplCreatePolyPolyRegion(rPolyPoly
);
366 Region::Region(const vcl::Region
&) = default;
368 Region::Region(vcl::Region
&& rRegion
) noexcept
369 : mpB2DPolyPolygon(std::move(rRegion
.mpB2DPolyPolygon
)),
370 mpPolyPolygon(std::move(rRegion
.mpPolyPolygon
)),
371 mpRegionBand(std::move(rRegion
.mpRegionBand
)),
372 mbIsNull(rRegion
.mbIsNull
)
374 rRegion
.mbIsNull
= true;
377 Region::~Region() = default;
379 void vcl::Region::ImplCreatePolyPolyRegion( const tools::PolyPolygon
& rPolyPoly
)
381 const sal_uInt16 nPolyCount
= rPolyPoly
.Count();
385 // polypolygon empty? -> empty region
386 const tools::Rectangle
aRect(rPolyPoly
.GetBoundRect());
390 // width OR height == 1 ? => Rectangular region
391 if((1 == aRect
.GetWidth()) || (1 == aRect
.GetHeight()) || rPolyPoly
.IsRect())
393 mpRegionBand
.reset(new RegionBand(aRect
));
397 mpPolyPolygon
.reset(new tools::PolyPolygon(rPolyPoly
));
405 void vcl::Region::ImplCreatePolyPolyRegion( const basegfx::B2DPolyPolygon
& rPolyPoly
)
407 if(rPolyPoly
.count() && !rPolyPoly
.getB2DRange().isEmpty())
409 mpB2DPolyPolygon
.reset(new basegfx::B2DPolyPolygon(rPolyPoly
));
414 void vcl::Region::Move( long nHorzMove
, long nVertMove
)
416 if(IsNull() || IsEmpty())
418 // empty or null need no move
422 if(!nHorzMove
&& !nVertMove
)
428 if(getB2DPolyPolygon())
430 basegfx::B2DPolyPolygon
aPoly(*getB2DPolyPolygon());
432 aPoly
.transform(basegfx::utils::createTranslateB2DHomMatrix(nHorzMove
, nVertMove
));
433 mpB2DPolyPolygon
.reset(aPoly
.count() ? new basegfx::B2DPolyPolygon(aPoly
) : nullptr);
434 mpPolyPolygon
.reset();
435 mpRegionBand
.reset();
437 else if(getPolyPolygon())
439 tools::PolyPolygon
aPoly(*getPolyPolygon());
441 aPoly
.Move(nHorzMove
, nVertMove
);
442 mpB2DPolyPolygon
.reset();
443 mpPolyPolygon
.reset(aPoly
.Count() ? new tools::PolyPolygon(aPoly
) : nullptr);
444 mpRegionBand
.reset();
446 else if(getRegionBand())
448 RegionBand
* pNew
= new RegionBand(*getRegionBand());
450 pNew
->Move(nHorzMove
, nVertMove
);
451 mpB2DPolyPolygon
.reset();
452 mpPolyPolygon
.reset();
453 mpRegionBand
.reset(pNew
);
457 OSL_ENSURE(false, "Region::Move error: impossible combination (!)");
461 void vcl::Region::Scale( double fScaleX
, double fScaleY
)
463 if(IsNull() || IsEmpty())
465 // empty or null need no scale
469 if(basegfx::fTools::equalZero(fScaleX
) && basegfx::fTools::equalZero(fScaleY
))
475 if(getB2DPolyPolygon())
477 basegfx::B2DPolyPolygon
aPoly(*getB2DPolyPolygon());
479 aPoly
.transform(basegfx::utils::createScaleB2DHomMatrix(fScaleX
, fScaleY
));
480 mpB2DPolyPolygon
.reset(aPoly
.count() ? new basegfx::B2DPolyPolygon(aPoly
) : nullptr);
481 mpPolyPolygon
.reset();
482 mpRegionBand
.reset();
484 else if(getPolyPolygon())
486 tools::PolyPolygon
aPoly(*getPolyPolygon());
488 aPoly
.Scale(fScaleX
, fScaleY
);
489 mpB2DPolyPolygon
.reset();
490 mpPolyPolygon
.reset(aPoly
.Count() ? new tools::PolyPolygon(aPoly
) : nullptr);
491 mpRegionBand
.reset();
493 else if(getRegionBand())
495 RegionBand
* pNew
= new RegionBand(*getRegionBand());
497 pNew
->Scale(fScaleX
, fScaleY
);
498 mpB2DPolyPolygon
.reset();
499 mpPolyPolygon
.reset();
500 mpRegionBand
.reset(pNew
);
504 OSL_ENSURE(false, "Region::Scale error: impossible combination (!)");
508 void vcl::Region::Union( const tools::Rectangle
& rRect
)
512 // empty rectangle will not expand the existing union, nothing to do
518 // no local data, the union will be equal to source. Create using rectangle
523 if(HasPolyPolygonOrB2DPolyPolygon())
525 // get this B2DPolyPolygon, solve on polygon base
526 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
528 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
530 if(!aThisPolyPoly
.count())
532 // no local polygon, use the rectangle as new region
537 // get the other B2DPolyPolygon and use logical Or-Operation
538 const basegfx::B2DPolygon
aRectPoly(
539 basegfx::utils::createPolygonFromRect(
540 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
541 const basegfx::B2DPolyPolygon
aClip(
542 basegfx::utils::solvePolygonOperationOr(
544 basegfx::B2DPolyPolygon(aRectPoly
)));
545 *this = vcl::Region(aClip
);
551 // only region band mode possibility left here or null/empty
552 const RegionBand
* pCurrent
= getRegionBand();
556 // no region band, create using the rectangle
561 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
563 // get justified rectangle
564 const long nLeft(std::min(rRect
.Left(), rRect
.Right()));
565 const long nTop(std::min(rRect
.Top(), rRect
.Bottom()));
566 const long nRight(std::max(rRect
.Left(), rRect
.Right()));
567 const long nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
569 // insert bands if the boundaries are not already in the list
570 pNew
->InsertBands(nTop
, nBottom
);
573 pNew
->Union(nLeft
, nTop
, nRight
, nBottom
);
576 if(!pNew
->OptimizeBandList())
581 mpRegionBand
= std::move(pNew
);
584 void vcl::Region::Intersect( const tools::Rectangle
& rRect
)
586 if ( rRect
.IsEmpty() )
588 // empty rectangle will create empty region
595 // null region (everything) intersect with rect will give rect
602 // no content, cannot get more empty
606 if(HasPolyPolygonOrB2DPolyPolygon())
608 // if polygon data prefer double precision, the other will be lost (if buffered)
609 if(getB2DPolyPolygon())
611 const basegfx::B2DPolyPolygon
aPoly(
612 basegfx::utils::clipPolyPolygonOnRange(
613 *getB2DPolyPolygon(),
622 mpB2DPolyPolygon
.reset(aPoly
.count() ? new basegfx::B2DPolyPolygon(aPoly
) : nullptr);
623 mpPolyPolygon
.reset();
624 mpRegionBand
.reset();
626 else // if(getPolyPolygon())
628 tools::PolyPolygon
aPoly(*getPolyPolygon());
630 // use the PolyPolygon::Clip method for rectangles, this is
631 // fairly simple (does not even use GPC) and saves us from
632 // unnecessary banding
635 mpB2DPolyPolygon
.reset();
636 mpPolyPolygon
.reset(aPoly
.Count() ? new tools::PolyPolygon(aPoly
) : nullptr);
637 mpRegionBand
.reset();
643 // only region band mode possibility left here or null/empty
644 const RegionBand
* pCurrent
= getRegionBand();
648 // region is empty -> nothing to do!
652 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
654 // get justified rectangle
655 const long nLeft(std::min(rRect
.Left(), rRect
.Right()));
656 const long nTop(std::min(rRect
.Top(), rRect
.Bottom()));
657 const long nRight(std::max(rRect
.Left(), rRect
.Right()));
658 const long nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
660 // insert bands if the boundaries are not already in the list
661 pNew
->InsertBands(nTop
, nBottom
);
664 pNew
->Intersect(nLeft
, nTop
, nRight
, nBottom
);
667 if(!pNew
->OptimizeBandList())
672 mpRegionBand
= std::move(pNew
);
675 void vcl::Region::Exclude( const tools::Rectangle
& rRect
)
677 if ( rRect
.IsEmpty() )
679 // excluding nothing will do no change
685 // cannot exclude from empty, done
691 // error; cannot exclude from null region since this is not representable
693 OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
697 if( HasPolyPolygonOrB2DPolyPolygon() )
699 // get this B2DPolyPolygon
700 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
702 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
704 if(!aThisPolyPoly
.count())
706 // when local polygon is empty, nothing can be excluded
710 // get the other B2DPolyPolygon
711 const basegfx::B2DPolygon
aRectPoly(
712 basegfx::utils::createPolygonFromRect(
713 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
714 const basegfx::B2DPolyPolygon
aOtherPolyPoly(aRectPoly
);
715 const basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationDiff(aThisPolyPoly
, aOtherPolyPoly
);
717 *this = vcl::Region(aClip
);
722 // only region band mode possibility left here or null/empty
723 const RegionBand
* pCurrent
= getRegionBand();
731 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
733 // get justified rectangle
734 const long nLeft(std::min(rRect
.Left(), rRect
.Right()));
735 const long nTop(std::min(rRect
.Top(), rRect
.Bottom()));
736 const long nRight(std::max(rRect
.Left(), rRect
.Right()));
737 const long nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
739 // insert bands if the boundaries are not already in the list
740 pNew
->InsertBands(nTop
, nBottom
);
743 pNew
->Exclude(nLeft
, nTop
, nRight
, nBottom
);
746 if(!pNew
->OptimizeBandList())
751 mpRegionBand
= std::move(pNew
);
754 void vcl::Region::XOr( const tools::Rectangle
& rRect
)
756 if ( rRect
.IsEmpty() )
758 // empty rectangle will not change local content
764 // rRect will be the xored-form (local off, rect on)
771 // error; cannot exclude from null region since this is not representable
773 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
777 if( HasPolyPolygonOrB2DPolyPolygon() )
779 // get this B2DPolyPolygon
780 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
782 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
784 if(!aThisPolyPoly
.count())
786 // no local content, XOr will be equal to rectangle
791 // get the other B2DPolyPolygon
792 const basegfx::B2DPolygon
aRectPoly(
793 basegfx::utils::createPolygonFromRect(
794 vcl::unotools::b2DRectangleFromRectangle(rRect
)));
795 const basegfx::B2DPolyPolygon
aOtherPolyPoly(aRectPoly
);
796 const basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationXor(aThisPolyPoly
, aOtherPolyPoly
);
798 *this = vcl::Region(aClip
);
803 // only region band mode possibility left here or null/empty
804 const RegionBand
* pCurrent
= getRegionBand();
808 // rRect will be the xored-form (local off, rect on)
813 // only region band mode possibility left here or null/empty
814 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*getRegionBand()));
816 // get justified rectangle
817 const long nLeft(std::min(rRect
.Left(), rRect
.Right()));
818 const long nTop(std::min(rRect
.Top(), rRect
.Bottom()));
819 const long nRight(std::max(rRect
.Left(), rRect
.Right()));
820 const long nBottom(std::max(rRect
.Top(), rRect
.Bottom()));
822 // insert bands if the boundaries are not already in the list
823 pNew
->InsertBands(nTop
, nBottom
);
826 pNew
->XOr(nLeft
, nTop
, nRight
, nBottom
);
829 if(!pNew
->OptimizeBandList())
834 mpRegionBand
= std::move(pNew
);
837 void vcl::Region::Union( const vcl::Region
& rRegion
)
839 if(rRegion
.IsEmpty())
841 // no extension at all
847 // extending with null region -> null region
848 *this = vcl::Region(true);
854 // local is empty, union will give source region
861 // already fully expanded (is null region), cannot be extended
865 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
867 // get this B2DPolyPolygon
868 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
870 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation(aThisPolyPoly
);
872 if(!aThisPolyPoly
.count())
874 // when no local content, union will be equal to rRegion
879 // get the other B2DPolyPolygon
880 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
881 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation(aOtherPolyPoly
);
883 // use logical OR operation
884 basegfx::B2DPolyPolygon
aClip(basegfx::utils::solvePolygonOperationOr(aThisPolyPoly
, aOtherPolyPoly
));
886 *this = vcl::Region( aClip
);
890 // only region band mode possibility left here or null/empty
891 const RegionBand
* pCurrent
= getRegionBand();
895 // local is empty, union will give source region
900 const RegionBand
* pSource
= rRegion
.getRegionBand();
904 // no extension at all
908 // prepare source and target
909 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
912 pNew
->Union(*pSource
);
915 if(!pNew
->OptimizeBandList())
920 mpRegionBand
= std::move(pNew
);
923 void vcl::Region::Intersect( const vcl::Region
& rRegion
)
925 // same instance data? -> nothing to do!
926 if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion
.getB2DPolyPolygon())
931 if(getPolyPolygon() && getPolyPolygon() == rRegion
.getPolyPolygon())
936 if(getRegionBand() && getRegionBand() == rRegion
.getRegionBand())
943 // source region is null-region, intersect will not change local region
949 // when local region is null-region, intersect will be equal to source
954 if(rRegion
.IsEmpty())
956 // source region is empty, intersection will always be empty
963 // local region is empty, cannot get more empty than that. Nothing to do
967 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
969 // get this B2DPolyPolygon
970 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
972 if(!aThisPolyPoly
.count())
974 // local region is empty, cannot get more empty than that. Nothing to do
978 // get the other B2DPolyPolygon
979 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
981 if(!aOtherPolyPoly
.count())
983 // source region is empty, intersection will always be empty
988 const basegfx::B2DPolyPolygon
aClip(
989 basegfx::utils::clipPolyPolygonOnPolyPolygon(
994 *this = vcl::Region( aClip
);
998 // only region band mode possibility left here or null/empty
999 const RegionBand
* pCurrent
= getRegionBand();
1003 // local region is empty, cannot get more empty than that. Nothing to do
1007 const RegionBand
* pSource
= rRegion
.getRegionBand();
1011 // source region is empty, intersection will always be empty
1016 // both RegionBands exist and are not empty
1017 if(pCurrent
->getRectangleCount() + 2 < pSource
->getRectangleCount())
1019 // when we have less rectangles, turn around the call
1020 vcl::Region aTempRegion
= rRegion
;
1021 aTempRegion
.Intersect( *this );
1022 *this = aTempRegion
;
1026 // prepare new regionBand
1027 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
1029 // intersect with source
1030 pNew
->Intersect(*pSource
);
1033 if(!pNew
->OptimizeBandList())
1038 mpRegionBand
= std::move(pNew
);
1042 void vcl::Region::Exclude( const vcl::Region
& rRegion
)
1044 if ( rRegion
.IsEmpty() )
1046 // excluding nothing will do no change
1050 if ( rRegion
.IsNull() )
1052 // excluding everything will create empty region
1059 // cannot exclude from empty, done
1065 // error; cannot exclude from null region since this is not representable
1067 OSL_ENSURE(false, "Region::Exclude error: Cannot exclude from null region (!)");
1071 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
1073 // get this B2DPolyPolygon
1074 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
1076 if(!aThisPolyPoly
.count())
1078 // cannot exclude from empty, done
1082 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
1084 // get the other B2DPolyPolygon
1085 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
1086 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly
);
1088 basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationDiff( aThisPolyPoly
, aOtherPolyPoly
);
1089 *this = vcl::Region( aClip
);
1093 // only region band mode possibility left here or null/empty
1094 const RegionBand
* pCurrent
= getRegionBand();
1098 // cannot exclude from empty, done
1102 const RegionBand
* pSource
= rRegion
.getRegionBand();
1106 // excluding nothing will do no change
1110 // prepare source and target
1111 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
1113 // union with source
1114 const bool bSuccess(pNew
->Exclude(*pSource
));
1122 mpRegionBand
= std::move(pNew
);
1125 bool vcl::Region::XOr( const vcl::Region
& rRegion
)
1127 if ( rRegion
.IsEmpty() )
1129 // empty region will not change local content
1133 if ( rRegion
.IsNull() )
1135 // error; cannot exclude null region from local since this is not representable
1137 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
1143 // rRect will be the xored-form (local off, rect on)
1150 // error: cannot exclude from null region since this is not representable
1152 OSL_ENSURE(false, "Region::XOr error: Cannot XOr with null region (!)");
1156 if( rRegion
.HasPolyPolygonOrB2DPolyPolygon() || HasPolyPolygonOrB2DPolyPolygon() )
1158 // get this B2DPolyPolygon
1159 basegfx::B2DPolyPolygon
aThisPolyPoly(GetAsB2DPolyPolygon());
1161 if(!aThisPolyPoly
.count())
1163 // rRect will be the xored-form (local off, rect on)
1168 aThisPolyPoly
= basegfx::utils::prepareForPolygonOperation( aThisPolyPoly
);
1170 // get the other B2DPolyPolygon
1171 basegfx::B2DPolyPolygon
aOtherPolyPoly(rRegion
.GetAsB2DPolyPolygon());
1172 aOtherPolyPoly
= basegfx::utils::prepareForPolygonOperation( aOtherPolyPoly
);
1174 basegfx::B2DPolyPolygon aClip
= basegfx::utils::solvePolygonOperationXor( aThisPolyPoly
, aOtherPolyPoly
);
1175 *this = vcl::Region( aClip
);
1179 // only region band mode possibility left here or null/empty
1180 const RegionBand
* pCurrent
= getRegionBand();
1184 // rRect will be the xored-form (local off, rect on)
1189 const RegionBand
* pSource
= rRegion
.getRegionBand();
1193 // empty region will not change local content
1197 // prepare source and target
1198 std::unique_ptr
<RegionBand
> pNew( std::make_unique
<RegionBand
>(*pCurrent
));
1200 // union with source
1201 pNew
->XOr(*pSource
);
1204 if(!pNew
->OptimizeBandList())
1209 mpRegionBand
= std::move(pNew
);
1214 tools::Rectangle
vcl::Region::GetBoundRect() const
1218 // no internal data? -> region is empty!
1219 return tools::Rectangle();
1224 // error; null region has no BoundRect
1225 // OSL_ENSURE(false, "Region::GetBoundRect error: null region has unlimited bound rect, not representable (!)");
1226 return tools::Rectangle();
1229 // prefer double precision source
1230 if(getB2DPolyPolygon())
1232 const basegfx::B2DRange
aRange(basegfx::utils::getRange(*getB2DPolyPolygon()));
1234 if(aRange
.isEmpty())
1236 // emulate PolyPolygon::GetBoundRect() when empty polygon
1237 return tools::Rectangle();
1241 // #i122149# corrected rounding, no need for ceil() and floor() here
1242 return tools::Rectangle(
1243 basegfx::fround(aRange
.getMinX()), basegfx::fround(aRange
.getMinY()),
1244 basegfx::fround(aRange
.getMaxX()), basegfx::fround(aRange
.getMaxY()));
1248 if(getPolyPolygon())
1250 return getPolyPolygon()->GetBoundRect();
1255 return getRegionBand()->GetBoundRect();
1258 return tools::Rectangle();
1261 tools::PolyPolygon
vcl::Region::GetAsPolyPolygon() const
1263 if(getPolyPolygon())
1265 return *getPolyPolygon();
1268 if(getB2DPolyPolygon())
1270 // the polygon needs to be converted, buffer the down conversion
1271 const tools::PolyPolygon
aPolyPolgon(*getB2DPolyPolygon());
1272 const_cast< vcl::Region
* >(this)->mpPolyPolygon
.reset(new tools::PolyPolygon(aPolyPolgon
));
1274 return *getPolyPolygon();
1279 // the BandRegion needs to be converted, buffer the conversion
1280 const tools::PolyPolygon
aPolyPolgon(ImplCreatePolyPolygonFromRegionBand());
1281 const_cast< vcl::Region
* >(this)->mpPolyPolygon
.reset(new tools::PolyPolygon(aPolyPolgon
));
1283 return *getPolyPolygon();
1286 return tools::PolyPolygon();
1289 basegfx::B2DPolyPolygon
vcl::Region::GetAsB2DPolyPolygon() const
1291 if(getB2DPolyPolygon())
1293 return *getB2DPolyPolygon();
1296 if(getPolyPolygon())
1298 // the polygon needs to be converted, buffer the up conversion. This will be preferred from now.
1299 const basegfx::B2DPolyPolygon
aB2DPolyPolygon(getPolyPolygon()->getB2DPolyPolygon());
1300 const_cast< vcl::Region
* >(this)->mpB2DPolyPolygon
.reset(new basegfx::B2DPolyPolygon(aB2DPolyPolygon
));
1302 return *getB2DPolyPolygon();
1307 // the BandRegion needs to be converted, buffer the conversion
1308 const basegfx::B2DPolyPolygon
aB2DPolyPolygon(ImplCreateB2DPolyPolygonFromRegionBand());
1309 const_cast< vcl::Region
* >(this)->mpB2DPolyPolygon
.reset(new basegfx::B2DPolyPolygon(aB2DPolyPolygon
));
1311 return *getB2DPolyPolygon();
1314 return basegfx::B2DPolyPolygon();
1317 const RegionBand
* vcl::Region::GetAsRegionBand() const
1319 if(!getRegionBand())
1321 if(getB2DPolyPolygon())
1323 // convert B2DPolyPolygon to RegionBand, buffer it and return it
1324 const_cast< vcl::Region
* >(this)->mpRegionBand
= ImplCreateRegionBandFromPolyPolygon(tools::PolyPolygon(*getB2DPolyPolygon()));
1326 else if(getPolyPolygon())
1328 // convert B2DPolyPolygon to RegionBand, buffer it and return it
1329 const_cast< vcl::Region
* >(this)->mpRegionBand
= ImplCreateRegionBandFromPolyPolygon(*getPolyPolygon());
1333 return getRegionBand();
1336 bool vcl::Region::IsInside( const Point
& rPoint
) const
1340 // no point can be in empty region
1346 // all points are inside null-region
1350 // Too expensive (?)
1351 //if(mpImplRegion->getRegionPolyPoly())
1353 // return mpImplRegion->getRegionPolyPoly()->IsInside( rPoint );
1356 // ensure RegionBand existence
1357 const RegionBand
* pRegionBand
= GetAsRegionBand();
1361 return pRegionBand
->IsInside(rPoint
);
1367 bool vcl::Region::IsOver( const tools::Rectangle
& rRect
) const
1371 // nothing can be over something empty
1377 // everything is over null region
1381 // Can we optimize this ??? - is used in StarDraw for brushes pointers
1382 // Why we have no IsOver for Regions ???
1383 // create region from rectangle and intersect own region
1384 vcl::Region
aRegion(rRect
);
1385 aRegion
.Intersect( *this );
1387 // rectangle is over if include is not empty
1388 return !aRegion
.IsEmpty();
1391 bool vcl::Region::IsRectangle() const
1393 if( IsEmpty() || IsNull() )
1396 if( getB2DPolyPolygon() )
1397 return basegfx::utils::isRectangle( *getB2DPolyPolygon() );
1399 if( getPolyPolygon() )
1400 return getPolyPolygon()->IsRect();
1402 if( getRegionBand() )
1403 return (getRegionBand()->getRectangleCount() == 1);
1408 void vcl::Region::SetNull()
1410 // reset all content
1411 mpB2DPolyPolygon
.reset();
1412 mpPolyPolygon
.reset();
1413 mpRegionBand
.reset();
1417 void vcl::Region::SetEmpty()
1419 // reset all content
1420 mpB2DPolyPolygon
.reset();
1421 mpPolyPolygon
.reset();
1422 mpRegionBand
.reset();
1426 Region
& vcl::Region::operator=( const vcl::Region
& ) = default;
1428 Region
& vcl::Region::operator=( vcl::Region
&& rRegion
) noexcept
1430 mpB2DPolyPolygon
= std::move(rRegion
.mpB2DPolyPolygon
);
1431 mpPolyPolygon
= std::move(rRegion
.mpPolyPolygon
);
1432 mpRegionBand
= std::move(rRegion
.mpRegionBand
);
1433 mbIsNull
= rRegion
.mbIsNull
;
1434 rRegion
.mbIsNull
= true;
1439 Region
& vcl::Region::operator=( const tools::Rectangle
& rRect
)
1441 mpB2DPolyPolygon
.reset();
1442 mpPolyPolygon
.reset();
1443 mpRegionBand
.reset(rRect
.IsEmpty() ? nullptr : new RegionBand(rRect
));
1449 bool vcl::Region::operator==( const vcl::Region
& rRegion
) const
1451 if(IsNull() && rRegion
.IsNull())
1453 // both are null region
1457 if(IsEmpty() && rRegion
.IsEmpty())
1463 if(getB2DPolyPolygon() && getB2DPolyPolygon() == rRegion
.getB2DPolyPolygon())
1465 // same instance data? -> equal
1469 if(getPolyPolygon() && getPolyPolygon() == rRegion
.getPolyPolygon())
1471 // same instance data? -> equal
1475 if(getRegionBand() && getRegionBand() == rRegion
.getRegionBand())
1477 // same instance data? -> equal
1481 if(IsNull() || IsEmpty())
1486 if(rRegion
.IsNull() || rRegion
.IsEmpty())
1491 if(rRegion
.getB2DPolyPolygon() || getB2DPolyPolygon())
1493 // one of both has a B2DPolyPolygon based region, ensure both have it
1494 // by evtl. conversion
1495 GetAsB2DPolyPolygon();
1496 rRegion
.GetAsB2DPolyPolygon();
1498 return *rRegion
.getB2DPolyPolygon() == *getB2DPolyPolygon();
1501 if(rRegion
.getPolyPolygon() || getPolyPolygon())
1503 // one of both has a B2DPolyPolygon based region, ensure both have it
1504 // by evtl. conversion
1506 rRegion
.GetAsPolyPolygon();
1508 return *rRegion
.getPolyPolygon() == *getPolyPolygon();
1511 // both are not empty or null (see above) and if content supported polygon
1512 // data the comparison is already done. Only both on RegionBand base can be left,
1514 if(rRegion
.getRegionBand() && getRegionBand())
1516 return *rRegion
.getRegionBand() == *getRegionBand();
1519 // should not happen, but better deny equality
1523 SvStream
& ReadRegion(SvStream
& rIStrm
, vcl::Region
& rRegion
)
1525 VersionCompat
aCompat(rIStrm
, StreamMode::READ
);
1526 sal_uInt16
nVersion(0);
1527 sal_uInt16
nTmp16(0);
1529 // clear region to be loaded
1532 // get version of streamed region
1533 rIStrm
.ReadUInt16( nVersion
);
1535 // get type of region
1536 rIStrm
.ReadUInt16( nTmp16
);
1538 enum RegionType
{ REGION_NULL
, REGION_EMPTY
, REGION_RECTANGLE
, REGION_COMPLEX
};
1539 RegionType meStreamedType
= static_cast<RegionType
>(nTmp16
);
1541 switch(meStreamedType
)
1557 RegionBand
* pNewRegionBand
= new RegionBand();
1558 bool bSuccess
= pNewRegionBand
->load(rIStrm
);
1559 rRegion
.mpRegionBand
.reset(pNewRegionBand
);
1561 bool bHasPolyPolygon(false);
1562 if (aCompat
.GetVersion() >= 2)
1564 rIStrm
.ReadCharAsBool( bHasPolyPolygon
);
1566 if (bHasPolyPolygon
)
1568 tools::PolyPolygon
* pNewPoly
= new tools::PolyPolygon();
1569 ReadPolyPolygon( rIStrm
, *pNewPoly
);
1570 rRegion
.mpPolyPolygon
.reset(pNewPoly
);
1574 if (!bSuccess
&& !bHasPolyPolygon
)
1576 SAL_WARN("vcl.gdi", "bad region band:" << bHasPolyPolygon
);
1587 SvStream
& WriteRegion( SvStream
& rOStrm
, const vcl::Region
& rRegion
)
1589 const sal_uInt16
nVersion(2);
1590 VersionCompat
aCompat(rOStrm
, StreamMode::WRITE
, nVersion
);
1593 rOStrm
.WriteUInt16( nVersion
);
1596 enum RegionType
{ REGION_NULL
, REGION_EMPTY
, REGION_RECTANGLE
, REGION_COMPLEX
};
1597 RegionType
aRegionType(REGION_COMPLEX
);
1598 bool bEmpty(rRegion
.IsEmpty());
1600 if(!bEmpty
&& rRegion
.getB2DPolyPolygon() && 0 == rRegion
.getB2DPolyPolygon()->count())
1602 OSL_ENSURE(false, "Region with empty B2DPolyPolygon, should not be created (!)");
1606 if(!bEmpty
&& rRegion
.getPolyPolygon() && 0 == rRegion
.getPolyPolygon()->Count())
1608 OSL_ENSURE(false, "Region with empty PolyPolygon, should not be created (!)");
1614 aRegionType
= REGION_EMPTY
;
1616 else if(rRegion
.IsNull())
1618 aRegionType
= REGION_NULL
;
1620 else if(rRegion
.getRegionBand() && rRegion
.getRegionBand()->isSingleRectangle())
1622 aRegionType
= REGION_RECTANGLE
;
1625 rOStrm
.WriteUInt16( aRegionType
);
1628 const RegionBand
* pRegionBand
= rRegion
.getRegionBand();
1632 pRegionBand
->save(rOStrm
);
1636 // for compatibility, write an empty RegionBand (will only write
1637 // the end marker STREAMENTRY_END, but this *is* needed)
1638 const RegionBand aRegionBand
;
1640 aRegionBand
.save(rOStrm
);
1643 // write polypolygon if available
1644 const bool bHasPolyPolygon(rRegion
.HasPolyPolygonOrB2DPolyPolygon());
1645 rOStrm
.WriteBool( bHasPolyPolygon
);
1650 tools::PolyPolygon aNoCurvePolyPolygon
;
1651 rRegion
.GetAsPolyPolygon().AdaptiveSubdivide(aNoCurvePolyPolygon
);
1653 WritePolyPolygon( rOStrm
, aNoCurvePolyPolygon
);
1659 void vcl::Region::GetRegionRectangles(RectangleVector
& rTarget
) const
1661 // clear returnvalues
1664 // ensure RegionBand existence
1665 const RegionBand
* pRegionBand
= GetAsRegionBand();
1669 pRegionBand
->GetRegionRectangles(rTarget
);
1673 static bool ImplPolygonRectTest( const tools::Polygon
& rPoly
, tools::Rectangle
* pRectOut
= nullptr )
1675 bool bIsRect
= false;
1676 const Point
* pPoints
= rPoly
.GetConstPointAry();
1677 sal_uInt16 nPoints
= rPoly
.GetSize();
1679 if( nPoints
== 4 || (nPoints
== 5 && pPoints
[0] == pPoints
[4]) )
1681 long nX1
= pPoints
[0].X(), nX2
= pPoints
[2].X(), nY1
= pPoints
[0].Y(), nY2
= pPoints
[2].Y();
1683 if( ( (pPoints
[1].X() == nX1
&& pPoints
[3].X() == nX2
) && (pPoints
[1].Y() == nY2
&& pPoints
[3].Y() == nY1
) )
1684 || ( (pPoints
[1].X() == nX2
&& pPoints
[3].X() == nX1
) && (pPoints
[1].Y() == nY1
&& pPoints
[3].Y() == nY2
) ) )
1716 pRectOut
->SetLeft( nX1
);
1717 pRectOut
->SetRight( nX2
);
1718 pRectOut
->SetTop( nY1
);
1719 pRectOut
->SetBottom( nY2
);
1727 vcl::Region
vcl::Region::GetRegionFromPolyPolygon( const tools::PolyPolygon
& rPolyPoly
)
1729 //return vcl::Region( rPolyPoly );
1731 // check if it's worth extracting the XOr'ing the Rectangles
1732 // empiricism shows that break even between XOr'ing rectangles separately
1733 // and ImplCreateRegionBandFromPolyPolygon is at half rectangles/half polygons
1734 int nPolygonRects
= 0, nPolygonPolygons
= 0;
1735 int nPolygons
= rPolyPoly
.Count();
1737 for( int i
= 0; i
< nPolygons
; i
++ )
1739 const tools::Polygon
& rPoly
= rPolyPoly
[i
];
1741 if( ImplPolygonRectTest( rPoly
) )
1751 if( nPolygonPolygons
> nPolygonRects
)
1753 return vcl::Region( rPolyPoly
);
1756 vcl::Region aResult
;
1757 tools::Rectangle aRect
;
1759 for( int i
= 0; i
< nPolygons
; i
++ )
1761 const tools::Polygon
& rPoly
= rPolyPoly
[i
];
1763 if( ImplPolygonRectTest( rPoly
, &aRect
) )
1765 aResult
.XOr( aRect
);
1769 aResult
.XOr( vcl::Region(rPoly
) );
1776 } /* namespace vcl */
1778 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */