Bug 555182 - Calculate the largest opaque rectangle in the opaque region to determine glass margins r=jimm,roc

This commit is contained in:
Rob Arnold 2010-05-23 23:29:04 -04:00
Родитель 4886cf3d1c
Коммит 41ac074713
5 изменённых файлов: 416 добавлений и 11 удалений

Просмотреть файл

@ -179,6 +179,7 @@ public:
PRUint32 GetNumRects () const { return mRectCount; }
const nsRect& GetBounds () const { return mBoundRect; }
nsIntRegion ToOutsidePixels (nscoord aAppUnitsPerPixel) const;
nsRect GetLargestRectangle () const;
/**
* Make sure the region has at most aMaxRects by adding area to it
@ -409,6 +410,7 @@ public:
}
PRUint32 GetNumRects () const { return mImpl.GetNumRects (); }
nsIntRect GetBounds () const { return FromRect (mImpl.GetBounds ()); }
nsIntRect GetLargestRectangle () const { return FromRect (mImpl.GetLargestRectangle()); }
/**
* Make sure the region has at most aMaxRects by adding area to it

Просмотреть файл

@ -37,6 +37,7 @@
#include "prlock.h"
#include "nsRegion.h"
#include "nsISupportsImpl.h"
#include "nsTArray.h"
/*
* The SENTINEL values below guaranties that a < or >
@ -1299,6 +1300,252 @@ nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const {
return result;
}
// This 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.
// 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 number 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) {
PRUint32 i;
if (!mStops.GreatestIndexLtEq(c, i)) {
mStops.InsertElementAt(i, c);
}
}
// Returns the array index of the given partition point. The partition
// point must already be present in the partitioning.
PRInt32 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(PRInt32 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(PRInt32 index) const {
return mStops[index+1] - mStops[index];
}
// Returns the number of partitions in this partitioning.
PRInt32 GetNumStops() const { return mStops.Length(); }
private:
nsTArray<nscoord> mStops;
};
const PRInt64 kVeryLargeNegativeNumber = 0xffff000000000000;
// 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,
PRInt32 *minIdx, PRInt32 *maxIdx) {
// The min/max indicies of the largest subarray found so far
PRInt64 min = 0,
max = 0;
PRInt32 currentMinIdx = 0;
*minIdx = 0;
*maxIdx = 0;
// 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;
if (cand > max) {
max = cand;
*minIdx = currentMinIdx;
*maxIdx = i;
}
if (min > A[i]) {
min = A[i];
currentMinIdx = i;
}
}
return max;
}
}
nsRect nsRegion::GetLargestRectangle () const {
nsRect bestRect;
if (!mRectCount)
return bestRect;
AxisPartition xaxis, yaxis;
// Step 1: Calculate the grid lines
nsRegionRectIterator iter(*this);
const nsRect *currentRect;
while ((currentRect = iter.Next())) {
xaxis.InsertCoord(currentRect->x);
xaxis.InsertCoord(currentRect->XMost());
yaxis.InsertCoord(currentRect->y);
yaxis.InsertCoord(currentRect->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.
PRInt32 matrixHeight = yaxis.GetNumStops() - 1;
PRInt32 matrixWidth = xaxis.GetNumStops() - 1;
PRInt32 matrixSize = matrixHeight * matrixWidth;
nsTArray<PRInt64> areas(matrixSize);
areas.SetLength(matrixSize);
memset(areas.Elements(), 0, matrixSize * sizeof PRInt64);
iter.Reset();
while ((currentRect = iter.Next())) {
PRInt32 xstart = xaxis.IndexOf(currentRect->x);
PRInt32 xend = xaxis.IndexOf(currentRect->XMost());
PRInt32 y = yaxis.IndexOf(currentRect->y);
PRInt32 yend = yaxis.IndexOf(currentRect->YMost());
for (; y < yend; y++) {
nscoord height = yaxis.StopSize(y);
for (PRInt32 x = xstart; x < xend; x++) {
nscoord width = xaxis.StopSize(x);
areas[y*matrixWidth+x] = width*PRInt64(height);
}
}
}
// Step 3: Find the maximum submatrix sum that does not contain a rectangle
{
// First get the prefix sum array
PRInt32 m = matrixHeight + 1;
PRInt32 n = matrixWidth + 1;
nsTArray<PRInt64> 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];
pareas[y*n+x] = area;
}
}
// No longer need the grid
areas.SetLength(0);
PRInt64 bestArea = 0;
struct {
PRInt32 left, top, right, bottom;
} bestRectIndices;
for (PRInt32 m1 = 0; m1 < m; m1++) {
for (PRInt32 m2 = m1+1; m2 < m; m2++) {
nsTArray<PRInt64> B;
B.SetLength(n);
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);
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;
}
void nsRegion::SimplifyOutward (PRUint32 aMaxRects)
{
NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count");

Просмотреть файл

@ -50,6 +50,7 @@ MOZILLA_INTERNAL_API = 1
CPP_UNIT_TESTS = \
TestColorNames.cpp \
TestRect.cpp \
TestRegion.cpp \
$(NULL)
LIBS = \

163
gfx/tests/TestRegion.cpp Normal file
Просмотреть файл

@ -0,0 +1,163 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Rob Arnold <robarnold@cs.cmu.edu>
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "TestHarness.h"
#include "nsRegion.h"
class TestLargestRegion {
static PRBool TestSingleRect(nsRect r) {
nsRegion region(r);
if (region.GetLargestRectangle() != r) {
fail("largest rect of singleton %d %d %d %d", r.x, r.y, r.width, r.height);
return PR_FALSE;
}
return PR_TRUE;
}
// Construct a rectangle, remove part of it, then check the remainder
static PRBool TestNonRectangular() {
nsRegion r(nsRect(0, 0, 30, 30));
const int nTests = 19;
struct {
nsRect rect;
PRInt64 expectedArea;
} tests[nTests] = {
// Remove a 20x10 chunk from the square
{ nsRect(0, 0, 20, 10), 600 },
{ nsRect(10, 0, 20, 10), 600 },
{ nsRect(10, 20, 20, 10), 600 },
{ nsRect(0, 20, 20, 10), 600 },
// Remove a 10x20 chunk from the square
{ nsRect(0, 0, 10, 20), 600 },
{ nsRect(20, 0, 10, 20), 600 },
{ nsRect(20, 10, 10, 20), 600 },
{ nsRect(0, 10, 10, 20), 600 },
// Remove the center 10x10
{ nsRect(10, 10, 10, 10), 300 },
// Remove the middle column
{ nsRect(10, 0, 10, 30), 300 },
// Remove the middle row
{ nsRect(0, 10, 30, 10), 300 },
// Remove the corners 10x10
{ nsRect(0, 0, 10, 10), 600 },
{ nsRect(20, 20, 10, 10), 600 },
{ nsRect(20, 0, 10, 10), 600 },
{ nsRect(0, 20, 10, 10), 600 },
// Remove the corners 20x20
{ nsRect(0, 0, 20, 20), 300 },
{ nsRect(10, 10, 20, 20), 300 },
{ nsRect(10, 0, 20, 20), 300 },
{ nsRect(0, 10, 20, 20), 300 }
};
PRBool success = PR_TRUE;
for (PRInt32 i = 0; i < nTests; i++) {
nsRegion r2;
r2.Sub(r, tests[i].rect);
if (!r2.IsComplex())
fail("nsRegion code got unexpectedly smarter!");
nsRect largest = r2.GetLargestRectangle();
if (largest.width * largest.height != tests[i].expectedArea) {
fail("Did not succesfully find largest rectangle in non-rectangular region on iteration %d", i);
success = PR_FALSE;
}
}
return success;
}
static PRBool TwoRectTest() {
nsRegion r(nsRect(0, 0, 100, 100));
const int nTests = 4;
struct {
nsRect rect1, rect2;
PRInt64 expectedArea;
} tests[nTests] = {
{ nsRect(0, 0, 75, 40), nsRect(0, 60, 75, 40), 2500 },
{ nsRect(25, 0, 75, 40), nsRect(25, 60, 75, 40), 2500 },
{ nsRect(25, 0, 75, 40), nsRect(0, 60, 75, 40), 2000 },
{ nsRect(0, 0, 75, 40), nsRect(25, 60, 75, 40), 2000 },
};
PRBool success = PR_TRUE;
for (PRInt32 i = 0; i < nTests; i++) {
nsRegion r2;
r2.Sub(r, tests[i].rect1);
r2.Sub(r2, tests[i].rect2);
if (!r2.IsComplex())
fail("nsRegion code got unexpectedly smarter!");
nsRect largest = r2.GetLargestRectangle();
if (largest.width * largest.height != tests[i].expectedArea) {
fail("Did not succesfully find largest rectangle in two-rect-subtract region on iteration %d", i);
success = PR_FALSE;
}
}
return success;
}
public:
static PRBool Test() {
if (!TestSingleRect(nsRect(0, 52, 720, 480)) ||
!TestSingleRect(nsRect(-20, 40, 50, 20)) ||
!TestSingleRect(nsRect(-20, 40, 10, 8)) ||
!TestSingleRect(nsRect(-20, -40, 10, 8)) ||
!TestSingleRect(nsRect(-10, -10, 20, 20)))
return PR_FALSE;
if (!TestNonRectangular())
return PR_FALSE;
if (!TwoRectTest())
return PR_FALSE;
passed("TestLargestRegion");
return PR_TRUE;
}
};
int main(int argc, char** argv) {
ScopedXPCOM xpcom("TestRegion");
if (xpcom.failed())
return -1;
if (NS_FAILED(nsRegion::InitStatic())) {
fail("Could not initialize region statics");
return -1;
}
if (!TestLargestRegion::Test())
return -1;
nsRegion::ShutdownStatic();
return 0;
}

Просмотреть файл

@ -2066,6 +2066,8 @@ void nsWindow::UpdatePossiblyTransparentRegion(const nsIntRegion &aDirtyRegion,
nsIntRegion opaqueRegion;
opaqueRegion.Sub(clientBounds, mPossiblyTransparentRegion);
opaqueRegion.Or(opaqueRegion, childWindowRegion);
// Sometimes child windows overlap our bounds
opaqueRegion.And(opaqueRegion, clientBounds);
MARGINS margins = { 0, 0, 0, 0 };
DWORD_PTR dwStyle = ::GetWindowLongPtrW(hWnd, GWL_STYLE);
// If there is no opaque region or hidechrome=true then full glass
@ -2073,17 +2075,7 @@ void nsWindow::UpdatePossiblyTransparentRegion(const nsIntRegion &aDirtyRegion,
margins.cxLeftWidth = -1;
} else {
// Find the largest rectangle and use that to calculate the inset
nsIntRegionRectIterator rgnIter(opaqueRegion);
const nsIntRect *currentRect = rgnIter.Next();
nsIntRect largest = *currentRect;
nscoord largestArea = largest.width * largest.height;
while (currentRect = rgnIter.Next()) {
nscoord area = currentRect->width * currentRect->height;
if (area > largestArea) {
largest = *currentRect;
largestArea = area;
}
}
nsIntRect largest = opaqueRegion.GetLargestRectangle();
margins.cxLeftWidth = largest.x;
margins.cxRightWidth = clientBounds.width - largest.XMost();
margins.cyTopHeight = largest.y;