blob: fb310d43e5d4c6781b82bfc17cb0f09b6ef1a308 [file] [log] [blame]
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +00001/*
2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/desktop_capture/desktop_region.h"
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000012
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000013#include <assert.h>
14
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000015#include <algorithm>
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000016
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000017namespace webrtc {
18
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000019DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right)
20 : left(left), right(right) {
21}
22
kjellander470dd372016-04-19 03:03:23 -070023DesktopRegion::Row::Row(const Row&) = default;
kwiberg4fb3d2b2016-04-22 04:59:31 -070024DesktopRegion::Row::Row(Row&&) = default;
kjellander470dd372016-04-19 03:03:23 -070025
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000026DesktopRegion::Row::Row(int32_t top, int32_t bottom)
27 : top(top), bottom(bottom) {
28}
29
pbos@webrtc.orge7242842013-07-31 15:32:43 +000030DesktopRegion::Row::~Row() {}
31
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000032DesktopRegion::DesktopRegion() {}
33
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000034DesktopRegion::DesktopRegion(const DesktopRect& rect) {
35 AddRect(rect);
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000036}
37
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000038DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) {
39 AddRects(rects, count);
40}
41
42DesktopRegion::DesktopRegion(const DesktopRegion& other) {
43 *this = other;
44}
45
46DesktopRegion::~DesktopRegion() {
47 Clear();
48}
49
50DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) {
sergeyu@chromium.orgd9c46582013-06-05 19:24:42 +000051 Clear();
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000052 rows_ = other.rows_;
53 for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
54 // Copy each row.
55 Row* row = it->second;
56 it->second = new Row(*row);
57 }
58 return *this;
59}
60
61bool DesktopRegion::Equals(const DesktopRegion& region) const {
62 // Iterate over rows of the tow regions and compare each row.
63 Rows::const_iterator it1 = rows_.begin();
64 Rows::const_iterator it2 = region.rows_.begin();
65 while (it1 != rows_.end()) {
66 if (it2 == region.rows_.end() ||
67 it1->first != it2->first ||
68 it1->second->top != it2->second->top ||
69 it1->second->bottom != it2->second->bottom ||
70 it1->second->spans != it2->second->spans) {
71 return false;
72 }
73 ++it1;
74 ++it2;
75 }
76 return it2 == region.rows_.end();
77}
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000078
79void DesktopRegion::Clear() {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000080 for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) {
81 delete row->second;
82 }
83 rows_.clear();
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +000084}
85
86void DesktopRegion::SetRect(const DesktopRect& rect) {
87 Clear();
88 AddRect(rect);
89}
90
91void DesktopRegion::AddRect(const DesktopRect& rect) {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +000092 if (rect.is_empty())
93 return;
94
95 // Top of the part of the |rect| that hasn't been inserted yet. Increased as
96 // we iterate over the rows until it reaches |rect.bottom()|.
97 int top = rect.top();
98
99 // Iterate over all rows that may intersect with |rect| and add new rows when
100 // necessary.
101 Rows::iterator row = rows_.upper_bound(top);
102 while (top < rect.bottom()) {
103 if (row == rows_.end() || top < row->second->top) {
104 // If |top| is above the top of the current |row| then add a new row above
105 // the current one.
106 int32_t bottom = rect.bottom();
107 if (row != rows_.end() && row->second->top < bottom)
108 bottom = row->second->top;
109 row = rows_.insert(
110 row, Rows::value_type(bottom, new Row(top, bottom)));
111 } else if (top > row->second->top) {
112 // If the |top| falls in the middle of the |row| then split |row| into
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000113 // two, at |top|, and leave |row| referring to the lower of the two,
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000114 // ready to insert a new span into.
115 assert(top <= row->second->bottom);
116 Rows::iterator new_row = rows_.insert(
117 row, Rows::value_type(top, new Row(row->second->top, top)));
118 row->second->top = top;
119 new_row->second->spans = row->second->spans;
120 }
121
122 if (rect.bottom() < row->second->bottom) {
123 // If the bottom of the |rect| falls in the middle of the |row| split
124 // |row| into two, at |top|, and leave |row| referring to the upper of
125 // the two, ready to insert a new span into.
126 Rows::iterator new_row = rows_.insert(
127 row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom())));
128 row->second->top = rect.bottom();
129 new_row->second->spans = row->second->spans;
130 row = new_row;
131 }
132
133 // Add a new span to the current row.
134 AddSpanToRow(row->second, rect.left(), rect.right());
135 top = row->second->bottom;
136
137 MergeWithPrecedingRow(row);
138
139 // Move to the next row.
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000140 ++row;
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000141 }
142
143 if (row != rows_.end())
144 MergeWithPrecedingRow(row);
145}
146
147void DesktopRegion::AddRects(const DesktopRect* rects, int count) {
148 for (int i = 0; i < count; ++i) {
149 AddRect(rects[i]);
150 }
151}
152
153void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) {
154 assert(row != rows_.end());
155
156 if (row != rows_.begin()) {
157 Rows::iterator previous_row = row;
158 previous_row--;
159
160 // If |row| and |previous_row| are next to each other and contain the same
161 // set of spans then they can be merged.
162 if (previous_row->second->bottom == row->second->top &&
163 previous_row->second->spans == row->second->spans) {
164 row->second->top = previous_row->second->top;
165 delete previous_row->second;
166 rows_.erase(previous_row);
167 }
168 }
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000169}
170
171void DesktopRegion::AddRegion(const DesktopRegion& region) {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000172 // TODO(sergeyu): This function is not optimized - potentially it can iterate
173 // over rows of the two regions similar to how it works in Intersect().
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000174 for (Iterator it(region); !it.IsAtEnd(); it.Advance()) {
175 AddRect(it.rect());
176 }
177}
178
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000179void DesktopRegion::Intersect(const DesktopRegion& region1,
180 const DesktopRegion& region2) {
181 Clear();
182
183 Rows::const_iterator it1 = region1.rows_.begin();
184 Rows::const_iterator end1 = region1.rows_.end();
185 Rows::const_iterator it2 = region2.rows_.begin();
186 Rows::const_iterator end2 = region2.rows_.end();
187 if (it1 == end1 || it2 == end2)
188 return;
189
190 while (it1 != end1 && it2 != end2) {
191 // Arrange for |it1| to always be the top-most of the rows.
192 if (it2->second->top < it1->second->top) {
193 std::swap(it1, it2);
194 std::swap(end1, end2);
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000195 }
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000196
197 // Skip |it1| if it doesn't intersect |it2| at all.
198 if (it1->second->bottom <= it2->second->top) {
199 ++it1;
200 continue;
201 }
202
203 // Top of the |it1| row is above the top of |it2|, so top of the
204 // intersection is always the top of |it2|.
205 int32_t top = it2->second->top;
206 int32_t bottom = std::min(it1->second->bottom, it2->second->bottom);
207
208 Rows::iterator new_row = rows_.insert(
209 rows_.end(), Rows::value_type(bottom, new Row(top, bottom)));
210 IntersectRows(it1->second->spans, it2->second->spans,
211 &new_row->second->spans);
212 if (new_row->second->spans.empty()) {
213 delete new_row->second;
214 rows_.erase(new_row);
sergeyu@chromium.org17758e92013-08-01 19:51:04 +0000215 } else {
216 MergeWithPrecedingRow(new_row);
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000217 }
218
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000219 // If |it1| was completely consumed, move to the next one.
220 if (it1->second->bottom == bottom)
221 ++it1;
222 // If |it2| was completely consumed, move to the next one.
223 if (it2->second->bottom == bottom)
224 ++it2;
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000225 }
226}
227
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000228// static
229void DesktopRegion::IntersectRows(const RowSpanSet& set1,
230 const RowSpanSet& set2,
231 RowSpanSet* output) {
232 RowSpanSet::const_iterator it1 = set1.begin();
233 RowSpanSet::const_iterator end1 = set1.end();
234 RowSpanSet::const_iterator it2 = set2.begin();
235 RowSpanSet::const_iterator end2 = set2.end();
236 assert(it1 != end1 && it2 != end2);
237
238 do {
239 // Arrange for |it1| to always be the left-most of the spans.
240 if (it2->left < it1->left) {
241 std::swap(it1, it2);
242 std::swap(end1, end2);
243 }
244
245 // Skip |it1| if it doesn't intersect |it2| at all.
246 if (it1->right <= it2->left) {
247 ++it1;
248 continue;
249 }
250
251 int32_t left = it2->left;
252 int32_t right = std::min(it1->right, it2->right);
253 assert(left < right);
254
255 output->push_back(RowSpan(left, right));
256
257 // If |it1| was completely consumed, move to the next one.
258 if (it1->right == right)
259 ++it1;
260 // If |it2| was completely consumed, move to the next one.
261 if (it2->right == right)
262 ++it2;
263 } while (it1 != end1 && it2 != end2);
264}
265
266void DesktopRegion::IntersectWith(const DesktopRegion& region) {
267 DesktopRegion old_region;
268 Swap(&old_region);
269 Intersect(old_region, region);
270}
271
272void DesktopRegion::IntersectWith(const DesktopRect& rect) {
273 DesktopRegion region;
274 region.AddRect(rect);
275 IntersectWith(region);
276}
277
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000278void DesktopRegion::Subtract(const DesktopRegion& region) {
279 if (region.rows_.empty())
280 return;
281
282 // |row_b| refers to the current row being subtracted.
283 Rows::const_iterator row_b = region.rows_.begin();
284
285 // Current vertical position at which subtraction is happening.
286 int top = row_b->second->top;
287
288 // |row_a| refers to the current row we are subtracting from. Skip all rows
289 // above |top|.
290 Rows::iterator row_a = rows_.upper_bound(top);
291
292 // Step through rows of the both regions subtracting content of |row_b| from
293 // |row_a|.
294 while (row_a != rows_.end() && row_b != region.rows_.end()) {
295 // Skip |row_a| if it doesn't intersect with the |row_b|.
296 if (row_a->second->bottom <= top) {
297 // Each output row is merged with previously-processed rows before further
298 // rows are processed.
299 MergeWithPrecedingRow(row_a);
300 ++row_a;
301 continue;
302 }
303
304 if (top > row_a->second->top) {
305 // If |top| falls in the middle of |row_a| then split |row_a| into two, at
306 // |top|, and leave |row_a| referring to the lower of the two, ready to
307 // subtract spans from.
308 assert(top <= row_a->second->bottom);
309 Rows::iterator new_row = rows_.insert(
310 row_a, Rows::value_type(top, new Row(row_a->second->top, top)));
311 row_a->second->top = top;
312 new_row->second->spans = row_a->second->spans;
313 } else if (top < row_a->second->top) {
314 // If the |top| is above |row_a| then skip the range between |top| and
315 // top of |row_a| because it's empty.
316 top = row_a->second->top;
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000317 if (top >= row_b->second->bottom) {
318 ++row_b;
319 if (row_b != region.rows_.end())
320 top = row_b->second->top;
321 continue;
322 }
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000323 }
324
325 if (row_b->second->bottom < row_a->second->bottom) {
326 // If the bottom of |row_b| falls in the middle of the |row_a| split
327 // |row_a| into two, at |top|, and leave |row_a| referring to the upper of
328 // the two, ready to subtract spans from.
329 int bottom = row_b->second->bottom;
330 Rows::iterator new_row =
331 rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom)));
332 row_a->second->top = bottom;
333 new_row->second->spans = row_a->second->spans;
334 row_a = new_row;
335 }
336
337 // At this point the vertical range covered by |row_a| lays within the
338 // range covered by |row_b|. Subtract |row_b| spans from |row_a|.
339 RowSpanSet new_spans;
340 SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans);
341 new_spans.swap(row_a->second->spans);
342 top = row_a->second->bottom;
343
344 if (top >= row_b->second->bottom) {
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000345 ++row_b;
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000346 if (row_b != region.rows_.end())
347 top = row_b->second->top;
348 }
349
350 // Check if the row is empty after subtraction and delete it. Otherwise move
351 // to the next one.
352 if (row_a->second->spans.empty()) {
353 Rows::iterator row_to_delete = row_a;
354 ++row_a;
355 delete row_to_delete->second;
356 rows_.erase(row_to_delete);
357 } else {
358 MergeWithPrecedingRow(row_a);
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000359 ++row_a;
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000360 }
361 }
362
363 if (row_a != rows_.end())
364 MergeWithPrecedingRow(row_a);
365}
366
367void DesktopRegion::Subtract(const DesktopRect& rect) {
368 DesktopRegion region;
369 region.AddRect(rect);
370 Subtract(region);
371}
372
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000373void DesktopRegion::Translate(int32_t dx, int32_t dy) {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000374 Rows new_rows;
375
376 for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) {
377 Row* row = it->second;
378
379 row->top += dy;
380 row->bottom += dy;
381
382 if (dx != 0) {
383 // Translate each span.
384 for (RowSpanSet::iterator span = row->spans.begin();
385 span != row->spans.end(); ++span) {
386 span->left += dx;
387 span->right += dx;
388 }
389 }
390
391 if (dy != 0)
392 new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row));
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000393 }
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000394
395 if (dy != 0)
396 new_rows.swap(rows_);
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000397}
398
399void DesktopRegion::Swap(DesktopRegion* region) {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000400 rows_.swap(region->rows_);
401}
402
403// static
404bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) {
405 return r.right < value;
406}
407
408// static
409bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) {
410 return r.left < value;
411}
412
413// static
414void DesktopRegion::AddSpanToRow(Row* row, int left, int right) {
415 // First check if the new span is located to the right of all existing spans.
416 // This is an optimization to avoid binary search in the case when rectangles
417 // are inserted sequentially from left to right.
418 if (row->spans.empty() || left > row->spans.back().right) {
419 row->spans.push_back(RowSpan(left, right));
420 return;
421 }
422
423 // Find the first span that ends at or after |left|.
424 RowSpanSet::iterator start =
425 std::lower_bound(row->spans.begin(), row->spans.end(), left,
426 CompareSpanRight);
427 assert(start < row->spans.end());
428
429 // Find the first span that starts after |right|.
430 RowSpanSet::iterator end =
431 std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft);
432 if (end == row->spans.begin()) {
433 // There are no overlaps. Just insert the new span at the beginning.
434 row->spans.insert(row->spans.begin(), RowSpan(left, right));
435 return;
436 }
437
438 // Move end to the left, so that it points the last span that ends at or
439 // before |right|.
440 end--;
441
442 // At this point [start, end] is the range of spans that intersect with the
443 // new one.
444 if (end < start) {
445 // There are no overlaps. Just insert the new span at the correct position.
446 row->spans.insert(start, RowSpan(left, right));
447 return;
448 }
449
450 left = std::min(left, start->left);
451 right = std::max(right, end->right);
452
453 // Replace range [start, end] with the new span.
454 *start = RowSpan(left, right);
455 ++start;
456 ++end;
457 if (start < end)
458 row->spans.erase(start, end);
459}
460
461// static
462bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) {
463 // Find the first span that starts at or after |span.left| and then check if
464 // it's the same span.
465 RowSpanSet::const_iterator it =
466 std::lower_bound(row.spans.begin(), row.spans.end(), span.left,
467 CompareSpanLeft);
468 return it != row.spans.end() && *it == span;
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000469}
470
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000471// static
472void DesktopRegion::SubtractRows(const RowSpanSet& set_a,
473 const RowSpanSet& set_b,
474 RowSpanSet* output) {
475 assert(!set_a.empty() && !set_b.empty());
476
477 RowSpanSet::const_iterator it_b = set_b.begin();
478
479 // Iterate over all spans in |set_a| adding parts of it that do not intersect
480 // with |set_b| to the |output|.
481 for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end();
482 ++it_a) {
483 // If there is no intersection then append the current span and continue.
484 if (it_b == set_b.end() || it_a->right < it_b->left) {
485 output->push_back(*it_a);
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000486 continue;
487 }
488
489 // Iterate over |set_b| spans that may intersect with |it_a|.
490 int pos = it_a->left;
491 while (it_b != set_b.end() && it_b->left < it_a->right) {
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000492 if (it_b->left > pos)
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000493 output->push_back(RowSpan(pos, it_b->left));
sergeyu@chromium.org2edb6422013-09-20 21:29:18 +0000494 if (it_b->right > pos) {
495 pos = it_b->right;
496 if (pos >= it_a->right)
497 break;
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000498 }
sergeyu@chromium.org6ab45b92013-09-13 19:53:16 +0000499 ++it_b;
500 }
501 if (pos < it_a->right)
502 output->push_back(RowSpan(pos, it_a->right));
503 }
504}
505
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000506DesktopRegion::Iterator::Iterator(const DesktopRegion& region)
507 : region_(region),
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000508 row_(region.rows_.begin()),
509 previous_row_(region.rows_.end()) {
510 if (!IsAtEnd()) {
511 assert(row_->second->spans.size() > 0);
512 row_span_ = row_->second->spans.begin();
513 UpdateCurrentRect();
514 }
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000515}
516
Sergey Ulanov098c1de2015-09-01 11:36:40 -0700517DesktopRegion::Iterator::~Iterator() {}
518
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000519bool DesktopRegion::Iterator::IsAtEnd() const {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000520 return row_ == region_.rows_.end();
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000521}
522
523void DesktopRegion::Iterator::Advance() {
sergeyu@chromium.org3ee13e42013-06-04 00:38:39 +0000524 assert(!IsAtEnd());
525
526 while (true) {
527 ++row_span_;
528 if (row_span_ == row_->second->spans.end()) {
529 previous_row_ = row_;
530 ++row_;
531 if (row_ != region_.rows_.end()) {
532 assert(row_->second->spans.size() > 0);
533 row_span_ = row_->second->spans.begin();
534 }
535 }
536
537 if (IsAtEnd())
538 return;
539
540 // If the same span exists on the previous row then skip it, as we've
541 // already returned this span merged into the previous one, via
542 // UpdateCurrentRect().
543 if (previous_row_ != region_.rows_.end() &&
544 previous_row_->second->bottom == row_->second->top &&
545 IsSpanInRow(*previous_row_->second, *row_span_)) {
546 continue;
547 }
548
549 break;
550 }
551
552 assert(!IsAtEnd());
553 UpdateCurrentRect();
554}
555
556void DesktopRegion::Iterator::UpdateCurrentRect() {
557 // Merge the current rectangle with the matching spans from later rows.
558 int bottom;
559 Rows::const_iterator bottom_row = row_;
560 Rows::const_iterator previous;
561 do {
562 bottom = bottom_row->second->bottom;
563 previous = bottom_row;
564 ++bottom_row;
565 } while (bottom_row != region_.rows_.end() &&
566 previous->second->bottom == bottom_row->second->top &&
567 IsSpanInRow(*bottom_row->second, *row_span_));
568 rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top,
569 row_span_->right, bottom);
sergeyu@chromium.org15e32cc2013-04-29 20:10:57 +0000570}
571
572} // namespace webrtc