nsRegion.cpp
/* -*- 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());
}