From 3ee13e4ac219338c861a5d08212ffba88001cdb1 Mon Sep 17 00:00:00 2001 From: "sergeyu@chromium.org" Date: Tue, 4 Jun 2013 00:38:39 +0000 Subject: [PATCH] Optimized DesktopRegion implementation. Now DestktopRegion can merge overlapping rectangles. R=wez@chromium.org Review URL: https://webrtc-codereview.appspot.com/1526004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4161 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../desktop_capture/desktop_capture.gypi | 1 + .../modules/desktop_capture/desktop_region.cc | 401 ++++++++++++++- .../modules/desktop_capture/desktop_region.h | 98 +++- .../desktop_region_unittest.cc | 464 ++++++++++++++++++ 4 files changed, 931 insertions(+), 33 deletions(-) create mode 100644 webrtc/modules/desktop_capture/desktop_region_unittest.cc diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 8ff238be57..a9fdac6380 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -59,6 +59,7 @@ '<(DEPTH)/testing/gtest.gyp:gtest', ], 'sources': [ + "desktop_region_unittest.cc", "window_capturer_unittest.cc", ], }, diff --git a/webrtc/modules/desktop_capture/desktop_region.cc b/webrtc/modules/desktop_capture/desktop_region.cc index 71398b31cc..8ba6c64351 100644 --- a/webrtc/modules/desktop_capture/desktop_region.cc +++ b/webrtc/modules/desktop_capture/desktop_region.cc @@ -10,18 +10,70 @@ #include "webrtc/modules/desktop_capture/desktop_region.h" +#include +#include + namespace webrtc { +DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right) + : left(left), right(right) { +} + +DesktopRegion::Row::Row(int32_t top, int32_t bottom) + : top(top), bottom(bottom) { +} + DesktopRegion::DesktopRegion() {} -DesktopRegion::DesktopRegion(const DesktopRegion& other) - : rects_(other.rects_) { +DesktopRegion::DesktopRegion(const DesktopRect& rect) { + AddRect(rect); } -DesktopRegion::~DesktopRegion() {} +DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) { + AddRects(rects, count); +} + +DesktopRegion::DesktopRegion(const DesktopRegion& other) { + *this = other; +} + +DesktopRegion::~DesktopRegion() { + Clear(); +} + +DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) { + rows_ = other.rows_; + for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { + // Copy each row. + Row* row = it->second; + it->second = new Row(*row); + } + return *this; +} + +bool DesktopRegion::Equals(const DesktopRegion& region) const { + // Iterate over rows of the tow regions and compare each row. + Rows::const_iterator it1 = rows_.begin(); + Rows::const_iterator it2 = region.rows_.begin(); + while (it1 != rows_.end()) { + if (it2 == region.rows_.end() || + it1->first != it2->first || + it1->second->top != it2->second->top || + it1->second->bottom != it2->second->bottom || + it1->second->spans != it2->second->spans) { + return false; + } + ++it1; + ++it2; + } + return it2 == region.rows_.end(); +} void DesktopRegion::Clear() { - rects_.clear(); + for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) { + delete row->second; + } + rows_.clear(); } void DesktopRegion::SetRect(const DesktopRect& rect) { @@ -30,55 +82,352 @@ void DesktopRegion::SetRect(const DesktopRect& rect) { } void DesktopRegion::AddRect(const DesktopRect& rect) { - if (!rect.is_empty()) - rects_.push_back(rect); + if (rect.is_empty()) + return; + + // Top of the part of the |rect| that hasn't been inserted yet. Increased as + // we iterate over the rows until it reaches |rect.bottom()|. + int top = rect.top(); + + // Iterate over all rows that may intersect with |rect| and add new rows when + // necessary. + Rows::iterator row = rows_.upper_bound(top); + while (top < rect.bottom()) { + if (row == rows_.end() || top < row->second->top) { + // If |top| is above the top of the current |row| then add a new row above + // the current one. + int32_t bottom = rect.bottom(); + if (row != rows_.end() && row->second->top < bottom) + bottom = row->second->top; + row = rows_.insert( + row, Rows::value_type(bottom, new Row(top, bottom))); + } else if (top > row->second->top) { + // If the |top| falls in the middle of the |row| then split |row| into + // two, at |top|, and leave |row| referring to the lower of of the two, + // ready to insert a new span into. + assert(top <= row->second->bottom); + Rows::iterator new_row = rows_.insert( + row, Rows::value_type(top, new Row(row->second->top, top))); + row->second->top = top; + new_row->second->spans = row->second->spans; + } + + if (rect.bottom() < row->second->bottom) { + // If the bottom of the |rect| falls in the middle of the |row| split + // |row| into two, at |top|, and leave |row| referring to the upper of + // the two, ready to insert a new span into. + Rows::iterator new_row = rows_.insert( + row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom()))); + row->second->top = rect.bottom(); + new_row->second->spans = row->second->spans; + row = new_row; + } + + // Add a new span to the current row. + AddSpanToRow(row->second, rect.left(), rect.right()); + top = row->second->bottom; + + MergeWithPrecedingRow(row); + + // Move to the next row. + row++; + } + + if (row != rows_.end()) + MergeWithPrecedingRow(row); +} + +void DesktopRegion::AddRects(const DesktopRect* rects, int count) { + for (int i = 0; i < count; ++i) { + AddRect(rects[i]); + } +} + +void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) { + assert(row != rows_.end()); + + if (row != rows_.begin()) { + Rows::iterator previous_row = row; + previous_row--; + + // If |row| and |previous_row| are next to each other and contain the same + // set of spans then they can be merged. + if (previous_row->second->bottom == row->second->top && + previous_row->second->spans == row->second->spans) { + row->second->top = previous_row->second->top; + delete previous_row->second; + rows_.erase(previous_row); + } + } } void DesktopRegion::AddRegion(const DesktopRegion& region) { + // TODO(sergeyu): This function is not optimized - potentially it can iterate + // over rows of the two regions similar to how it works in Intersect(). for (Iterator it(region); !it.IsAtEnd(); it.Advance()) { AddRect(it.rect()); } } -void DesktopRegion::IntersectWith(const DesktopRect& rect) { - bool remove_empty_rects = false; - for (RectsList::iterator it = rects_.begin(); it != rects_.end(); ++it) { - it->IntersectWith(rect); - remove_empty_rects = remove_empty_rects | it->is_empty(); - } - if (remove_empty_rects) { - RectsList new_rects; - new_rects.reserve(rects_.size()); - for (RectsList::iterator it = rects_.begin(); it != rects_.end(); ++it) { - if (!it->is_empty()) - new_rects.push_back(*it); +void DesktopRegion::Intersect(const DesktopRegion& region1, + const DesktopRegion& region2) { + Clear(); + + Rows::const_iterator it1 = region1.rows_.begin(); + Rows::const_iterator end1 = region1.rows_.end(); + Rows::const_iterator it2 = region2.rows_.begin(); + Rows::const_iterator end2 = region2.rows_.end(); + if (it1 == end1 || it2 == end2) + return; + + while (it1 != end1 && it2 != end2) { + // Arrange for |it1| to always be the top-most of the rows. + if (it2->second->top < it1->second->top) { + std::swap(it1, it2); + std::swap(end1, end2); } - rects_.swap(new_rects); + + // Skip |it1| if it doesn't intersect |it2| at all. + if (it1->second->bottom <= it2->second->top) { + ++it1; + continue; + } + + // Top of the |it1| row is above the top of |it2|, so top of the + // intersection is always the top of |it2|. + int32_t top = it2->second->top; + int32_t bottom = std::min(it1->second->bottom, it2->second->bottom); + + Rows::iterator new_row = rows_.insert( + rows_.end(), Rows::value_type(bottom, new Row(top, bottom))); + IntersectRows(it1->second->spans, it2->second->spans, + &new_row->second->spans); + if (new_row->second->spans.empty()) { + delete new_row->second; + rows_.erase(new_row); + } + + MergeWithPrecedingRow(new_row); + + // If |it1| was completely consumed, move to the next one. + if (it1->second->bottom == bottom) + ++it1; + // If |it2| was completely consumed, move to the next one. + if (it2->second->bottom == bottom) + ++it2; } } +// static +void DesktopRegion::IntersectRows(const RowSpanSet& set1, + const RowSpanSet& set2, + RowSpanSet* output) { + RowSpanSet::const_iterator it1 = set1.begin(); + RowSpanSet::const_iterator end1 = set1.end(); + RowSpanSet::const_iterator it2 = set2.begin(); + RowSpanSet::const_iterator end2 = set2.end(); + assert(it1 != end1 && it2 != end2); + + do { + // Arrange for |it1| to always be the left-most of the spans. + if (it2->left < it1->left) { + std::swap(it1, it2); + std::swap(end1, end2); + } + + // Skip |it1| if it doesn't intersect |it2| at all. + if (it1->right <= it2->left) { + ++it1; + continue; + } + + int32_t left = it2->left; + int32_t right = std::min(it1->right, it2->right); + assert(left < right); + + output->push_back(RowSpan(left, right)); + + // If |it1| was completely consumed, move to the next one. + if (it1->right == right) + ++it1; + // If |it2| was completely consumed, move to the next one. + if (it2->right == right) + ++it2; + } while (it1 != end1 && it2 != end2); +} + +void DesktopRegion::IntersectWith(const DesktopRegion& region) { + DesktopRegion old_region; + Swap(&old_region); + Intersect(old_region, region); +} + +void DesktopRegion::IntersectWith(const DesktopRect& rect) { + DesktopRegion region; + region.AddRect(rect); + IntersectWith(region); +} + void DesktopRegion::Translate(int32_t dx, int32_t dy) { - for (RectsList::iterator it = rects_.begin(); it != rects_.end(); ++it) { - it->Translate(dx, dy); + Rows new_rows; + + for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { + Row* row = it->second; + + row->top += dy; + row->bottom += dy; + + if (dx != 0) { + // Translate each span. + for (RowSpanSet::iterator span = row->spans.begin(); + span != row->spans.end(); ++span) { + span->left += dx; + span->right += dx; + } + } + + if (dy != 0) + new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row)); } + + if (dy != 0) + new_rows.swap(rows_); } void DesktopRegion::Swap(DesktopRegion* region) { - rects_.swap(region->rects_); + rows_.swap(region->rows_); +} + +// static +bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) { + return r.right < value; +} + +// static +bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) { + return r.left < value; +} + +// static +void DesktopRegion::AddSpanToRow(Row* row, int left, int right) { + // First check if the new span is located to the right of all existing spans. + // This is an optimization to avoid binary search in the case when rectangles + // are inserted sequentially from left to right. + if (row->spans.empty() || left > row->spans.back().right) { + row->spans.push_back(RowSpan(left, right)); + return; + } + + // Find the first span that ends at or after |left|. + RowSpanSet::iterator start = + std::lower_bound(row->spans.begin(), row->spans.end(), left, + CompareSpanRight); + assert(start < row->spans.end()); + + // Find the first span that starts after |right|. + RowSpanSet::iterator end = + std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft); + if (end == row->spans.begin()) { + // There are no overlaps. Just insert the new span at the beginning. + row->spans.insert(row->spans.begin(), RowSpan(left, right)); + return; + } + + // Move end to the left, so that it points the last span that ends at or + // before |right|. + end--; + + // At this point [start, end] is the range of spans that intersect with the + // new one. + if (end < start) { + // There are no overlaps. Just insert the new span at the correct position. + row->spans.insert(start, RowSpan(left, right)); + return; + } + + left = std::min(left, start->left); + right = std::max(right, end->right); + + // Replace range [start, end] with the new span. + *start = RowSpan(left, right); + ++start; + ++end; + if (start < end) + row->spans.erase(start, end); +} + +// static +bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) { + // Find the first span that starts at or after |span.left| and then check if + // it's the same span. + RowSpanSet::const_iterator it = + std::lower_bound(row.spans.begin(), row.spans.end(), span.left, + CompareSpanLeft); + return it != row.spans.end() && *it == span; } DesktopRegion::Iterator::Iterator(const DesktopRegion& region) : region_(region), - it_(region.rects_.begin()) { + row_(region.rows_.begin()), + previous_row_(region.rows_.end()) { + if (!IsAtEnd()) { + assert(row_->second->spans.size() > 0); + row_span_ = row_->second->spans.begin(); + UpdateCurrentRect(); + } } bool DesktopRegion::Iterator::IsAtEnd() const { - return it_ == region_.rects_.end(); + return row_ == region_.rows_.end(); } void DesktopRegion::Iterator::Advance() { - ++it_; + assert(!IsAtEnd()); + + while (true) { + ++row_span_; + if (row_span_ == row_->second->spans.end()) { + previous_row_ = row_; + ++row_; + if (row_ != region_.rows_.end()) { + assert(row_->second->spans.size() > 0); + row_span_ = row_->second->spans.begin(); + } + } + + if (IsAtEnd()) + return; + + // If the same span exists on the previous row then skip it, as we've + // already returned this span merged into the previous one, via + // UpdateCurrentRect(). + if (previous_row_ != region_.rows_.end() && + previous_row_->second->bottom == row_->second->top && + IsSpanInRow(*previous_row_->second, *row_span_)) { + continue; + } + + break; + } + + assert(!IsAtEnd()); + UpdateCurrentRect(); +} + +void DesktopRegion::Iterator::UpdateCurrentRect() { + // Merge the current rectangle with the matching spans from later rows. + int bottom; + Rows::const_iterator bottom_row = row_; + Rows::const_iterator previous; + do { + bottom = bottom_row->second->bottom; + previous = bottom_row; + ++bottom_row; + } while (bottom_row != region_.rows_.end() && + previous->second->bottom == bottom_row->second->top && + IsSpanInRow(*bottom_row->second, *row_span_)); + rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top, + row_span_->right, bottom); } } // namespace webrtc - diff --git a/webrtc/modules/desktop_capture/desktop_region.h b/webrtc/modules/desktop_capture/desktop_region.h index 9a9daa4dea..403f2c29e1 100644 --- a/webrtc/modules/desktop_capture/desktop_region.h +++ b/webrtc/modules/desktop_capture/desktop_region.h @@ -11,6 +11,7 @@ #ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ #define WEBRTC_MODULES_DESKTOP_CAPTURE_DESKTOP_REGION_H_ +#include #include #include "webrtc/modules/desktop_capture/desktop_geometry.h" @@ -21,9 +22,44 @@ namespace webrtc { // DesktopRegion represents a region of the screen or window. // -// TODO(sergeyu): Current implementation just stores list of rectangles that may -// overlap. Optimize it. +// Internally each region is stored as a set of rows where each row contains one +// or more rectangles aligned vertically. class DesktopRegion { + private: + // The following private types need to be declared first because they are used + // in the public Iterator. + + // RowSpan represents a horizontal span withing a single row. + struct RowSpan { + RowSpan(int32_t left, int32_t right); + + // Used by std::vector<>. + bool operator==(const RowSpan& that) const { + return left == that.left && right == that.right; + } + + int32_t left; + int32_t right; + }; + + typedef std::vector RowSpanSet; + + // Row represents a single row of a region. A row is set of rectangles that + // have the same vertical position. + struct Row { + Row(int32_t top, int32_t bottom); + + int32_t top; + int32_t bottom; + + RowSpanSet spans; + }; + + // Type used to store list of rows in the region. The bottom position of row + // is used as the key so that rows are always ordered by their position. The + // map stores pointers to make Translate() more efficient. + typedef std::map Rows; + public: // Iterator that can be used to iterate over rectangles of a DesktopRegion. // The region must not be mutated while the iterator is used. @@ -34,24 +70,51 @@ class DesktopRegion { bool IsAtEnd() const; void Advance(); - const DesktopRect& rect() const { return *it_; } + const DesktopRect& rect() const { return rect_; } private: const DesktopRegion& region_; - std::vector::const_iterator it_; + + // Updates |rect_| based on the current |row_| and |row_span_|. If + // |row_span_| matches spans on consecutive rows then they are also merged + // into |rect_|, to generate more efficient output. + void UpdateCurrentRect(); + + Rows::const_iterator row_; + Rows::const_iterator previous_row_; + RowSpanSet::const_iterator row_span_; + DesktopRect rect_; }; DesktopRegion(); + explicit DesktopRegion(const DesktopRect& rect); + DesktopRegion(const DesktopRect* rects, int count); DesktopRegion(const DesktopRegion& other); ~DesktopRegion(); - bool is_empty() const { return rects_.empty(); } + DesktopRegion& operator=(const DesktopRegion& other); + bool is_empty() const { return rows_.empty(); } + + bool Equals(const DesktopRegion& region) const; + + // Reset the region to be empty. void Clear(); + + // Reset region to contain just |rect|. void SetRect(const DesktopRect& rect); + + // Adds specified rect(s) or region to the region. void AddRect(const DesktopRect& rect); + void AddRects(const DesktopRect* rects, int count); void AddRegion(const DesktopRegion& region); + // Finds intersection of two regions and stores them in the current region. + void Intersect(const DesktopRegion& region1, const DesktopRegion& region2); + + // Same as above but intersects content of the current region with |region|. + void IntersectWith(const DesktopRegion& region); + // Clips the region by the |rect|. void IntersectWith(const DesktopRect& rect); @@ -61,8 +124,29 @@ class DesktopRegion { void Swap(DesktopRegion* region); private: - typedef std::vector RectsList; - RectsList rects_; + // Comparison functions used for std::lower_bound(). Compare left or right + // edges withs a given |value|. + static bool CompareSpanLeft(const RowSpan& r, int32_t value); + static bool CompareSpanRight(const RowSpan& r, int32_t value); + + // Adds a new span to the row, coalescing spans if necessary. + static void AddSpanToRow(Row* row, int32_t left, int32_t right); + + // Returns true if the |span| exists in the given |row|. + static bool IsSpanInRow(const Row& row, const RowSpan& rect); + + // Calculates the intersection of two sets of spans. + static void IntersectRows(const RowSpanSet& set1, + const RowSpanSet& set2, + RowSpanSet* output); + + // Merges |row| with the row above it if they contain the same spans. Doesn't + // do anything if called with |row| set to rows_.begin() (i.e. first row of + // the region). If the rows were merged |row| remains a valid iterator to the + // merged row. + void MergeWithPrecedingRow(Rows::iterator row); + + Rows rows_; }; } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/desktop_region_unittest.cc b/webrtc/modules/desktop_capture/desktop_region_unittest.cc new file mode 100644 index 0000000000..64ad4b8a8e --- /dev/null +++ b/webrtc/modules/desktop_capture/desktop_region_unittest.cc @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/desktop_region.h" + +#include + +#include "gtest/gtest.h" + +namespace webrtc { + +namespace { + +int RadmonInt(int max) { + return (rand() / 256) % max; +} + +void CompareRegion(const DesktopRegion& region, + const DesktopRect rects[], int rects_size) { + DesktopRegion::Iterator it(region); + for (int i = 0; i < rects_size; ++i) { + SCOPED_TRACE(i); + ASSERT_FALSE(it.IsAtEnd()); + EXPECT_TRUE(it.rect().equals(rects[i])) + << it.rect().left() << "-" << it.rect().right() << "." + << it.rect().top() << "-" << it.rect().bottom() << " " + << rects[i].left() << "-" << rects[i].right() << "." + << rects[i].top() << "-" << rects[i].bottom(); + it.Advance(); + } + EXPECT_TRUE(it.IsAtEnd()); +} + +} // namespace + +// Verify that regions are empty when created. +TEST(DesktopRegionTest, Empty) { + DesktopRegion r; + CompareRegion(r, NULL, 0); +} + +// Verify that empty rectangles are ignored. +TEST(DesktopRegionTest, AddEmpty) { + DesktopRegion r; + DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 0, 0); + r.AddRect(rect); + CompareRegion(r, NULL, 0); +} + +// Verify that regions with a single rectangles are handled properly. +TEST(DesktopRegionTest, SingleRect) { + DesktopRegion r; + DesktopRect rect = DesktopRect::MakeXYWH(1, 2, 3, 4); + r.AddRect(rect); + CompareRegion(r, &rect, 1); +} + +// Verify that non-overlapping rectangles are not merged. +TEST(DesktopRegionTest, NonOverlappingRects) { + struct Case { + int count; + DesktopRect rects[4]; + } cases[] = { + { 1, { DesktopRect::MakeXYWH(10, 10, 10, 10) } }, + { 2, { DesktopRect::MakeXYWH(10, 10, 10, 10), + DesktopRect::MakeXYWH(30, 10, 10, 15) } }, + { 2, { DesktopRect::MakeXYWH(10, 10, 10, 10), + DesktopRect::MakeXYWH(10, 30, 10, 5) } }, + { 3, { DesktopRect::MakeXYWH(10, 10, 10, 9), + DesktopRect::MakeXYWH(30, 10, 15, 10), + DesktopRect::MakeXYWH(10, 30, 8, 10) } }, + { 4, { DesktopRect::MakeXYWH(0, 0, 30, 10), + DesktopRect::MakeXYWH(40, 0, 10, 30), + DesktopRect::MakeXYWH(0, 20, 10, 30), + DesktopRect::MakeXYWH(20, 40, 30, 10) } }, + { 4, { DesktopRect::MakeXYWH(0, 0, 10, 100), + DesktopRect::MakeXYWH(20, 10, 30, 10), + DesktopRect::MakeXYWH(20, 30, 30, 10), + DesktopRect::MakeXYWH(20, 50, 30, 10) } }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + + for (int j = 0; j < cases[i].count; ++j) { + r.AddRect(cases[i].rects[j]); + } + CompareRegion(r, cases[i].rects, cases[i].count); + + SCOPED_TRACE("Reverse"); + + // Try inserting rects in reverse order. + r.Clear(); + for (int j = cases[i].count - 1; j >= 0; --j) { + r.AddRect(cases[i].rects[j]); + } + CompareRegion(r, cases[i].rects, cases[i].count); + } +} + +TEST(DesktopRegionTest, TwoRects) { + struct Case { + DesktopRect input_rect1; + DesktopRect input_rect2; + int expected_count; + DesktopRect expected_rects[3]; + } cases[] = { + // Touching rectangles that merge into one. + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 100, 100, 200), + 1, { DesktopRect::MakeLTRB(0, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(100, 0, 200, 100), + 1, { DesktopRect::MakeLTRB(100, 0, 200, 200) } }, + + // Rectangles touching on the vertical edge. + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 150, 100, 250), + 3, { DesktopRect::MakeLTRB(100, 100, 200, 150), + DesktopRect::MakeLTRB(0, 150, 200, 200), + DesktopRect::MakeLTRB(0, 200, 100, 250) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 50, 100, 150), + 3, { DesktopRect::MakeLTRB(0, 50, 100, 100), + DesktopRect::MakeLTRB(0, 100, 200, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 120, 100, 180), + 3, { DesktopRect::MakeLTRB(100, 100, 200, 120), + DesktopRect::MakeLTRB(0, 120, 200, 180), + DesktopRect::MakeLTRB(100, 180, 200, 200) } }, + + // Rectangles touching on the horizontal edge. + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 0, 250, 100), + 2, { DesktopRect::MakeLTRB(150, 0, 250, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 0, 150, 100), + 2, { DesktopRect::MakeLTRB(50, 0, 150, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(120, 0, 180, 100), + 2, { DesktopRect::MakeLTRB(120, 0, 180, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + + // Overlapping rectangles. + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + 3, { DesktopRect::MakeLTRB(50, 50, 150, 100), + DesktopRect::MakeLTRB(50, 100, 200, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 50, 250, 150), + 3, { DesktopRect::MakeLTRB(150, 50, 250, 100), + DesktopRect::MakeLTRB(100, 100, 250, 150), + DesktopRect::MakeLTRB(100, 150, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 120, 150, 180), + 3, { DesktopRect::MakeLTRB(100, 100, 200, 120), + DesktopRect::MakeLTRB(0, 120, 200, 180), + DesktopRect::MakeLTRB(100, 180, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(120, 0, 180, 150), + 2, { DesktopRect::MakeLTRB(120, 0, 180, 100), + DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 0, 200, 300), + DesktopRect::MakeLTRB(0, 100, 300, 200), + 3, { DesktopRect::MakeLTRB(100, 0, 200, 100), + DesktopRect::MakeLTRB(0, 100, 300, 200), + DesktopRect::MakeLTRB(100, 200, 200, 300)} }, + + // One rectangle enclosing another. + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 150, 180, 180), + 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(100, 100, 180, 180), + 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(150, 150, 200, 200), + 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + + r.AddRect(cases[i].input_rect1); + r.AddRect(cases[i].input_rect2); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + + SCOPED_TRACE("Reverse"); + + // Run the same test with rectangles inserted in reverse order. + r.Clear(); + r.AddRect(cases[i].input_rect2); + r.AddRect(cases[i].input_rect1); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +// Verify that DesktopRegion::AddRectToRow() works correctly by creating a row +// of not overlapping rectangles and insert an overlapping rectangle into the +// row at different positions. Result is verified by building a map of the +// region in an array and comparing it with the expected values. +TEST(DesktopRegionTest, SameRow) { + const int kMapWidth = 50; + const int kLastRectSizes[] = {3, 27}; + + DesktopRegion base_region; + bool base_map[kMapWidth] = { false, }; + + base_region.AddRect(DesktopRect::MakeXYWH(5, 0, 5, 1)); + std::fill_n(base_map + 5, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(15, 0, 5, 1)); + std::fill_n(base_map + 15, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(25, 0, 5, 1)); + std::fill_n(base_map + 25, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(35, 0, 5, 1)); + std::fill_n(base_map + 35, 5, true); + base_region.AddRect(DesktopRect::MakeXYWH(45, 0, 5, 1)); + std::fill_n(base_map + 45, 5, true); + + for (size_t i = 0; i < sizeof(kLastRectSizes) / sizeof(kLastRectSizes[0]); + i++) { + int last_rect_size = kLastRectSizes[i]; + for (int x = 0; x < kMapWidth - last_rect_size; x++) { + SCOPED_TRACE(x); + + DesktopRegion r = base_region; + r.AddRect(DesktopRect::MakeXYWH(x, 0, last_rect_size, 1)); + + bool expected_map[kMapWidth]; + std::copy(base_map, base_map + kMapWidth, expected_map); + std::fill_n(expected_map + x, last_rect_size, true); + + bool map[kMapWidth] = { false, }; + + int pos = -1; + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + EXPECT_GT(it.rect().left(), pos); + pos = it.rect().right(); + std::fill_n(map + it.rect().left(), it.rect().width(), true); + } + + EXPECT_TRUE(std::equal(map, map + kMapWidth, expected_map)); + } + } +} + +TEST(DesktopRegionTest, ComplexRegions) { + struct Case { + int input_count; + DesktopRect input_rects[4]; + int expected_count; + DesktopRect expected_rects[6]; + } cases[] = { + { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(0, 100, 100, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120), }, + 2, { DesktopRect::MakeLTRB(0, 100, 200, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120) } }, + { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175) }, + 4, { DesktopRect::MakeLTRB(50, 50, 150, 100), + DesktopRect::MakeLTRB(50, 100, 200, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175), + DesktopRect::MakeLTRB(100, 150, 200, 200) } }, + { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 0, 65, 15) }, + 6, { DesktopRect::MakeLTRB(0, 0, 30, 10), + DesktopRect::MakeLTRB(50, 0, 65, 15), + DesktopRect::MakeLTRB(0, 10, 40, 20), + DesktopRect::MakeLTRB(0, 20, 50, 30), + DesktopRect::MakeLTRB(10, 30, 50, 40), + DesktopRect::MakeLTRB(20, 40, 50, 50) } }, + { 3, { DesktopRect::MakeLTRB(10, 10, 40, 20), + DesktopRect::MakeLTRB(10, 30, 40, 40), + DesktopRect::MakeLTRB(10, 20, 40, 30) }, + 1, { DesktopRect::MakeLTRB(10, 10, 40, 40) } }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r; + r.AddRects(cases[i].input_rects, cases[i].input_count); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + + // Try inserting rectangles in reverse order. + r.Clear(); + for (int j = cases[i].input_count - 1; j >= 0; --j) { + r.AddRect(cases[i].input_rects[j]); + } + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, Equals) { + struct Region { + int count; + DesktopRect rects[4]; + int id; + } regions[] = { + // Same region with one of the rectangles 1 pixel wider/taller. + { 2, { DesktopRect::MakeLTRB(0, 100, 200, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120) }, 0 }, + { 2, { DesktopRect::MakeLTRB(0, 100, 201, 200), + DesktopRect::MakeLTRB(310, 110, 320, 120) }, 1 }, + { 2, { DesktopRect::MakeLTRB(0, 100, 200, 201), + DesktopRect::MakeLTRB(310, 110, 320, 120) }, 2 }, + + // Same region with one of the rectangles shifted horizontally and + // vertically. + { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 0, 65, 15) }, 3 }, + { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(50, 1, 65, 16) }, 4 }, + { 4, { DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50), + DesktopRect::MakeLTRB(51, 0, 66, 15) }, 5 }, + + // Same region defined by a different set of rectangles - one of the + // rectangle is split horizontally into two. + { 3, { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175) }, 6 }, + { 4, { DesktopRect::MakeLTRB(100, 100, 200, 200), + DesktopRect::MakeLTRB(50, 50, 100, 150), + DesktopRect::MakeLTRB(100, 50, 150, 150), + DesktopRect::MakeLTRB(300, 125, 350, 175) }, 6 }, + + // Rectangle region defined by a set of rectangles that merge into one. + { 3, { DesktopRect::MakeLTRB(10, 10, 40, 20), + DesktopRect::MakeLTRB(10, 30, 40, 40), + DesktopRect::MakeLTRB(10, 20, 40, 30) }, 7 }, + { 1, { DesktopRect::MakeLTRB(10, 10, 40, 40) }, 7 }, + }; + int kTotalRegions = sizeof(regions) / sizeof(Region); + + for (int i = 0; i < kTotalRegions; ++i) { + SCOPED_TRACE(i); + + DesktopRegion r1(regions[i].rects, regions[i].count); + for (int j = 0; j < kTotalRegions; ++j) { + SCOPED_TRACE(j); + + DesktopRegion r2(regions[j].rects, regions[j].count); + EXPECT_EQ(regions[i].id == regions[j].id, r1.Equals(r2)); + } + } +} + +TEST(DesktopRegionTest, Translate) { + struct Case { + int input_count; + DesktopRect input_rects[4]; + int dx; + int dy; + int expected_count; + DesktopRect expected_rects[5]; + } cases[] = { + { 3, { DesktopRect::MakeLTRB(0, 0, 30, 30), + DesktopRect::MakeLTRB(10, 10, 40, 40), + DesktopRect::MakeLTRB(20, 20, 50, 50) }, + 3, 5, + 5, { DesktopRect::MakeLTRB(3, 5, 33, 15), + DesktopRect::MakeLTRB(3, 15, 43, 25), + DesktopRect::MakeLTRB(3, 25, 53, 35), + DesktopRect::MakeLTRB(13, 35, 53, 45), + DesktopRect::MakeLTRB(23, 45, 53, 55) } }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r(cases[i].input_rects, cases[i].input_count); + r.Translate(cases[i].dx, cases[i].dy); + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, Intersect) { + struct Case { + int input1_count; + DesktopRect input1_rects[4]; + int input2_count; + DesktopRect input2_rects[4]; + int expected_count; + DesktopRect expected_rects[5]; + } cases[] = { + { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) }, + 1, { DesktopRect::MakeLTRB(50, 50, 150, 150) }, + 1, { DesktopRect::MakeLTRB(50, 50, 100, 100) } }, + + { 1, { DesktopRect::MakeLTRB(100, 0, 200, 300) }, + 1, { DesktopRect::MakeLTRB(0, 100, 300, 200) }, + 1, { DesktopRect::MakeLTRB(100, 100, 200, 200) } }, + + { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) }, + 2, { DesktopRect::MakeLTRB(50, 10, 150, 30), + DesktopRect::MakeLTRB(50, 30, 160, 50) }, + 1, { DesktopRect::MakeLTRB(50, 10, 100, 50) } }, + + { 1, { DesktopRect::MakeLTRB(0, 0, 100, 100) }, + 2, { DesktopRect::MakeLTRB(50, 10, 150, 30), + DesktopRect::MakeLTRB(50, 30, 90, 50) }, + 2, { DesktopRect::MakeLTRB(50, 10, 100, 30), + DesktopRect::MakeLTRB(50, 30, 90, 50) } }, + }; + + for (size_t i = 0; i < (sizeof(cases) / sizeof(Case)); ++i) { + SCOPED_TRACE(i); + + DesktopRegion r1(cases[i].input1_rects, cases[i].input1_count); + DesktopRegion r2(cases[i].input2_rects, cases[i].input2_count); + + DesktopRegion r; + r.Intersect(r1, r2); + + CompareRegion(r, cases[i].expected_rects, cases[i].expected_count); + } +} + +TEST(DesktopRegionTest, DISABLED_Performance) { + for (int c = 0; c < 1000; ++c) { + DesktopRegion r; + for (int i = 0; i < 10; ++i) { + r.AddRect(DesktopRect::MakeXYWH( + RadmonInt(1000), RadmonInt(1000), 200, 200)); + } + + for (int i = 0; i < 1000; ++i) { + r.AddRect(DesktopRect::MakeXYWH( + RadmonInt(1000), RadmonInt(1000), + 5 + RadmonInt(10) * 5, 5 + RadmonInt(10) * 5)); + } + + // Iterate over the rectangles. + for (DesktopRegion::Iterator it(r); !it.IsAtEnd(); it.Advance()) { + } + } +} + +} // namespace webrtc