1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
10 #include "gfx2DGlue.h"
11 #include "mozilla/ToString.h"
13 void nsRegion::AssertStateInternal() const {
15 // Verify consistent state inside the region.
16 int32_t lastY
= INT32_MIN
;
17 int32_t lowestX
= INT32_MAX
;
18 int32_t highestX
= INT32_MIN
;
19 for (auto iter
= mBands
.begin(); iter
!= mBands
.end(); iter
++) {
20 const Band
& band
= *iter
;
21 if (band
.bottom
<= band
.top
) {
25 if (band
.top
< lastY
) {
31 lowestX
= std::min(lowestX
, band
.mStrips
.begin()->left
);
32 highestX
= std::max(highestX
, band
.mStrips
.LastElement().right
);
34 int32_t lastX
= INT32_MIN
;
35 if (iter
!= mBands
.begin()) {
39 if (prev
->bottom
== iter
->top
) {
40 if (band
.EqualStrips(*prev
)) {
46 for (const Strip
& strip
: band
.mStrips
) {
47 if (strip
.right
<= strip
.left
) {
51 if (strip
.left
<= lastX
) {
62 if (!(mBounds
.IsEqualEdges(CalculateBounds()))) {
68 if (mCurrentOpGenerator
) {
69 mCurrentOpGenerator
->OutputOp();
76 bool nsRegion::Contains(const nsRegion
& aRgn
) const {
77 // XXX this could be made faster by iterating over
78 // both regions at the same time some how
79 for (auto iter
= aRgn
.RectIter(); !iter
.Done(); iter
.Next()) {
80 if (!Contains(iter
.Get())) {
87 bool nsRegion::Intersects(const nsRectAbsolute
& aRect
) const {
88 if (mBands
.IsEmpty()) {
89 return mBounds
.Intersects(aRect
);
92 if (!mBounds
.Intersects(aRect
)) {
96 Strip
rectStrip(aRect
.X(), aRect
.XMost());
98 auto iter
= mBands
.begin();
99 while (iter
!= mBands
.end()) {
100 if (iter
->top
>= aRect
.YMost()) {
104 if (iter
->bottom
<= aRect
.Y()) {
105 // This band is entirely before aRect, move on.
110 if (!iter
->Intersects(rectStrip
)) {
111 // This band does not intersect aRect horizontally. Move on.
116 // This band intersects with aRect.
123 void nsRegion::Inflate(const nsMargin
& aMargin
) {
125 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
126 nsRectAbsolute rect
= iter
.GetAbsolute();
127 rect
.Inflate(aMargin
);
128 newRegion
.AddRect(rect
);
131 *this = std::move(newRegion
);
134 void nsRegion::SimplifyOutward(uint32_t aMaxRects
) {
135 MOZ_ASSERT(aMaxRects
>= 1, "Invalid max rect count");
137 if (GetNumRects() <= aMaxRects
) {
141 // Try combining rects in horizontal bands into a single rect
142 // The goal here is to try to keep groups of rectangles that are vertically
143 // discontiguous as separate rectangles in the final region. This is
144 // simple and fast to implement and page contents tend to vary more
145 // vertically than horizontally (which is why our rectangles are stored
146 // sorted by y-coordinate, too).
148 // Note: if boxes share y1 because of the canonical representation they
153 while (idx
< mBands
.Length()) {
155 mBands
[idx
].mStrips
.begin()->right
=
156 mBands
[idx
].mStrips
.LastElement().right
;
157 mBands
[idx
].mStrips
.TruncateLength(1);
160 // Merge any bands with the same bounds.
161 while (idx
< mBands
.Length() &&
162 mBands
[idx
].mStrips
.begin()->left
==
163 mBands
[oldIdx
].mStrips
.begin()->left
&&
164 mBands
[idx
].mStrips
.LastElement().right
==
165 mBands
[oldIdx
].mStrips
.begin()->right
) {
166 mBands
[oldIdx
].bottom
= mBands
[idx
].bottom
;
167 mBands
.RemoveElementAt(idx
);
173 // mBands.size() is now equal to our rect count.
174 if (mBands
.Length() > aMaxRects
) {
179 // compute the covered area difference between two rows.
180 // by iterating over both rows simultaneously and adding up
181 // the additional increase in area caused by extending each
182 // of the rectangles to the combined height of both rows
183 uint32_t nsRegion::ComputeMergedAreaIncrease(const Band
& aTopBand
,
184 const Band
& aBottomBand
) {
185 uint32_t totalArea
= 0;
187 uint32_t topHeight
= aBottomBand
.top
- aTopBand
.top
;
188 uint32_t bottomHeight
= aBottomBand
.bottom
- aTopBand
.bottom
;
189 uint32_t currentStripBottom
= 0;
191 // This could be done with slightly better worse case performance by merging
192 // these two for-loops, but this makes the code a lot easier to understand.
193 for (auto& strip
: aTopBand
.mStrips
) {
194 if (currentStripBottom
== aBottomBand
.mStrips
.Length() ||
195 strip
.right
< aBottomBand
.mStrips
[currentStripBottom
].left
) {
196 totalArea
+= bottomHeight
* strip
.Size();
200 int32_t currentX
= strip
.left
;
201 while (currentStripBottom
!= aBottomBand
.mStrips
.Length() &&
202 aBottomBand
.mStrips
[currentStripBottom
].left
< strip
.right
) {
203 if (currentX
>= strip
.right
) {
206 if (currentX
< aBottomBand
.mStrips
[currentStripBottom
].left
) {
207 // Add the part that's not intersecting.
208 totalArea
+= (aBottomBand
.mStrips
[currentStripBottom
].left
- currentX
) *
213 std::max(aBottomBand
.mStrips
[currentStripBottom
].right
, currentX
);
214 currentStripBottom
++;
217 // Add remainder of this strip.
218 if (currentX
< strip
.right
) {
219 totalArea
+= (strip
.right
- currentX
) * bottomHeight
;
221 if (currentStripBottom
) {
222 currentStripBottom
--;
225 uint32_t currentStripTop
= 0;
226 for (auto& strip
: aBottomBand
.mStrips
) {
227 if (currentStripTop
== aTopBand
.mStrips
.Length() ||
228 strip
.right
< aTopBand
.mStrips
[currentStripTop
].left
) {
229 totalArea
+= topHeight
* strip
.Size();
233 int32_t currentX
= strip
.left
;
234 while (currentStripTop
!= aTopBand
.mStrips
.Length() &&
235 aTopBand
.mStrips
[currentStripTop
].left
< strip
.right
) {
236 if (currentX
>= strip
.right
) {
239 if (currentX
< aTopBand
.mStrips
[currentStripTop
].left
) {
240 // Add the part that's not intersecting.
242 (aTopBand
.mStrips
[currentStripTop
].left
- currentX
) * topHeight
;
245 currentX
= std::max(aTopBand
.mStrips
[currentStripTop
].right
, currentX
);
249 // Add remainder of this strip.
250 if (currentX
< strip
.right
) {
251 totalArea
+= (strip
.right
- currentX
) * topHeight
;
253 if (currentStripTop
) {
260 void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold
) {
261 if (mBands
.Length() < 2) {
262 // We have only one or no row and we're done.
266 uint32_t currentBand
= 0;
268 Band
& band
= mBands
[currentBand
];
271 ComputeMergedAreaIncrease(band
, mBands
[currentBand
+ 1]);
273 if (totalArea
<= aThreshold
) {
274 for (Strip
& strip
: mBands
[currentBand
+ 1].mStrips
) {
275 // This could use an optimized function to merge two bands.
276 band
.InsertStrip(strip
);
278 band
.bottom
= mBands
[currentBand
+ 1].bottom
;
279 mBands
.RemoveElementAt(currentBand
+ 1);
283 } while (currentBand
+ 1 < mBands
.Length());
289 typedef void (*visit_fn
)(void* closure
, VisitSide side
, int x1
, int y1
, int x2
,
292 void nsRegion::VisitEdges(visit_fn visit
, void* closure
) const {
293 if (mBands
.IsEmpty()) {
294 visit(closure
, VisitSide::LEFT
, mBounds
.X(), mBounds
.Y(), mBounds
.X(),
296 visit(closure
, VisitSide::RIGHT
, mBounds
.XMost(), mBounds
.Y(),
297 mBounds
.XMost(), mBounds
.YMost());
298 visit(closure
, VisitSide::TOP
, mBounds
.X() - 1, mBounds
.Y(),
299 mBounds
.XMost() + 1, mBounds
.Y());
300 visit(closure
, VisitSide::BOTTOM
, mBounds
.X() - 1, mBounds
.YMost(),
301 mBounds
.XMost() + 1, mBounds
.YMost());
305 auto band
= std::begin(mBands
);
306 auto bandFinal
= std::end(mBands
);
308 for (const Strip
& strip
: band
->mStrips
) {
309 visit(closure
, VisitSide::LEFT
, strip
.left
, band
->top
, strip
.left
,
311 visit(closure
, VisitSide::RIGHT
, strip
.right
, band
->top
, strip
.right
,
313 visit(closure
, VisitSide::TOP
, strip
.left
- 1, band
->top
, strip
.right
+ 1,
317 if (band
!= bandFinal
) {
319 const Band
& topBand
= *band
;
322 for (const Strip
& strip
: band
->mStrips
) {
323 visit(closure
, VisitSide::LEFT
, strip
.left
, band
->top
, strip
.left
,
325 visit(closure
, VisitSide::RIGHT
, strip
.right
, band
->top
, strip
.right
,
329 if (band
->top
== topBand
.bottom
) {
330 // Two bands touching each other vertically.
331 const Band
& bottomBand
= *band
;
332 auto topStrip
= std::begin(topBand
.mStrips
);
333 auto bottomStrip
= std::begin(bottomBand
.mStrips
);
335 int y
= topBand
.bottom
;
337 // State from this point on along the vertical edge:
339 // 1 - Touched by top rect
340 // 2 - Touched by bottom rect
341 // 3 - Touched on both sides
343 const int TouchedByNothing
= 0;
344 const int TouchedByTop
= 1;
345 const int TouchedByBottom
= 2;
346 // We always start with nothing.
347 int oldState
= TouchedByNothing
;
348 // Last state change, adjusted by -1 if the last state change was
349 // a change away from 0.
350 int lastX
= std::min(topStrip
->left
, bottomStrip
->left
) - 1;
352 // Current edge being considered for top and bottom,
353 // 0 - left, 1 - right.
354 bool topEdgeIsLeft
= true;
355 bool bottomEdgeIsLeft
= true;
356 while (topStrip
!= std::end(topBand
.mStrips
) &&
357 bottomStrip
!= std::end(bottomBand
.mStrips
)) {
361 topPos
= topStrip
->left
;
363 topPos
= topStrip
->right
;
365 if (bottomEdgeIsLeft
) {
366 bottomPos
= bottomStrip
->left
;
368 bottomPos
= bottomStrip
->right
;
371 int currentX
= std::min(topPos
, bottomPos
);
372 if (topPos
< bottomPos
) {
374 state
= oldState
| TouchedByTop
;
376 state
= oldState
^ TouchedByTop
;
379 topEdgeIsLeft
= !topEdgeIsLeft
;
380 } else if (bottomPos
< topPos
) {
381 if (bottomEdgeIsLeft
) {
382 state
= oldState
| TouchedByBottom
;
384 state
= oldState
^ TouchedByBottom
;
387 bottomEdgeIsLeft
= !bottomEdgeIsLeft
;
389 // bottomPos == topPos
390 state
= TouchedByNothing
;
391 if (bottomEdgeIsLeft
) {
392 state
= TouchedByBottom
;
397 state
|= TouchedByTop
;
401 topEdgeIsLeft
= !topEdgeIsLeft
;
402 bottomEdgeIsLeft
= !bottomEdgeIsLeft
;
405 MOZ_ASSERT(state
!= oldState
);
406 if (oldState
== TouchedByNothing
) {
407 // We had nothing before, make sure the left edge will be padded.
408 lastX
= currentX
- 1;
409 } else if (oldState
== TouchedByTop
) {
410 if (state
== TouchedByNothing
) {
411 visit(closure
, VisitSide::BOTTOM
, lastX
, y
, currentX
+ 1, y
);
413 visit(closure
, VisitSide::BOTTOM
, lastX
, y
, currentX
, y
);
416 } else if (oldState
== TouchedByBottom
) {
417 if (state
== TouchedByNothing
) {
418 visit(closure
, VisitSide::TOP
, lastX
, y
, currentX
+ 1, y
);
420 visit(closure
, VisitSide::TOP
, lastX
, y
, currentX
, y
);
429 MOZ_ASSERT(!state
|| (topEdgeIsLeft
|| bottomEdgeIsLeft
));
430 if (topStrip
!= std::end(topBand
.mStrips
)) {
431 if (!topEdgeIsLeft
) {
432 visit(closure
, VisitSide::BOTTOM
, lastX
, y
, topStrip
->right
+ 1, y
);
435 while (topStrip
!= std::end(topBand
.mStrips
)) {
436 visit(closure
, VisitSide::BOTTOM
, topStrip
->left
- 1, y
,
437 topStrip
->right
+ 1, y
);
440 } else if (bottomStrip
!= std::end(bottomBand
.mStrips
)) {
441 if (!bottomEdgeIsLeft
) {
442 visit(closure
, VisitSide::TOP
, lastX
, y
, bottomStrip
->right
+ 1, y
);
445 while (bottomStrip
!= std::end(bottomBand
.mStrips
)) {
446 visit(closure
, VisitSide::TOP
, bottomStrip
->left
- 1, y
,
447 bottomStrip
->right
+ 1, y
);
452 for (const Strip
& strip
: topBand
.mStrips
) {
453 visit(closure
, VisitSide::BOTTOM
, strip
.left
- 1, topBand
.bottom
,
454 strip
.right
+ 1, topBand
.bottom
);
456 for (const Strip
& strip
: band
->mStrips
) {
457 visit(closure
, VisitSide::TOP
, strip
.left
- 1, band
->top
,
458 strip
.right
+ 1, band
->top
);
461 } while (band
!= bandFinal
);
464 for (const Strip
& strip
: band
->mStrips
) {
465 visit(closure
, VisitSide::BOTTOM
, strip
.left
- 1, band
->bottom
,
466 strip
.right
+ 1, band
->bottom
);
470 void nsRegion::SimplifyInward(uint32_t aMaxRects
) {
471 NS_ASSERTION(aMaxRects
>= 1, "Invalid max rect count");
473 if (GetNumRects() <= aMaxRects
) return;
478 uint64_t nsRegion::Area() const {
479 if (mBands
.IsEmpty()) {
480 return mBounds
.Area();
484 for (const Band
& band
: mBands
) {
485 uint32_t height
= band
.bottom
- band
.top
;
486 for (const Strip
& strip
: band
.mStrips
) {
487 area
+= (strip
.right
- strip
.left
) * height
;
494 nsRegion
& nsRegion::ScaleRoundOut(float aXScale
, float aYScale
) {
495 if (mozilla::gfx::FuzzyEqual(aXScale
, 1.0f
) &&
496 mozilla::gfx::FuzzyEqual(aYScale
, 1.0f
)) {
501 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
502 nsRectAbsolute rect
= iter
.GetAbsolute();
503 rect
.ScaleRoundOut(aXScale
, aYScale
);
504 newRegion
.AddRect(rect
);
507 *this = std::move(newRegion
);
511 nsRegion
& nsRegion::ScaleInverseRoundOut(float aXScale
, float aYScale
) {
513 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
514 nsRectAbsolute rect
= iter
.GetAbsolute();
515 rect
.ScaleInverseRoundOut(aXScale
, aYScale
);
516 newRegion
.AddRect(rect
);
519 *this = std::move(newRegion
);
523 static mozilla::gfx::IntRect
TransformRect(
524 const mozilla::gfx::IntRect
& aRect
,
525 const mozilla::gfx::Matrix4x4
& aTransform
) {
526 if (aRect
.IsEmpty()) {
527 return mozilla::gfx::IntRect();
530 mozilla::gfx::RectDouble
rect(aRect
.X(), aRect
.Y(), aRect
.Width(),
532 rect
= aTransform
.TransformAndClipBounds(
533 rect
, mozilla::gfx::RectDouble::MaxIntRect());
536 mozilla::gfx::IntRect intRect
;
537 if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect
), &intRect
)) {
538 return mozilla::gfx::IntRect();
544 nsRegion
& nsRegion::Transform(const mozilla::gfx::Matrix4x4
& aTransform
) {
546 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
547 nsRect rect
= nsIntRegion::ToRect(
548 TransformRect(nsIntRegion::FromRect(iter
.Get()), aTransform
));
549 newRegion
.AddRect(nsRectAbsolute::FromRect(rect
));
552 *this = std::move(newRegion
);
556 nsRegion
nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP
,
557 int32_t aToAPP
) const {
558 if (aFromAPP
== aToAPP
) {
562 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
563 nsRect rect
= iter
.Get();
564 rect
= rect
.ScaleToOtherAppUnitsRoundOut(aFromAPP
, aToAPP
);
565 newRegion
.AddRect(nsRectAbsolute::FromRect(rect
));
571 nsRegion
nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP
,
572 int32_t aToAPP
) const {
573 if (aFromAPP
== aToAPP
) {
578 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
579 nsRect rect
= iter
.Get();
580 rect
= rect
.ScaleToOtherAppUnitsRoundIn(aFromAPP
, aToAPP
);
581 newRegion
.AddRect(nsRectAbsolute::FromRect(rect
));
587 nsIntRegion
nsRegion::ToPixels(nscoord aAppUnitsPerPixel
,
588 bool aOutsidePixels
) const {
589 nsIntRegion intRegion
;
590 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
591 mozilla::gfx::IntRect deviceRect
;
592 nsRect rect
= iter
.Get();
594 deviceRect
= rect
.ToOutsidePixels(aAppUnitsPerPixel
);
596 deviceRect
= rect
.ToNearestPixels(aAppUnitsPerPixel
);
597 intRegion
.OrWith(deviceRect
);
603 nsIntRegion
nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel
) const {
604 return ToPixels(aAppUnitsPerPixel
, true);
607 nsIntRegion
nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel
) const {
608 return ToPixels(aAppUnitsPerPixel
, false);
611 nsIntRegion
nsRegion::ScaleToNearestPixels(float aScaleX
, float aScaleY
,
612 nscoord aAppUnitsPerPixel
) const {
614 for (auto iter
= RectIter(); !iter
.Done(); iter
.Next()) {
615 mozilla::gfx::IntRect deviceRect
=
616 iter
.Get().ScaleToNearestPixels(aScaleX
, aScaleY
, aAppUnitsPerPixel
);
617 result
.Or(result
, deviceRect
);
622 nsIntRegion
nsRegion::ScaleToOutsidePixels(float aScaleX
, float aScaleY
,
623 nscoord aAppUnitsPerPixel
) const {
624 // make a copy of the region so that we can mutate it inplace
625 nsIntRegion intRegion
;
626 for (RectIterator iter
= RectIterator(*this); !iter
.Done(); iter
.Next()) {
627 nsRect rect
= iter
.Get();
629 rect
.ScaleToOutsidePixels(aScaleX
, aScaleY
, aAppUnitsPerPixel
));
634 nsIntRegion
nsRegion::ScaleToInsidePixels(float aScaleX
, float aScaleY
,
635 nscoord aAppUnitsPerPixel
) const {
636 /* When scaling a rect, walk forward through the rect list up until the y
637 * value is greater than the current rect's YMost() value.
639 * For each rect found, check if the rects have a touching edge (in unscaled
640 * coordinates), and if one edge is entirely contained within the other.
642 * If it is, then the contained edge can be moved (in scaled pixels) to ensure
643 * that no gap exists.
645 * Since this could be potentially expensive - O(n^2), we only attempt this
646 * algorithm for the first rect.
649 if (mBands
.IsEmpty()) {
650 nsIntRect rect
= mBounds
.ToNSRect().ScaleToInsidePixels(aScaleX
, aScaleY
,
652 return nsIntRegion(rect
);
655 nsIntRegion intRegion
;
656 RectIterator iter
= RectIterator(*this);
658 nsRect first
= iter
.Get();
660 mozilla::gfx::IntRect firstDeviceRect
=
661 first
.ScaleToInsidePixels(aScaleX
, aScaleY
, aAppUnitsPerPixel
);
663 for (iter
.Next(); !iter
.Done(); iter
.Next()) {
664 nsRect rect
= iter
.Get();
665 mozilla::gfx::IntRect deviceRect
=
666 rect
.ScaleToInsidePixels(aScaleX
, aScaleY
, aAppUnitsPerPixel
);
668 if (rect
.Y() <= first
.YMost()) {
669 if (rect
.XMost() == first
.X() && rect
.YMost() <= first
.YMost()) {
670 // rect is touching on the left edge of the first rect and contained
671 // within the length of its left edge
672 deviceRect
.SetRightEdge(firstDeviceRect
.X());
673 } else if (rect
.X() == first
.XMost() && rect
.YMost() <= first
.YMost()) {
674 // rect is touching on the right edge of the first rect and contained
675 // within the length of its right edge
676 deviceRect
.SetLeftEdge(firstDeviceRect
.XMost());
677 } else if (rect
.Y() == first
.YMost()) {
678 // The bottom of the first rect is on the same line as the top of rect,
679 // but they aren't necessarily contained.
680 if (rect
.X() <= first
.X() && rect
.XMost() >= first
.XMost()) {
681 // The top of rect contains the bottom of the first rect
682 firstDeviceRect
.SetBottomEdge(deviceRect
.Y());
683 } else if (rect
.X() >= first
.X() && rect
.XMost() <= first
.XMost()) {
684 // The bottom of the first contains the top of rect
685 deviceRect
.SetTopEdge(firstDeviceRect
.YMost());
690 intRegion
.OrWith(deviceRect
);
693 intRegion
.OrWith(firstDeviceRect
);
697 // A cell's "value" is a pair consisting of
698 // a) the area of the subrectangle it corresponds to, if it's in
699 // aContainingRect and in the region, 0 otherwise
700 // b) the area of the subrectangle it corresponds to, if it's in the region,
702 // Addition, subtraction and identity are defined on these values in the
703 // obvious way. Partial order is lexicographic.
704 // A "large negative value" is defined with large negative numbers for both
705 // fields of the pair. This negative value has the property that adding any
706 // number of non-negative values to it always results in a negative value.
708 // The GetLargestRectangle algorithm works in three phases:
709 // 1) Convert the region into a grid by adding vertical/horizontal lines for
710 // each edge of each rectangle in the region.
711 // 2) For each rectangle in the region, for each cell it contains, set that
712 // cells's value as described above.
713 // 3) Calculate the submatrix with the largest sum such that none of its cells
714 // contain any 0s (empty regions). The rectangle represented by the
715 // submatrix is the largest rectangle in the region.
717 // Let k be the number of rectangles in the region.
718 // Let m be the height of the grid generated in step 1.
719 // Let n be the width of the grid generated in step 1.
721 // Step 1 is O(k) in time and O(m+n) in space for the sparse grid.
722 // Step 2 is O(mn) in time and O(mn) in additional space for the full grid.
723 // Step 3 is O(m^2 n) in time and O(mn) in additional space
725 // The implementation of steps 1 and 2 are rather straightforward. However our
726 // implementation of step 3 uses dynamic programming to achieve its efficiency.
728 // Psuedo code for step 3 is as follows where G is the grid from step 1 and A
729 // is the array from step 2:
730 // Phase3 = function (G, A, m, n) {
731 // let (t,b,l,r,_) = MaxSum2D(A,m,n)
732 // return rect(G[t],G[l],G[r],G[b]);
734 // MaxSum2D = function (A, m, n) {
735 // S = array(m+1,n+1)
736 // S[0][i] = 0 for i in [0,n]
737 // S[j][0] = 0 for j in [0,m]
738 // S[j][i] = (if A[j-1][i-1] = 0 then some large negative value
740 // + S[j-1][n] + S[j][i-1] - S[j-1][i-1]
742 // // top, bottom, left, right, area
743 // var maxRect = (-1, -1, -1, -1, 0);
745 // for all (m',m'') in [0, m]^2 {
746 // let B = { S[m'][i] - S[m''][i] | 0 <= i <= n }
747 // let ((l,r),area) = MaxSum1D(B,n+1)
748 // if (area > maxRect.area) {
749 // maxRect := (m', m'', l, r, area)
756 // Originally taken from Improved algorithms for the k-maximum subarray problem
757 // for small k - SE Bae, T Takaoka but modified to show the explicit tracking
758 // of indices and we already have the prefix sums from our one call site so
759 // there's no need to construct them.
760 // MaxSum1D = function (A,n) {
763 // var maxIndices = (0,0);
765 // for i in range(n) {
766 // let cand = A[i] - min;
769 // maxIndices := (minIdx, i)
776 // return (minIdx, maxIdx, max);
780 // This class represents a partitioning of an axis delineated by coordinates.
781 // It internally maintains a sorted array of coordinates.
782 class AxisPartition
{
784 // Adds a new partition at the given coordinate to this partitioning. If
785 // the coordinate is already present in the partitioning, this does nothing.
786 void InsertCoord(nscoord c
) {
787 uint32_t i
= mStops
.IndexOfFirstElementGt(c
);
788 if (i
== 0 || mStops
[i
- 1] != c
) {
789 mStops
.InsertElementAt(i
, c
);
793 // Returns the array index of the given partition point. The partition
794 // point must already be present in the partitioning.
795 int32_t IndexOf(nscoord p
) const { return mStops
.BinaryIndexOf(p
); }
797 // Returns the partition at the given index which must be non-zero and
798 // less than the number of partitions in this partitioning.
799 nscoord
StopAt(int32_t index
) const { return mStops
[index
]; }
801 // Returns the size of the gap between the partition at the given index and
802 // the next partition in this partitioning. If the index is the last index
803 // in the partitioning, the result is undefined.
804 nscoord
StopSize(int32_t index
) const {
805 return mStops
[index
+ 1] - mStops
[index
];
808 // Returns the number of partitions in this partitioning.
809 int32_t GetNumStops() const { return mStops
.Length(); }
812 nsTArray
<nscoord
> mStops
;
815 const int64_t kVeryLargeNegativeNumber
= 0xffff000000000000ll
;
818 int64_t mSizeContainingRect
;
821 SizePair() : mSizeContainingRect(0), mSize(0) {}
823 static SizePair
VeryLargeNegative() {
825 result
.mSize
= result
.mSizeContainingRect
= kVeryLargeNegativeNumber
;
828 bool operator<(const SizePair
& aOther
) const {
829 if (mSizeContainingRect
< aOther
.mSizeContainingRect
) return true;
830 if (mSizeContainingRect
> aOther
.mSizeContainingRect
) return false;
831 return mSize
< aOther
.mSize
;
833 bool operator>(const SizePair
& aOther
) const {
834 return aOther
.operator<(*this);
836 SizePair
operator+(const SizePair
& aOther
) const {
837 SizePair result
= *this;
838 result
.mSizeContainingRect
+= aOther
.mSizeContainingRect
;
839 result
.mSize
+= aOther
.mSize
;
842 SizePair
operator-(const SizePair
& aOther
) const {
843 SizePair result
= *this;
844 result
.mSizeContainingRect
-= aOther
.mSizeContainingRect
;
845 result
.mSize
-= aOther
.mSize
;
850 // Returns the sum and indices of the subarray with the maximum sum of the
851 // given array (A,n), assuming the array is already in prefix sum form.
852 SizePair
MaxSum1D(const nsTArray
<SizePair
>& A
, int32_t n
, int32_t* minIdx
,
854 // The min/max indicies of the largest subarray found so far
856 int32_t currentMinIdx
= 0;
861 // Because we're given the array in prefix sum form, we know the first
863 for (int32_t i
= 1; i
< n
; i
++) {
864 SizePair cand
= A
[i
] - min
;
867 *minIdx
= currentMinIdx
;
880 nsRect
nsRegion::GetLargestRectangle(const nsRect
& aContainingRect
) const {
883 if (GetNumRects() <= 1) {
884 bestRect
= GetBounds();
888 AxisPartition xaxis
, yaxis
;
890 // Step 1: Calculate the grid lines
891 for (auto iter
= RectIter(); !iter
.Done(); iter
.Next()) {
892 const nsRect
& rect
= iter
.Get();
893 xaxis
.InsertCoord(rect
.X());
894 xaxis
.InsertCoord(rect
.XMost());
895 yaxis
.InsertCoord(rect
.Y());
896 yaxis
.InsertCoord(rect
.YMost());
898 if (!aContainingRect
.IsEmpty()) {
899 xaxis
.InsertCoord(aContainingRect
.X());
900 xaxis
.InsertCoord(aContainingRect
.XMost());
901 yaxis
.InsertCoord(aContainingRect
.Y());
902 yaxis
.InsertCoord(aContainingRect
.YMost());
905 // Step 2: Fill out the grid with the areas
906 // Note: due to the ordering of rectangles in the region, it is not always
907 // possible to combine steps 2 and 3 so we don't try to be clever.
908 int32_t matrixHeight
= yaxis
.GetNumStops() - 1;
909 int32_t matrixWidth
= xaxis
.GetNumStops() - 1;
910 int32_t matrixSize
= matrixHeight
* matrixWidth
;
911 nsTArray
<SizePair
> areas(matrixSize
);
912 areas
.SetLength(matrixSize
);
914 for (auto iter
= RectIter(); !iter
.Done(); iter
.Next()) {
915 const nsRect
& rect
= iter
.Get();
916 int32_t xstart
= xaxis
.IndexOf(rect
.X());
917 int32_t xend
= xaxis
.IndexOf(rect
.XMost());
918 int32_t y
= yaxis
.IndexOf(rect
.Y());
919 int32_t yend
= yaxis
.IndexOf(rect
.YMost());
921 for (; y
< yend
; y
++) {
922 nscoord height
= yaxis
.StopSize(y
);
923 for (int32_t x
= xstart
; x
< xend
; x
++) {
924 nscoord width
= xaxis
.StopSize(x
);
925 int64_t size
= width
* int64_t(height
);
926 if (rect
.Intersects(aContainingRect
)) {
927 areas
[y
* matrixWidth
+ x
].mSizeContainingRect
= size
;
929 areas
[y
* matrixWidth
+ x
].mSize
= size
;
934 // Step 3: Find the maximum submatrix sum that does not contain a rectangle
936 // First get the prefix sum array
937 int32_t m
= matrixHeight
+ 1;
938 int32_t n
= matrixWidth
+ 1;
939 nsTArray
<SizePair
> pareas(m
* n
);
940 pareas
.SetLength(m
* n
);
941 for (int32_t y
= 1; y
< m
; y
++) {
942 for (int32_t x
= 1; x
< n
; x
++) {
943 SizePair area
= areas
[(y
- 1) * matrixWidth
+ x
- 1];
945 area
= SizePair::VeryLargeNegative();
947 area
= area
+ pareas
[y
* n
+ x
- 1] + pareas
[(y
- 1) * n
+ x
] -
948 pareas
[(y
- 1) * n
+ x
- 1];
949 pareas
[y
* n
+ x
] = area
;
953 // No longer need the grid
958 int32_t left
, top
, right
, bottom
;
959 } bestRectIndices
= {0, 0, 0, 0};
960 for (int32_t m1
= 0; m1
< m
; m1
++) {
961 for (int32_t m2
= m1
+ 1; m2
< m
; m2
++) {
962 nsTArray
<SizePair
> B
;
964 for (int32_t i
= 0; i
< n
; i
++) {
965 B
[i
] = pareas
[m2
* n
+ i
] - pareas
[m1
* n
+ i
];
967 int32_t minIdx
, maxIdx
;
968 SizePair area
= MaxSum1D(B
, n
, &minIdx
, &maxIdx
);
969 if (area
> bestArea
) {
970 bestRectIndices
.left
= minIdx
;
971 bestRectIndices
.top
= m1
;
972 bestRectIndices
.right
= maxIdx
;
973 bestRectIndices
.bottom
= m2
;
979 bestRect
.MoveTo(xaxis
.StopAt(bestRectIndices
.left
),
980 yaxis
.StopAt(bestRectIndices
.top
));
981 bestRect
.SizeTo(xaxis
.StopAt(bestRectIndices
.right
) - bestRect
.X(),
982 yaxis
.StopAt(bestRectIndices
.bottom
) - bestRect
.Y());
988 std::ostream
& operator<<(std::ostream
& stream
, const nsRegion
& m
) {
992 for (auto iter
= m
.RectIter(); !iter
.Done(); iter
.Next()) {
998 const nsRect
& rect
= iter
.Get();
999 stream
<< rect
.X() << "," << rect
.Y() << "," << rect
.XMost() << ","
1007 void nsRegion::OutputToStream(std::string aObjName
,
1008 std::ostream
& stream
) const {
1009 auto iter
= RectIter();
1010 nsRect r
= iter
.Get();
1011 stream
<< "nsRegion " << aObjName
<< "(nsRect(" << r
.X() << ", " << r
.Y()
1012 << ", " << r
.Width() << ", " << r
.Height() << "));\n";
1015 for (; !iter
.Done(); iter
.Next()) {
1016 nsRect r
= iter
.Get();
1017 stream
<< aObjName
<< ".OrWith(nsRect(" << r
.X() << ", " << r
.Y() << ", "
1018 << r
.Width() << ", " << r
.Height() << "));\n";
1022 nsCString
nsRegion::ToString() const {
1023 return nsCString(mozilla::ToString(*this).c_str());