Raw File
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());
}
back to top