Bug 613449. Extend GetLargestRectangle to support considering only rectangles that contain a given rectangle. r=robarnold

This commit is contained in:
Robert O'Callahan 2011-01-04 16:56:57 +13:00
parent eb517ba4b4
commit e7c81607af
3 changed files with 117 additions and 31 deletions

View File

@ -1339,12 +1339,22 @@ nsIntRegion nsRegion::ToOutsidePixels (nscoord aAppUnitsPerPixel) const
return result;
}
// This algorithm works in three phases:
// 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 to the area of the subrectangle it corresponds to. Cells
// that are not contained by any rectangle have the value 0.
// 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.
@ -1370,7 +1380,7 @@ nsIntRegion nsRegion::ToOutsidePixels (nscoord aAppUnitsPerPixel) const
// 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 number else A[j-1][i-1])
// 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
@ -1452,13 +1462,52 @@ namespace {
const PRInt64 kVeryLargeNegativeNumber = 0xffff000000000000ll;
struct SizePair {
PRInt64 mSizeContainingRect;
PRInt64 mSize;
SizePair() : mSizeContainingRect(0), mSize(0) {}
static SizePair VeryLargeNegative() {
SizePair result;
result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber;
return result;
}
SizePair& operator=(const SizePair& aOther) {
mSizeContainingRect = aOther.mSizeContainingRect;
mSize = aOther.mSize;
return *this;
}
PRBool operator<(const SizePair& aOther) const {
if (mSizeContainingRect < aOther.mSizeContainingRect)
return PR_TRUE;
if (mSizeContainingRect > aOther.mSizeContainingRect)
return PR_FALSE;
return mSize < aOther.mSize;
}
PRBool 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.
PRInt64 MaxSum1D(const nsTArray<PRInt64> &A, PRInt32 n,
SizePair MaxSum1D(const nsTArray<SizePair> &A, PRInt32 n,
PRInt32 *minIdx, PRInt32 *maxIdx) {
// The min/max indicies of the largest subarray found so far
PRInt64 min = 0,
max = 0;
SizePair min, max;
PRInt32 currentMinIdx = 0;
*minIdx = 0;
@ -1467,7 +1516,7 @@ namespace {
// Because we're given the array in prefix sum form, we know the first
// element is 0
for(PRInt32 i = 1; i < n; i++) {
PRInt64 cand = A[i] - min;
SizePair cand = A[i] - min;
if (cand > max) {
max = cand;
*minIdx = currentMinIdx;
@ -1483,7 +1532,7 @@ namespace {
}
}
nsRect nsRegion::GetLargestRectangle () const {
nsRect nsRegion::GetLargestRectangle (const nsRect& aContainingRect) const {
nsRect bestRect;
if (mRectCount <= 1) {
@ -1502,6 +1551,12 @@ nsRect nsRegion::GetLargestRectangle () const {
yaxis.InsertCoord(currentRect->y);
yaxis.InsertCoord(currentRect->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
@ -1509,9 +1564,8 @@ nsRect nsRegion::GetLargestRectangle () const {
PRInt32 matrixHeight = yaxis.GetNumStops() - 1;
PRInt32 matrixWidth = xaxis.GetNumStops() - 1;
PRInt32 matrixSize = matrixHeight * matrixWidth;
nsTArray<PRInt64> areas(matrixSize);
nsTArray<SizePair> areas(matrixSize);
areas.SetLength(matrixSize);
memset(areas.Elements(), 0, matrixSize * sizeof(PRInt64));
iter.Reset();
while ((currentRect = iter.Next())) {
@ -1524,7 +1578,11 @@ nsRect nsRegion::GetLargestRectangle () const {
nscoord height = yaxis.StopSize(y);
for (PRInt32 x = xstart; x < xend; x++) {
nscoord width = xaxis.StopSize(x);
areas[y*matrixWidth+x] = width*PRInt64(height);
PRInt64 size = width*PRInt64(height);
if (currentRect->Intersects(aContainingRect)) {
areas[y*matrixWidth+x].mSizeContainingRect = size;
}
areas[y*matrixWidth+x].mSize = size;
}
}
}
@ -1534,19 +1592,15 @@ nsRect nsRegion::GetLargestRectangle () const {
// First get the prefix sum array
PRInt32 m = matrixHeight + 1;
PRInt32 n = matrixWidth + 1;
nsTArray<PRInt64> pareas(m*n);
nsTArray<SizePair> pareas(m*n);
pareas.SetLength(m*n);
// Zero out the first row
for (PRInt32 x = 0; x < n; x++)
pareas[x] = 0;
for (PRInt32 y = 1; y < m; y++) {
// Zero out the left column
pareas[y*n] = 0;
for (PRInt32 x = 1; x < n; x++) {
PRInt64 area = areas[(y-1)*matrixWidth+x-1];
if (!area)
area = kVeryLargeNegativeNumber;
area += pareas[ y*n+x-1]
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;
@ -1556,18 +1610,19 @@ nsRect nsRegion::GetLargestRectangle () const {
// No longer need the grid
areas.SetLength(0);
PRInt64 bestArea = 0;
SizePair bestArea;
struct {
PRInt32 left, top, right, bottom;
} bestRectIndices = { 0, 0, 0, 0 };
for (PRInt32 m1 = 0; m1 < m; m1++) {
for (PRInt32 m2 = m1+1; m2 < m; m2++) {
nsTArray<PRInt64> B;
nsTArray<SizePair> B;
B.SetLength(n);
for (PRInt32 i = 0; i < n; i++)
for (PRInt32 i = 0; i < n; i++) {
B[i] = pareas[m2*n+i] - pareas[m1*n+i];
}
PRInt32 minIdx, maxIdx;
PRInt64 area = MaxSum1D(B, n, &minIdx, &maxIdx);
SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx);
if (area > bestArea) {
bestRectIndices.left = minIdx;
bestRectIndices.top = m1;

View File

@ -184,7 +184,13 @@ public:
nsRegion ConvertAppUnitsRoundOut (PRInt32 aFromAPP, PRInt32 aToAPP) const;
nsRegion ConvertAppUnitsRoundIn (PRInt32 aFromAPP, PRInt32 aToAPP) const;
nsIntRegion ToOutsidePixels (nscoord aAppUnitsPerPixel) const;
nsRect GetLargestRectangle () const;
/**
* Gets the largest rectangle contained in the region.
* @param aContainingRect if non-empty, we choose a rectangle that
* maximizes the area intersecting with aContainingRect (and break ties by
* then choosing the largest rectangle overall)
*/
nsRect GetLargestRectangle (const nsRect& aContainingRect = nsRect()) const;
/**
* Make sure the region has at most aMaxRects by adding area to it
@ -421,7 +427,10 @@ public:
PRUint32 GetNumRects () const { return mImpl.GetNumRects (); }
nsIntRect GetBounds () const { return FromRect (mImpl.GetBounds ()); }
nsRegion ToAppUnits (nscoord aAppUnitsPerPixel) const;
nsIntRect GetLargestRectangle () const { return FromRect (mImpl.GetLargestRectangle()); }
nsIntRect GetLargestRectangle (const nsIntRect& aContainingRect = nsIntRect()) const
{
return FromRect (mImpl.GetLargestRectangle( ToRect(aContainingRect) ));
}
/**
* Make sure the region has at most aMaxRects by adding area to it

View File

@ -131,6 +131,24 @@ class TestLargestRegion {
}
return success;
}
static PRBool TestContainsSpecifiedRect() {
nsRegion r(nsRect(0, 0, 100, 100));
r.Or(r, nsRect(0, 300, 50, 50));
if (r.GetLargestRectangle(nsRect(0, 300, 10, 10)) != nsRect(0, 300, 50, 50)) {
fail("Chose wrong rectangle");
return PR_FALSE;
}
return PR_TRUE;
}
static PRBool TestContainsSpecifiedOverflowingRect() {
nsRegion r(nsRect(0, 0, 100, 100));
r.Or(r, nsRect(0, 300, 50, 50));
if (r.GetLargestRectangle(nsRect(0, 290, 10, 20)) != nsRect(0, 300, 50, 50)) {
fail("Chose wrong rectangle");
return PR_FALSE;
}
return PR_TRUE;
}
public:
static PRBool Test() {
if (!TestSingleRect(nsRect(0, 52, 720, 480)) ||
@ -143,6 +161,10 @@ public:
return PR_FALSE;
if (!TwoRectTest())
return PR_FALSE;
if (!TestContainsSpecifiedRect())
return PR_FALSE;
if (!TestContainsSpecifiedOverflowingRect())
return PR_FALSE;
passed("TestLargestRegion");
return PR_TRUE;
}