From e7c81607af4c66ff8ee047e97c1affdcc6f0fcb9 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 4 Jan 2011 16:56:57 +1300 Subject: [PATCH] Bug 613449. Extend GetLargestRectangle to support considering only rectangles that contain a given rectangle. r=robarnold --- gfx/src/nsRegion.cpp | 113 +++++++++++++++++++++++++++++---------- gfx/src/nsRegion.h | 13 ++++- gfx/tests/TestRegion.cpp | 22 ++++++++ 3 files changed, 117 insertions(+), 31 deletions(-) diff --git a/gfx/src/nsRegion.cpp b/gfx/src/nsRegion.cpp index 4a2bc06d594..d791736a2cd 100644 --- a/gfx/src/nsRegion.cpp +++ b/gfx/src/nsRegion.cpp @@ -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 &A, PRInt32 n, - PRInt32 *minIdx, PRInt32 *maxIdx) { + SizePair MaxSum1D(const nsTArray &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 areas(matrixSize); + nsTArray 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,21 +1592,17 @@ nsRect nsRegion::GetLargestRectangle () const { // First get the prefix sum array PRInt32 m = matrixHeight + 1; PRInt32 n = matrixWidth + 1; - nsTArray pareas(m*n); + nsTArray 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] - + pareas[(y-1)*n+x ] - - pareas[(y-1)*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 B; + nsTArray 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; diff --git a/gfx/src/nsRegion.h b/gfx/src/nsRegion.h index 83ad6231b6a..6e70fb39508 100644 --- a/gfx/src/nsRegion.h +++ b/gfx/src/nsRegion.h @@ -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 diff --git a/gfx/tests/TestRegion.cpp b/gfx/tests/TestRegion.cpp index 14fadc7d009..e9724aa71b1 100644 --- a/gfx/tests/TestRegion.cpp +++ b/gfx/tests/TestRegion.cpp @@ -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; }