| /* |
| * 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 "modules/desktop_capture/desktop_region.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "rtc_base/checks.h" |
| |
| namespace webrtc { |
| |
| DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right) |
| : left(left), right(right) {} |
| |
| DesktopRegion::Row::Row(const Row&) = default; |
| DesktopRegion::Row::Row(Row&&) = default; |
| |
| DesktopRegion::Row::Row(int32_t top, int32_t bottom) |
| : top(top), bottom(bottom) {} |
| |
| DesktopRegion::Row::~Row() {} |
| |
| DesktopRegion::DesktopRegion() {} |
| |
| DesktopRegion::DesktopRegion(const DesktopRect& rect) { |
| AddRect(rect); |
| } |
| |
| 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) { |
| Clear(); |
| 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() { |
| for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) { |
| delete row->second; |
| } |
| rows_.clear(); |
| } |
| |
| void DesktopRegion::SetRect(const DesktopRect& rect) { |
| Clear(); |
| AddRect(rect); |
| } |
| |
| void DesktopRegion::AddRect(const DesktopRect& 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 the two, |
| // ready to insert a new span into. |
| RTC_DCHECK_LE(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) { |
| RTC_DCHECK(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::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); |
| } |
| |
| // 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); |
| } else { |
| 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(); |
| RTC_DCHECK(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); |
| RTC_DCHECK_LT(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::Subtract(const DesktopRegion& region) { |
| if (region.rows_.empty()) |
| return; |
| |
| // `row_b` refers to the current row being subtracted. |
| Rows::const_iterator row_b = region.rows_.begin(); |
| |
| // Current vertical position at which subtraction is happening. |
| int top = row_b->second->top; |
| |
| // `row_a` refers to the current row we are subtracting from. Skip all rows |
| // above `top`. |
| Rows::iterator row_a = rows_.upper_bound(top); |
| |
| // Step through rows of the both regions subtracting content of `row_b` from |
| // `row_a`. |
| while (row_a != rows_.end() && row_b != region.rows_.end()) { |
| // Skip `row_a` if it doesn't intersect with the `row_b`. |
| if (row_a->second->bottom <= top) { |
| // Each output row is merged with previously-processed rows before further |
| // rows are processed. |
| MergeWithPrecedingRow(row_a); |
| ++row_a; |
| continue; |
| } |
| |
| if (top > row_a->second->top) { |
| // If `top` falls in the middle of `row_a` then split `row_a` into two, at |
| // `top`, and leave `row_a` referring to the lower of the two, ready to |
| // subtract spans from. |
| RTC_DCHECK_LE(top, row_a->second->bottom); |
| Rows::iterator new_row = rows_.insert( |
| row_a, Rows::value_type(top, new Row(row_a->second->top, top))); |
| row_a->second->top = top; |
| new_row->second->spans = row_a->second->spans; |
| } else if (top < row_a->second->top) { |
| // If the `top` is above `row_a` then skip the range between `top` and |
| // top of `row_a` because it's empty. |
| top = row_a->second->top; |
| if (top >= row_b->second->bottom) { |
| ++row_b; |
| if (row_b != region.rows_.end()) |
| top = row_b->second->top; |
| continue; |
| } |
| } |
| |
| if (row_b->second->bottom < row_a->second->bottom) { |
| // If the bottom of `row_b` falls in the middle of the `row_a` split |
| // `row_a` into two, at `top`, and leave `row_a` referring to the upper of |
| // the two, ready to subtract spans from. |
| int bottom = row_b->second->bottom; |
| Rows::iterator new_row = |
| rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom))); |
| row_a->second->top = bottom; |
| new_row->second->spans = row_a->second->spans; |
| row_a = new_row; |
| } |
| |
| // At this point the vertical range covered by `row_a` lays within the |
| // range covered by `row_b`. Subtract `row_b` spans from `row_a`. |
| RowSpanSet new_spans; |
| SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans); |
| new_spans.swap(row_a->second->spans); |
| top = row_a->second->bottom; |
| |
| if (top >= row_b->second->bottom) { |
| ++row_b; |
| if (row_b != region.rows_.end()) |
| top = row_b->second->top; |
| } |
| |
| // Check if the row is empty after subtraction and delete it. Otherwise move |
| // to the next one. |
| if (row_a->second->spans.empty()) { |
| Rows::iterator row_to_delete = row_a; |
| ++row_a; |
| delete row_to_delete->second; |
| rows_.erase(row_to_delete); |
| } else { |
| MergeWithPrecedingRow(row_a); |
| ++row_a; |
| } |
| } |
| |
| if (row_a != rows_.end()) |
| MergeWithPrecedingRow(row_a); |
| } |
| |
| void DesktopRegion::Subtract(const DesktopRect& rect) { |
| DesktopRegion region; |
| region.AddRect(rect); |
| Subtract(region); |
| } |
| |
| void DesktopRegion::Translate(int32_t dx, int32_t 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) { |
| 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); |
| RTC_DCHECK(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; |
| } |
| |
| // static |
| void DesktopRegion::SubtractRows(const RowSpanSet& set_a, |
| const RowSpanSet& set_b, |
| RowSpanSet* output) { |
| RTC_DCHECK(!set_a.empty() && !set_b.empty()); |
| |
| RowSpanSet::const_iterator it_b = set_b.begin(); |
| |
| // Iterate over all spans in `set_a` adding parts of it that do not intersect |
| // with `set_b` to the `output`. |
| for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end(); |
| ++it_a) { |
| // If there is no intersection then append the current span and continue. |
| if (it_b == set_b.end() || it_a->right < it_b->left) { |
| output->push_back(*it_a); |
| continue; |
| } |
| |
| // Iterate over `set_b` spans that may intersect with `it_a`. |
| int pos = it_a->left; |
| while (it_b != set_b.end() && it_b->left < it_a->right) { |
| if (it_b->left > pos) |
| output->push_back(RowSpan(pos, it_b->left)); |
| if (it_b->right > pos) { |
| pos = it_b->right; |
| if (pos >= it_a->right) |
| break; |
| } |
| ++it_b; |
| } |
| if (pos < it_a->right) |
| output->push_back(RowSpan(pos, it_a->right)); |
| } |
| } |
| |
| DesktopRegion::Iterator::Iterator(const DesktopRegion& region) |
| : region_(region), |
| row_(region.rows_.begin()), |
| previous_row_(region.rows_.end()) { |
| if (!IsAtEnd()) { |
| RTC_DCHECK_GT(row_->second->spans.size(), 0); |
| row_span_ = row_->second->spans.begin(); |
| UpdateCurrentRect(); |
| } |
| } |
| |
| DesktopRegion::Iterator::~Iterator() {} |
| |
| bool DesktopRegion::Iterator::IsAtEnd() const { |
| return row_ == region_.rows_.end(); |
| } |
| |
| void DesktopRegion::Iterator::Advance() { |
| RTC_DCHECK(!IsAtEnd()); |
| |
| while (true) { |
| ++row_span_; |
| if (row_span_ == row_->second->spans.end()) { |
| previous_row_ = row_; |
| ++row_; |
| if (row_ != region_.rows_.end()) { |
| RTC_DCHECK_GT(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; |
| } |
| |
| RTC_DCHECK(!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 |