/* -*- 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/. */
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())) { returnfalse;
}
} returntrue;
}
auto iter = mBands.begin(); while (iter != mBands.end()) { if (iter->top >= aRect.YMost()) { returnfalse;
}
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. returntrue;
}
returnfalse;
}
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
// 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;
// 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;
}
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();
}
typedefvoid (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2, int y2);
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; constint TouchedByNothing = 0; constint TouchedByTop = 1; constint 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;
} elseif (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;
} elseif (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;
}
} elseif (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;
}
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);
}
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);
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());
} elseif (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());
} elseif (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());
} elseif (rect.X() >= first.X() && rect.XMost() <= first.XMost()) { // The bottom of the first contains the top of rect
deviceRect.SetTopEdge(firstDeviceRect.YMost());
}
}
}
// 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(); }
// 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;
}
}
// 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;
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.