Bug 1891710: part 2) Enable <Element-outerHTML.html> WPT for Trusted Types. r=smaug
[gecko.git] / gfx / src / nsRegion.cpp
blobaa50d44fc4860968183e1664a3f0823f26891d93
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/. */
7 #include "nsRegion.h"
8 #include "nsTArray.h"
9 #include "gfxUtils.h"
10 #include "gfx2DGlue.h"
11 #include "mozilla/ToString.h"
13 void nsRegion::AssertStateInternal() const {
14 bool failed = false;
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) {
22 failed = true;
23 break;
25 if (band.top < lastY) {
26 failed = true;
27 break;
29 lastY = band.bottom;
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()) {
36 auto prev = iter;
37 prev--;
39 if (prev->bottom == iter->top) {
40 if (band.EqualStrips(*prev)) {
41 failed = true;
42 break;
46 for (const Strip& strip : band.mStrips) {
47 if (strip.right <= strip.left) {
48 failed = true;
49 break;
51 if (strip.left <= lastX) {
52 failed = true;
53 break;
55 lastX = strip.right;
57 if (failed) {
58 break;
62 if (!(mBounds.IsEqualEdges(CalculateBounds()))) {
63 failed = true;
66 if (failed) {
67 #ifdef DEBUG_REGIONS
68 if (mCurrentOpGenerator) {
69 mCurrentOpGenerator->OutputOp();
71 #endif
72 MOZ_ASSERT(false);
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())) {
81 return false;
84 return true;
87 bool nsRegion::Intersects(const nsRectAbsolute& aRect) const {
88 if (mBands.IsEmpty()) {
89 return mBounds.Intersects(aRect);
92 if (!mBounds.Intersects(aRect)) {
93 return false;
96 Strip rectStrip(aRect.X(), aRect.XMost());
98 auto iter = mBands.begin();
99 while (iter != mBands.end()) {
100 if (iter->top >= aRect.YMost()) {
101 return false;
104 if (iter->bottom <= aRect.Y()) {
105 // This band is entirely before aRect, move on.
106 iter++;
107 continue;
110 if (!iter->Intersects(rectStrip)) {
111 // This band does not intersect aRect horizontally. Move on.
112 iter++;
113 continue;
116 // This band intersects with aRect.
117 return true;
120 return false;
123 void nsRegion::Inflate(const nsMargin& aMargin) {
124 nsRegion newRegion;
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) {
138 return;
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
149 // will share y2
151 size_t idx = 0;
153 while (idx < mBands.Length()) {
154 size_t oldIdx = idx;
155 mBands[idx].mStrips.begin()->right =
156 mBands[idx].mStrips.LastElement().right;
157 mBands[idx].mStrips.TruncateLength(1);
158 idx++;
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);
171 AssertState();
173 // mBands.size() is now equal to our rect count.
174 if (mBands.Length() > aMaxRects) {
175 *this = GetBounds();
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();
197 continue;
200 int32_t currentX = strip.left;
201 while (currentStripBottom != aBottomBand.mStrips.Length() &&
202 aBottomBand.mStrips[currentStripBottom].left < strip.right) {
203 if (currentX >= strip.right) {
204 break;
206 if (currentX < aBottomBand.mStrips[currentStripBottom].left) {
207 // Add the part that's not intersecting.
208 totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) *
209 bottomHeight;
212 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();
230 continue;
233 int32_t currentX = strip.left;
234 while (currentStripTop != aTopBand.mStrips.Length() &&
235 aTopBand.mStrips[currentStripTop].left < strip.right) {
236 if (currentX >= strip.right) {
237 break;
239 if (currentX < aTopBand.mStrips[currentStripTop].left) {
240 // Add the part that's not intersecting.
241 totalArea +=
242 (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight;
245 currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX);
246 currentStripTop++;
249 // Add remainder of this strip.
250 if (currentX < strip.right) {
251 totalArea += (strip.right - currentX) * topHeight;
253 if (currentStripTop) {
254 currentStripTop--;
257 return totalArea;
260 void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) {
261 if (mBands.Length() < 2) {
262 // We have only one or no row and we're done.
263 return;
266 uint32_t currentBand = 0;
267 do {
268 Band& band = mBands[currentBand];
270 uint32_t totalArea =
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);
280 } else {
281 currentBand++;
283 } while (currentBand + 1 < mBands.Length());
285 EnsureSimplified();
286 AssertState();
289 typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2,
290 int y2);
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(),
295 mBounds.YMost());
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());
302 return;
305 auto band = std::begin(mBands);
306 auto bandFinal = std::end(mBands);
307 bandFinal--;
308 for (const Strip& strip : band->mStrips) {
309 visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
310 band->bottom);
311 visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
312 band->bottom);
313 visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1,
314 band->top);
317 if (band != bandFinal) {
318 do {
319 const Band& topBand = *band;
320 band++;
322 for (const Strip& strip : band->mStrips) {
323 visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left,
324 band->bottom);
325 visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right,
326 band->bottom);
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:
338 // 0 - Empty
339 // 1 - Touched by top rect
340 // 2 - Touched by bottom rect
341 // 3 - Touched on both sides
342 int state;
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)) {
358 int topPos;
359 int bottomPos;
360 if (topEdgeIsLeft) {
361 topPos = topStrip->left;
362 } else {
363 topPos = topStrip->right;
365 if (bottomEdgeIsLeft) {
366 bottomPos = bottomStrip->left;
367 } else {
368 bottomPos = bottomStrip->right;
371 int currentX = std::min(topPos, bottomPos);
372 if (topPos < bottomPos) {
373 if (topEdgeIsLeft) {
374 state = oldState | TouchedByTop;
375 } else {
376 state = oldState ^ TouchedByTop;
377 topStrip++;
379 topEdgeIsLeft = !topEdgeIsLeft;
380 } else if (bottomPos < topPos) {
381 if (bottomEdgeIsLeft) {
382 state = oldState | TouchedByBottom;
383 } else {
384 state = oldState ^ TouchedByBottom;
385 bottomStrip++;
387 bottomEdgeIsLeft = !bottomEdgeIsLeft;
388 } else {
389 // bottomPos == topPos
390 state = TouchedByNothing;
391 if (bottomEdgeIsLeft) {
392 state = TouchedByBottom;
393 } else {
394 bottomStrip++;
396 if (topEdgeIsLeft) {
397 state |= TouchedByTop;
398 } else {
399 topStrip++;
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);
412 } else {
413 visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y);
414 lastX = currentX;
416 } else if (oldState == TouchedByBottom) {
417 if (state == TouchedByNothing) {
418 visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y);
419 } else {
420 visit(closure, VisitSide::TOP, lastX, y, currentX, y);
421 lastX = currentX;
423 } else {
424 lastX = currentX;
426 oldState = state;
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);
433 topStrip++;
435 while (topStrip != std::end(topBand.mStrips)) {
436 visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y,
437 topStrip->right + 1, y);
438 topStrip++;
440 } else if (bottomStrip != std::end(bottomBand.mStrips)) {
441 if (!bottomEdgeIsLeft) {
442 visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y);
443 bottomStrip++;
445 while (bottomStrip != std::end(bottomBand.mStrips)) {
446 visit(closure, VisitSide::TOP, bottomStrip->left - 1, y,
447 bottomStrip->right + 1, y);
448 bottomStrip++;
451 } else {
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;
475 SetEmpty();
478 uint64_t nsRegion::Area() const {
479 if (mBands.IsEmpty()) {
480 return mBounds.Area();
483 uint64_t area = 0;
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;
491 return area;
494 nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) {
495 if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) &&
496 mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) {
497 return *this;
500 nsRegion newRegion;
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);
508 return *this;
511 nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) {
512 nsRegion newRegion;
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);
520 return *this;
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(),
531 aRect.Height());
532 rect = aTransform.TransformAndClipBounds(
533 rect, mozilla::gfx::RectDouble::MaxIntRect());
534 rect.RoundOut();
536 mozilla::gfx::IntRect intRect;
537 if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) {
538 return mozilla::gfx::IntRect();
541 return intRect;
544 nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) {
545 nsRegion newRegion;
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);
553 return *this;
556 nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP,
557 int32_t aToAPP) const {
558 if (aFromAPP == aToAPP) {
559 return *this;
561 nsRegion newRegion;
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));
568 return newRegion;
571 nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP,
572 int32_t aToAPP) const {
573 if (aFromAPP == aToAPP) {
574 return *this;
577 nsRegion newRegion;
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));
584 return newRegion;
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();
593 if (aOutsidePixels)
594 deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel);
595 else
596 deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel);
597 intRegion.OrWith(deviceRect);
600 return intRegion;
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 {
613 nsIntRegion result;
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);
619 return result;
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();
628 intRegion.OrWith(
629 rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel));
631 return intRegion;
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,
651 aAppUnitsPerPixel);
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);
694 return intRegion;
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,
701 // 0 otherwise
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]);
733 // }
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
739 // else A[j-1][i-1])
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)
750 // }
751 // }
753 // return maxRect;
754 // }
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) {
761 // var minIdx = 0;
762 // var min = 0;
763 // var maxIndices = (0,0);
764 // var max = 0;
765 // for i in range(n) {
766 // let cand = A[i] - min;
767 // if (cand > max) {
768 // max := cand;
769 // maxIndices := (minIdx, i)
770 // }
771 // if (min > A[i]) {
772 // min := A[i];
773 // minIdx := i;
774 // }
775 // }
776 // return (minIdx, maxIdx, max);
777 // }
779 namespace {
780 // This class represents a partitioning of an axis delineated by coordinates.
781 // It internally maintains a sorted array of coordinates.
782 class AxisPartition {
783 public:
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(); }
811 private:
812 nsTArray<nscoord> mStops;
815 const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll;
817 struct SizePair {
818 int64_t mSizeContainingRect;
819 int64_t mSize;
821 SizePair() : mSizeContainingRect(0), mSize(0) {}
823 static SizePair VeryLargeNegative() {
824 SizePair result;
825 result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
826 return result;
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;
840 return result;
842 SizePair operator-(const SizePair& aOther) const {
843 SizePair result = *this;
844 result.mSizeContainingRect -= aOther.mSizeContainingRect;
845 result.mSize -= aOther.mSize;
846 return result;
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,
853 int32_t* maxIdx) {
854 // The min/max indicies of the largest subarray found so far
855 SizePair min, max;
856 int32_t currentMinIdx = 0;
858 *minIdx = 0;
859 *maxIdx = 0;
861 // Because we're given the array in prefix sum form, we know the first
862 // element is 0
863 for (int32_t i = 1; i < n; i++) {
864 SizePair cand = A[i] - min;
865 if (cand > max) {
866 max = cand;
867 *minIdx = currentMinIdx;
868 *maxIdx = i;
870 if (min > A[i]) {
871 min = A[i];
872 currentMinIdx = i;
876 return max;
878 } // namespace
880 nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const {
881 nsRect bestRect;
883 if (GetNumRects() <= 1) {
884 bestRect = GetBounds();
885 return bestRect;
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];
944 if (!area.mSize) {
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
954 areas.SetLength(0);
956 SizePair bestArea;
957 struct {
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;
963 B.SetLength(n);
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;
974 bestArea = area;
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());
985 return bestRect;
988 std::ostream& operator<<(std::ostream& stream, const nsRegion& m) {
989 stream << "[";
991 bool first = true;
992 for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) {
993 if (!first) {
994 stream << "; ";
995 } else {
996 first = false;
998 const nsRect& rect = iter.Get();
999 stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << ","
1000 << rect.YMost();
1003 stream << "]";
1004 return stream;
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";
1013 iter.Next();
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());