diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/src/nsRegion.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/src/nsRegion.cpp')
-rw-r--r-- | gfx/src/nsRegion.cpp | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp new file mode 100644 index 0000000000..aa50d44fc4 --- /dev/null +++ b/gfx/src/nsRegion.cpp @@ -0,0 +1,1024 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRegion.h" +#include "nsTArray.h" +#include "gfxUtils.h" +#include "gfx2DGlue.h" +#include "mozilla/ToString.h" + +void nsRegion::AssertStateInternal() const { + bool failed = false; + // Verify consistent state inside the region. + int32_t lastY = INT32_MIN; + int32_t lowestX = INT32_MAX; + int32_t highestX = INT32_MIN; + for (auto iter = mBands.begin(); iter != mBands.end(); iter++) { + const Band& band = *iter; + if (band.bottom <= band.top) { + failed = true; + break; + } + if (band.top < lastY) { + failed = true; + break; + } + lastY = band.bottom; + + lowestX = std::min(lowestX, band.mStrips.begin()->left); + highestX = std::max(highestX, band.mStrips.LastElement().right); + + int32_t lastX = INT32_MIN; + if (iter != mBands.begin()) { + auto prev = iter; + prev--; + + if (prev->bottom == iter->top) { + if (band.EqualStrips(*prev)) { + failed = true; + break; + } + } + } + for (const Strip& strip : band.mStrips) { + if (strip.right <= strip.left) { + failed = true; + break; + } + if (strip.left <= lastX) { + failed = true; + break; + } + lastX = strip.right; + } + if (failed) { + break; + } + } + + if (!(mBounds.IsEqualEdges(CalculateBounds()))) { + failed = true; + } + + if (failed) { +#ifdef DEBUG_REGIONS + if (mCurrentOpGenerator) { + mCurrentOpGenerator->OutputOp(); + } +#endif + MOZ_ASSERT(false); + } +} + +bool nsRegion::Contains(const nsRegion& aRgn) const { + // XXX this could be made faster by iterating over + // both regions at the same time some how + for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) { + if (!Contains(iter.Get())) { + return false; + } + } + return true; +} + +bool nsRegion::Intersects(const nsRectAbsolute& aRect) const { + if (mBands.IsEmpty()) { + return mBounds.Intersects(aRect); + } + + if (!mBounds.Intersects(aRect)) { + return false; + } + + Strip rectStrip(aRect.X(), aRect.XMost()); + + auto iter = mBands.begin(); + while (iter != mBands.end()) { + if (iter->top >= aRect.YMost()) { + return false; + } + + if (iter->bottom <= aRect.Y()) { + // This band is entirely before aRect, move on. + iter++; + continue; + } + + if (!iter->Intersects(rectStrip)) { + // This band does not intersect aRect horizontally. Move on. + iter++; + continue; + } + + // This band intersects with aRect. + return true; + } + + return false; +} + +void nsRegion::Inflate(const nsMargin& aMargin) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.Inflate(aMargin); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); +} + +void nsRegion::SimplifyOutward(uint32_t aMaxRects) { + MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) { + return; + } + + // Try combining rects in horizontal bands into a single rect + // The goal here is to try to keep groups of rectangles that are vertically + // discontiguous as separate rectangles in the final region. This is + // simple and fast to implement and page contents tend to vary more + // vertically than horizontally (which is why our rectangles are stored + // sorted by y-coordinate, too). + // + // Note: if boxes share y1 because of the canonical representation they + // will share y2 + + size_t idx = 0; + + while (idx < mBands.Length()) { + size_t oldIdx = idx; + mBands[idx].mStrips.begin()->right = + mBands[idx].mStrips.LastElement().right; + mBands[idx].mStrips.TruncateLength(1); + idx++; + + // Merge any bands with the same bounds. + while (idx < mBands.Length() && + mBands[idx].mStrips.begin()->left == + mBands[oldIdx].mStrips.begin()->left && + mBands[idx].mStrips.LastElement().right == + mBands[oldIdx].mStrips.begin()->right) { + mBands[oldIdx].bottom = mBands[idx].bottom; + mBands.RemoveElementAt(idx); + } + } + + AssertState(); + + // mBands.size() is now equal to our rect count. + if (mBands.Length() > aMaxRects) { + *this = GetBounds(); + } +} + +// compute the covered area difference between two rows. +// by iterating over both rows simultaneously and adding up +// the additional increase in area caused by extending each +// of the rectangles to the combined height of both rows +uint32_t nsRegion::ComputeMergedAreaIncrease(const Band& aTopBand, + const Band& aBottomBand) { + uint32_t totalArea = 0; + + uint32_t topHeight = aBottomBand.top - aTopBand.top; + uint32_t bottomHeight = aBottomBand.bottom - aTopBand.bottom; + uint32_t currentStripBottom = 0; + + // This could be done with slightly better worse case performance by merging + // these two for-loops, but this makes the code a lot easier to understand. + for (auto& strip : aTopBand.mStrips) { + if (currentStripBottom == aBottomBand.mStrips.Length() || + strip.right < aBottomBand.mStrips[currentStripBottom].left) { + totalArea += bottomHeight * strip.Size(); + continue; + } + + int32_t currentX = strip.left; + while (currentStripBottom != aBottomBand.mStrips.Length() && + aBottomBand.mStrips[currentStripBottom].left < strip.right) { + if (currentX >= strip.right) { + break; + } + if (currentX < aBottomBand.mStrips[currentStripBottom].left) { + // Add the part that's not intersecting. + totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) * + bottomHeight; + } + + currentX = + std::max(aBottomBand.mStrips[currentStripBottom].right, currentX); + currentStripBottom++; + } + + // Add remainder of this strip. + if (currentX < strip.right) { + totalArea += (strip.right - currentX) * bottomHeight; + } + if (currentStripBottom) { + currentStripBottom--; + } + } + uint32_t currentStripTop = 0; + for (auto& strip : aBottomBand.mStrips) { + if (currentStripTop == aTopBand.mStrips.Length() || + strip.right < aTopBand.mStrips[currentStripTop].left) { + totalArea += topHeight * strip.Size(); + continue; + } + + int32_t currentX = strip.left; + while (currentStripTop != aTopBand.mStrips.Length() && + aTopBand.mStrips[currentStripTop].left < strip.right) { + if (currentX >= strip.right) { + break; + } + if (currentX < aTopBand.mStrips[currentStripTop].left) { + // Add the part that's not intersecting. + totalArea += + (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight; + } + + currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX); + currentStripTop++; + } + + // Add remainder of this strip. + if (currentX < strip.right) { + totalArea += (strip.right - currentX) * topHeight; + } + if (currentStripTop) { + currentStripTop--; + } + } + return totalArea; +} + +void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) { + if (mBands.Length() < 2) { + // We have only one or no row and we're done. + return; + } + + uint32_t currentBand = 0; + do { + Band& band = mBands[currentBand]; + + uint32_t totalArea = + ComputeMergedAreaIncrease(band, mBands[currentBand + 1]); + + if (totalArea <= aThreshold) { + for (Strip& strip : mBands[currentBand + 1].mStrips) { + // This could use an optimized function to merge two bands. + band.InsertStrip(strip); + } + band.bottom = mBands[currentBand + 1].bottom; + mBands.RemoveElementAt(currentBand + 1); + } else { + currentBand++; + } + } while (currentBand + 1 < mBands.Length()); + + EnsureSimplified(); + AssertState(); +} + +typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2, + int y2); + +void nsRegion::VisitEdges(visit_fn visit, void* closure) const { + if (mBands.IsEmpty()) { + visit(closure, VisitSide::LEFT, mBounds.X(), mBounds.Y(), mBounds.X(), + mBounds.YMost()); + visit(closure, VisitSide::RIGHT, mBounds.XMost(), mBounds.Y(), + mBounds.XMost(), mBounds.YMost()); + visit(closure, VisitSide::TOP, mBounds.X() - 1, mBounds.Y(), + mBounds.XMost() + 1, mBounds.Y()); + visit(closure, VisitSide::BOTTOM, mBounds.X() - 1, mBounds.YMost(), + mBounds.XMost() + 1, mBounds.YMost()); + return; + } + + auto band = std::begin(mBands); + auto bandFinal = std::end(mBands); + bandFinal--; + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, + band->bottom); + visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, + band->bottom); + visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1, + band->top); + } + + if (band != bandFinal) { + do { + const Band& topBand = *band; + band++; + + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, + band->bottom); + visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, + band->bottom); + } + + if (band->top == topBand.bottom) { + // Two bands touching each other vertically. + const Band& bottomBand = *band; + auto topStrip = std::begin(topBand.mStrips); + auto bottomStrip = std::begin(bottomBand.mStrips); + + int y = topBand.bottom; + + // State from this point on along the vertical edge: + // 0 - Empty + // 1 - Touched by top rect + // 2 - Touched by bottom rect + // 3 - Touched on both sides + int state; + const int TouchedByNothing = 0; + const int TouchedByTop = 1; + const int TouchedByBottom = 2; + // We always start with nothing. + int oldState = TouchedByNothing; + // Last state change, adjusted by -1 if the last state change was + // a change away from 0. + int lastX = std::min(topStrip->left, bottomStrip->left) - 1; + + // Current edge being considered for top and bottom, + // 0 - left, 1 - right. + bool topEdgeIsLeft = true; + bool bottomEdgeIsLeft = true; + while (topStrip != std::end(topBand.mStrips) && + bottomStrip != std::end(bottomBand.mStrips)) { + int topPos; + int bottomPos; + if (topEdgeIsLeft) { + topPos = topStrip->left; + } else { + topPos = topStrip->right; + } + if (bottomEdgeIsLeft) { + bottomPos = bottomStrip->left; + } else { + bottomPos = bottomStrip->right; + } + + int currentX = std::min(topPos, bottomPos); + if (topPos < bottomPos) { + if (topEdgeIsLeft) { + state = oldState | TouchedByTop; + } else { + state = oldState ^ TouchedByTop; + topStrip++; + } + topEdgeIsLeft = !topEdgeIsLeft; + } else if (bottomPos < topPos) { + if (bottomEdgeIsLeft) { + state = oldState | TouchedByBottom; + } else { + state = oldState ^ TouchedByBottom; + bottomStrip++; + } + bottomEdgeIsLeft = !bottomEdgeIsLeft; + } else { + // bottomPos == topPos + state = TouchedByNothing; + if (bottomEdgeIsLeft) { + state = TouchedByBottom; + } else { + bottomStrip++; + } + if (topEdgeIsLeft) { + state |= TouchedByTop; + } else { + topStrip++; + } + topEdgeIsLeft = !topEdgeIsLeft; + bottomEdgeIsLeft = !bottomEdgeIsLeft; + } + + MOZ_ASSERT(state != oldState); + if (oldState == TouchedByNothing) { + // We had nothing before, make sure the left edge will be padded. + lastX = currentX - 1; + } else if (oldState == TouchedByTop) { + if (state == TouchedByNothing) { + visit(closure, VisitSide::BOTTOM, lastX, y, currentX + 1, y); + } else { + visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y); + lastX = currentX; + } + } else if (oldState == TouchedByBottom) { + if (state == TouchedByNothing) { + visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y); + } else { + visit(closure, VisitSide::TOP, lastX, y, currentX, y); + lastX = currentX; + } + } else { + lastX = currentX; + } + oldState = state; + } + + MOZ_ASSERT(!state || (topEdgeIsLeft || bottomEdgeIsLeft)); + if (topStrip != std::end(topBand.mStrips)) { + if (!topEdgeIsLeft) { + visit(closure, VisitSide::BOTTOM, lastX, y, topStrip->right + 1, y); + topStrip++; + } + while (topStrip != std::end(topBand.mStrips)) { + visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y, + topStrip->right + 1, y); + topStrip++; + } + } else if (bottomStrip != std::end(bottomBand.mStrips)) { + if (!bottomEdgeIsLeft) { + visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y); + bottomStrip++; + } + while (bottomStrip != std::end(bottomBand.mStrips)) { + visit(closure, VisitSide::TOP, bottomStrip->left - 1, y, + bottomStrip->right + 1, y); + bottomStrip++; + } + } + } else { + for (const Strip& strip : topBand.mStrips) { + visit(closure, VisitSide::BOTTOM, strip.left - 1, topBand.bottom, + strip.right + 1, topBand.bottom); + } + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::TOP, strip.left - 1, band->top, + strip.right + 1, band->top); + } + } + } while (band != bandFinal); + } + + for (const Strip& strip : band->mStrips) { + visit(closure, VisitSide::BOTTOM, strip.left - 1, band->bottom, + strip.right + 1, band->bottom); + } +} + +void nsRegion::SimplifyInward(uint32_t aMaxRects) { + NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count"); + + if (GetNumRects() <= aMaxRects) return; + + SetEmpty(); +} + +uint64_t nsRegion::Area() const { + if (mBands.IsEmpty()) { + return mBounds.Area(); + } + + uint64_t area = 0; + for (const Band& band : mBands) { + uint32_t height = band.bottom - band.top; + for (const Strip& strip : band.mStrips) { + area += (strip.right - strip.left) * height; + } + } + + return area; +} + +nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) { + if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) && + mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) { + return *this; + } + + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.ScaleRoundOut(aXScale, aYScale); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); + return *this; +} + +nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRectAbsolute rect = iter.GetAbsolute(); + rect.ScaleInverseRoundOut(aXScale, aYScale); + newRegion.AddRect(rect); + } + + *this = std::move(newRegion); + return *this; +} + +static mozilla::gfx::IntRect TransformRect( + const mozilla::gfx::IntRect& aRect, + const mozilla::gfx::Matrix4x4& aTransform) { + if (aRect.IsEmpty()) { + return mozilla::gfx::IntRect(); + } + + mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(), + aRect.Height()); + rect = aTransform.TransformAndClipBounds( + rect, mozilla::gfx::RectDouble::MaxIntRect()); + rect.RoundOut(); + + mozilla::gfx::IntRect intRect; + if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) { + return mozilla::gfx::IntRect(); + } + + return intRect; +} + +nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) { + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = nsIntRegion::ToRect( + TransformRect(nsIntRegion::FromRect(iter.Get()), aTransform)); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + *this = std::move(newRegion); + return *this; +} + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + return newRegion; +} + +nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, + int32_t aToAPP) const { + if (aFromAPP == aToAPP) { + return *this; + } + + nsRegion newRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP); + newRegion.AddRect(nsRectAbsolute::FromRect(rect)); + } + + return newRegion; +} + +nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel, + bool aOutsidePixels) const { + nsIntRegion intRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + mozilla::gfx::IntRect deviceRect; + nsRect rect = iter.Get(); + if (aOutsidePixels) + deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel); + else + deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel); + intRegion.OrWith(deviceRect); + } + + return intRegion; +} + +nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const { + return ToPixels(aAppUnitsPerPixel, true); +} + +nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const { + return ToPixels(aAppUnitsPerPixel, false); +} + +nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + nsIntRegion result; + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + mozilla::gfx::IntRect deviceRect = + iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel); + result.Or(result, deviceRect); + } + return result; +} + +nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + // make a copy of the region so that we can mutate it inplace + nsIntRegion intRegion; + for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + intRegion.OrWith( + rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel)); + } + return intRegion; +} + +nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY, + nscoord aAppUnitsPerPixel) const { + /* When scaling a rect, walk forward through the rect list up until the y + * value is greater than the current rect's YMost() value. + * + * For each rect found, check if the rects have a touching edge (in unscaled + * coordinates), and if one edge is entirely contained within the other. + * + * If it is, then the contained edge can be moved (in scaled pixels) to ensure + * that no gap exists. + * + * Since this could be potentially expensive - O(n^2), we only attempt this + * algorithm for the first rect. + */ + + if (mBands.IsEmpty()) { + nsIntRect rect = mBounds.ToNSRect().ScaleToInsidePixels(aScaleX, aScaleY, + aAppUnitsPerPixel); + return nsIntRegion(rect); + } + + nsIntRegion intRegion; + RectIterator iter = RectIterator(*this); + + nsRect first = iter.Get(); + + mozilla::gfx::IntRect firstDeviceRect = + first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + + for (iter.Next(); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + mozilla::gfx::IntRect deviceRect = + rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); + + if (rect.Y() <= first.YMost()) { + if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) { + // rect is touching on the left edge of the first rect and contained + // within the length of its left edge + deviceRect.SetRightEdge(firstDeviceRect.X()); + } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) { + // rect is touching on the right edge of the first rect and contained + // within the length of its right edge + deviceRect.SetLeftEdge(firstDeviceRect.XMost()); + } else if (rect.Y() == first.YMost()) { + // The bottom of the first rect is on the same line as the top of rect, + // but they aren't necessarily contained. + if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) { + // The top of rect contains the bottom of the first rect + firstDeviceRect.SetBottomEdge(deviceRect.Y()); + } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) { + // The bottom of the first contains the top of rect + deviceRect.SetTopEdge(firstDeviceRect.YMost()); + } + } + } + + intRegion.OrWith(deviceRect); + } + + intRegion.OrWith(firstDeviceRect); + return intRegion; +} + +// A cell's "value" is a pair consisting of +// a) the area of the subrectangle it corresponds to, if it's in +// aContainingRect and in the region, 0 otherwise +// b) the area of the subrectangle it corresponds to, if it's in the region, +// 0 otherwise +// Addition, subtraction and identity are defined on these values in the +// obvious way. Partial order is lexicographic. +// A "large negative value" is defined with large negative numbers for both +// fields of the pair. This negative value has the property that adding any +// number of non-negative values to it always results in a negative value. +// +// The GetLargestRectangle algorithm works in three phases: +// 1) Convert the region into a grid by adding vertical/horizontal lines for +// each edge of each rectangle in the region. +// 2) For each rectangle in the region, for each cell it contains, set that +// cells's value as described above. +// 3) Calculate the submatrix with the largest sum such that none of its cells +// contain any 0s (empty regions). The rectangle represented by the +// submatrix is the largest rectangle in the region. +// +// Let k be the number of rectangles in the region. +// Let m be the height of the grid generated in step 1. +// Let n be the width of the grid generated in step 1. +// +// Step 1 is O(k) in time and O(m+n) in space for the sparse grid. +// Step 2 is O(mn) in time and O(mn) in additional space for the full grid. +// Step 3 is O(m^2 n) in time and O(mn) in additional space +// +// The implementation of steps 1 and 2 are rather straightforward. However our +// implementation of step 3 uses dynamic programming to achieve its efficiency. +// +// Psuedo code for step 3 is as follows where G is the grid from step 1 and A +// is the array from step 2: +// Phase3 = function (G, A, m, n) { +// let (t,b,l,r,_) = MaxSum2D(A,m,n) +// return rect(G[t],G[l],G[r],G[b]); +// } +// MaxSum2D = function (A, m, n) { +// S = array(m+1,n+1) +// S[0][i] = 0 for i in [0,n] +// S[j][0] = 0 for j in [0,m] +// S[j][i] = (if A[j-1][i-1] = 0 then some large negative value +// else A[j-1][i-1]) +// + S[j-1][n] + S[j][i-1] - S[j-1][i-1] +// +// // top, bottom, left, right, area +// var maxRect = (-1, -1, -1, -1, 0); +// +// for all (m',m'') in [0, m]^2 { +// let B = { S[m'][i] - S[m''][i] | 0 <= i <= n } +// let ((l,r),area) = MaxSum1D(B,n+1) +// if (area > maxRect.area) { +// maxRect := (m', m'', l, r, area) +// } +// } +// +// return maxRect; +// } +// +// Originally taken from Improved algorithms for the k-maximum subarray problem +// for small k - SE Bae, T Takaoka but modified to show the explicit tracking +// of indices and we already have the prefix sums from our one call site so +// there's no need to construct them. +// MaxSum1D = function (A,n) { +// var minIdx = 0; +// var min = 0; +// var maxIndices = (0,0); +// var max = 0; +// for i in range(n) { +// let cand = A[i] - min; +// if (cand > max) { +// max := cand; +// maxIndices := (minIdx, i) +// } +// if (min > A[i]) { +// min := A[i]; +// minIdx := i; +// } +// } +// return (minIdx, maxIdx, max); +// } + +namespace { +// This class represents a partitioning of an axis delineated by coordinates. +// It internally maintains a sorted array of coordinates. +class AxisPartition { + public: + // Adds a new partition at the given coordinate to this partitioning. If + // the coordinate is already present in the partitioning, this does nothing. + void InsertCoord(nscoord c) { + uint32_t i = mStops.IndexOfFirstElementGt(c); + if (i == 0 || mStops[i - 1] != c) { + mStops.InsertElementAt(i, c); + } + } + + // Returns the array index of the given partition point. The partition + // point must already be present in the partitioning. + int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); } + + // Returns the partition at the given index which must be non-zero and + // less than the number of partitions in this partitioning. + nscoord StopAt(int32_t index) const { return mStops[index]; } + + // Returns the size of the gap between the partition at the given index and + // the next partition in this partitioning. If the index is the last index + // in the partitioning, the result is undefined. + nscoord StopSize(int32_t index) const { + return mStops[index + 1] - mStops[index]; + } + + // Returns the number of partitions in this partitioning. + int32_t GetNumStops() const { return mStops.Length(); } + + private: + nsTArray<nscoord> mStops; +}; + +const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll; + +struct SizePair { + int64_t mSizeContainingRect; + int64_t mSize; + + SizePair() : mSizeContainingRect(0), mSize(0) {} + + static SizePair VeryLargeNegative() { + SizePair result; + result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber; + return result; + } + bool operator<(const SizePair& aOther) const { + if (mSizeContainingRect < aOther.mSizeContainingRect) return true; + if (mSizeContainingRect > aOther.mSizeContainingRect) return false; + return mSize < aOther.mSize; + } + bool operator>(const SizePair& aOther) const { + return aOther.operator<(*this); + } + SizePair operator+(const SizePair& aOther) const { + SizePair result = *this; + result.mSizeContainingRect += aOther.mSizeContainingRect; + result.mSize += aOther.mSize; + return result; + } + SizePair operator-(const SizePair& aOther) const { + SizePair result = *this; + result.mSizeContainingRect -= aOther.mSizeContainingRect; + result.mSize -= aOther.mSize; + return result; + } +}; + +// Returns the sum and indices of the subarray with the maximum sum of the +// given array (A,n), assuming the array is already in prefix sum form. +SizePair MaxSum1D(const nsTArray<SizePair>& A, int32_t n, int32_t* minIdx, + int32_t* maxIdx) { + // The min/max indicies of the largest subarray found so far + SizePair min, max; + int32_t currentMinIdx = 0; + + *minIdx = 0; + *maxIdx = 0; + + // Because we're given the array in prefix sum form, we know the first + // element is 0 + for (int32_t i = 1; i < n; i++) { + SizePair cand = A[i] - min; + if (cand > max) { + max = cand; + *minIdx = currentMinIdx; + *maxIdx = i; + } + if (min > A[i]) { + min = A[i]; + currentMinIdx = i; + } + } + + return max; +} +} // namespace + +nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const { + nsRect bestRect; + + if (GetNumRects() <= 1) { + bestRect = GetBounds(); + return bestRect; + } + + AxisPartition xaxis, yaxis; + + // Step 1: Calculate the grid lines + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + const nsRect& rect = iter.Get(); + xaxis.InsertCoord(rect.X()); + xaxis.InsertCoord(rect.XMost()); + yaxis.InsertCoord(rect.Y()); + yaxis.InsertCoord(rect.YMost()); + } + if (!aContainingRect.IsEmpty()) { + xaxis.InsertCoord(aContainingRect.X()); + xaxis.InsertCoord(aContainingRect.XMost()); + yaxis.InsertCoord(aContainingRect.Y()); + yaxis.InsertCoord(aContainingRect.YMost()); + } + + // Step 2: Fill out the grid with the areas + // Note: due to the ordering of rectangles in the region, it is not always + // possible to combine steps 2 and 3 so we don't try to be clever. + int32_t matrixHeight = yaxis.GetNumStops() - 1; + int32_t matrixWidth = xaxis.GetNumStops() - 1; + int32_t matrixSize = matrixHeight * matrixWidth; + nsTArray<SizePair> areas(matrixSize); + areas.SetLength(matrixSize); + + for (auto iter = RectIter(); !iter.Done(); iter.Next()) { + const nsRect& rect = iter.Get(); + int32_t xstart = xaxis.IndexOf(rect.X()); + int32_t xend = xaxis.IndexOf(rect.XMost()); + int32_t y = yaxis.IndexOf(rect.Y()); + int32_t yend = yaxis.IndexOf(rect.YMost()); + + for (; y < yend; y++) { + nscoord height = yaxis.StopSize(y); + for (int32_t x = xstart; x < xend; x++) { + nscoord width = xaxis.StopSize(x); + int64_t size = width * int64_t(height); + if (rect.Intersects(aContainingRect)) { + areas[y * matrixWidth + x].mSizeContainingRect = size; + } + areas[y * matrixWidth + x].mSize = size; + } + } + } + + // Step 3: Find the maximum submatrix sum that does not contain a rectangle + { + // First get the prefix sum array + int32_t m = matrixHeight + 1; + int32_t n = matrixWidth + 1; + nsTArray<SizePair> pareas(m * n); + pareas.SetLength(m * n); + for (int32_t y = 1; y < m; y++) { + for (int32_t x = 1; x < n; x++) { + SizePair area = areas[(y - 1) * matrixWidth + x - 1]; + if (!area.mSize) { + area = SizePair::VeryLargeNegative(); + } + area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] - + pareas[(y - 1) * n + x - 1]; + pareas[y * n + x] = area; + } + } + + // No longer need the grid + areas.SetLength(0); + + SizePair bestArea; + struct { + int32_t left, top, right, bottom; + } bestRectIndices = {0, 0, 0, 0}; + for (int32_t m1 = 0; m1 < m; m1++) { + for (int32_t m2 = m1 + 1; m2 < m; m2++) { + nsTArray<SizePair> B; + B.SetLength(n); + for (int32_t i = 0; i < n; i++) { + B[i] = pareas[m2 * n + i] - pareas[m1 * n + i]; + } + int32_t minIdx, maxIdx; + SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx); + if (area > bestArea) { + bestRectIndices.left = minIdx; + bestRectIndices.top = m1; + bestRectIndices.right = maxIdx; + bestRectIndices.bottom = m2; + bestArea = area; + } + } + } + + bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left), + yaxis.StopAt(bestRectIndices.top)); + bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(), + yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y()); + } + + return bestRect; +} + +std::ostream& operator<<(std::ostream& stream, const nsRegion& m) { + stream << "["; + + bool first = true; + for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) { + if (!first) { + stream << "; "; + } else { + first = false; + } + const nsRect& rect = iter.Get(); + stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << "," + << rect.YMost(); + } + + stream << "]"; + return stream; +} + +void nsRegion::OutputToStream(std::string aObjName, + std::ostream& stream) const { + auto iter = RectIter(); + nsRect r = iter.Get(); + stream << "nsRegion " << aObjName << "(nsRect(" << r.X() << ", " << r.Y() + << ", " << r.Width() << ", " << r.Height() << "));\n"; + iter.Next(); + + for (; !iter.Done(); iter.Next()) { + nsRect r = iter.Get(); + stream << aObjName << ".OrWith(nsRect(" << r.X() << ", " << r.Y() << ", " + << r.Width() << ", " << r.Height() << "));\n"; + } +} + +nsCString nsRegion::ToString() const { + return nsCString(mozilla::ToString(*this).c_str()); +} |