desktop_region.cc (17532B)
1 /* 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 11 #include "modules/desktop_capture/desktop_region.h" 12 13 #include <algorithm> 14 #include <cstdint> 15 #include <utility> 16 17 #include "modules/desktop_capture/desktop_geometry.h" 18 #include "rtc_base/checks.h" 19 20 namespace webrtc { 21 22 DesktopRegion::RowSpan::RowSpan(int32_t left, int32_t right) 23 : left(left), right(right) {} 24 25 DesktopRegion::Row::Row(const Row&) = default; 26 DesktopRegion::Row::Row(Row&&) = default; 27 28 DesktopRegion::Row::Row(int32_t top, int32_t bottom) 29 : top(top), bottom(bottom) {} 30 31 DesktopRegion::Row::~Row() {} 32 33 DesktopRegion::DesktopRegion() {} 34 35 DesktopRegion::DesktopRegion(const DesktopRect& rect) { 36 AddRect(rect); 37 } 38 39 DesktopRegion::DesktopRegion(const DesktopRect* rects, int count) { 40 AddRects(rects, count); 41 } 42 43 DesktopRegion::DesktopRegion(const DesktopRegion& other) { 44 *this = other; 45 } 46 47 DesktopRegion::~DesktopRegion() { 48 Clear(); 49 } 50 51 DesktopRegion& DesktopRegion::operator=(const DesktopRegion& other) { 52 Clear(); 53 rows_ = other.rows_; 54 for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { 55 // Copy each row. 56 Row* row = it->second; 57 it->second = new Row(*row); 58 } 59 return *this; 60 } 61 62 bool DesktopRegion::Equals(const DesktopRegion& region) const { 63 // Iterate over rows of the tow regions and compare each row. 64 Rows::const_iterator it1 = rows_.begin(); 65 Rows::const_iterator it2 = region.rows_.begin(); 66 while (it1 != rows_.end()) { 67 if (it2 == region.rows_.end() || 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 } 78 79 void DesktopRegion::Clear() { 80 for (Rows::iterator row = rows_.begin(); row != rows_.end(); ++row) { 81 delete row->second; 82 } 83 rows_.clear(); 84 } 85 86 void DesktopRegion::SetRect(const DesktopRect& rect) { 87 Clear(); 88 AddRect(rect); 89 } 90 91 void DesktopRegion::AddRect(const DesktopRect& rect) { 92 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(row, Rows::value_type(bottom, new Row(top, bottom))); 110 } else if (top > row->second->top) { 111 // If the `top` falls in the middle of the `row` then split `row` into 112 // two, at `top`, and leave `row` referring to the lower of the two, 113 // ready to insert a new span into. 114 RTC_DCHECK_LE(top, row->second->bottom); 115 Rows::iterator new_row = rows_.insert( 116 row, Rows::value_type(top, new Row(row->second->top, top))); 117 row->second->top = top; 118 new_row->second->spans = row->second->spans; 119 } 120 121 if (rect.bottom() < row->second->bottom) { 122 // If the bottom of the `rect` falls in the middle of the `row` split 123 // `row` into two, at `top`, and leave `row` referring to the upper of 124 // the two, ready to insert a new span into. 125 Rows::iterator new_row = rows_.insert( 126 row, Rows::value_type(rect.bottom(), new Row(top, rect.bottom()))); 127 row->second->top = rect.bottom(); 128 new_row->second->spans = row->second->spans; 129 row = new_row; 130 } 131 132 // Add a new span to the current row. 133 AddSpanToRow(row->second, rect.left(), rect.right()); 134 top = row->second->bottom; 135 136 MergeWithPrecedingRow(row); 137 138 // Move to the next row. 139 ++row; 140 } 141 142 if (row != rows_.end()) 143 MergeWithPrecedingRow(row); 144 } 145 146 void DesktopRegion::AddRects(const DesktopRect* rects, int count) { 147 for (int i = 0; i < count; ++i) { 148 AddRect(rects[i]); 149 } 150 } 151 152 void DesktopRegion::MergeWithPrecedingRow(Rows::iterator row) { 153 RTC_DCHECK(row != rows_.end()); 154 155 if (row != rows_.begin()) { 156 Rows::iterator previous_row = row; 157 previous_row--; 158 159 // If `row` and `previous_row` are next to each other and contain the same 160 // set of spans then they can be merged. 161 if (previous_row->second->bottom == row->second->top && 162 previous_row->second->spans == row->second->spans) { 163 row->second->top = previous_row->second->top; 164 delete previous_row->second; 165 rows_.erase(previous_row); 166 } 167 } 168 } 169 170 void DesktopRegion::AddRegion(const DesktopRegion& region) { 171 // TODO(sergeyu): This function is not optimized - potentially it can iterate 172 // over rows of the two regions similar to how it works in Intersect(). 173 for (Iterator it(region); !it.IsAtEnd(); it.Advance()) { 174 AddRect(it.rect()); 175 } 176 } 177 178 void DesktopRegion::Intersect(const DesktopRegion& region1, 179 const DesktopRegion& region2) { 180 Clear(); 181 182 Rows::const_iterator it1 = region1.rows_.begin(); 183 Rows::const_iterator end1 = region1.rows_.end(); 184 Rows::const_iterator it2 = region2.rows_.begin(); 185 Rows::const_iterator end2 = region2.rows_.end(); 186 if (it1 == end1 || it2 == end2) 187 return; 188 189 while (it1 != end1 && it2 != end2) { 190 // Arrange for `it1` to always be the top-most of the rows. 191 if (it2->second->top < it1->second->top) { 192 std::swap(it1, it2); 193 std::swap(end1, end2); 194 } 195 196 // Skip `it1` if it doesn't intersect `it2` at all. 197 if (it1->second->bottom <= it2->second->top) { 198 ++it1; 199 continue; 200 } 201 202 // Top of the `it1` row is above the top of `it2`, so top of the 203 // intersection is always the top of `it2`. 204 int32_t top = it2->second->top; 205 int32_t bottom = std::min(it1->second->bottom, it2->second->bottom); 206 207 Rows::iterator new_row = rows_.insert( 208 rows_.end(), Rows::value_type(bottom, new Row(top, bottom))); 209 IntersectRows(it1->second->spans, it2->second->spans, 210 &new_row->second->spans); 211 if (new_row->second->spans.empty()) { 212 delete new_row->second; 213 rows_.erase(new_row); 214 } else { 215 MergeWithPrecedingRow(new_row); 216 } 217 218 // If `it1` was completely consumed, move to the next one. 219 if (it1->second->bottom == bottom) 220 ++it1; 221 // If `it2` was completely consumed, move to the next one. 222 if (it2->second->bottom == bottom) 223 ++it2; 224 } 225 } 226 227 // static 228 void DesktopRegion::IntersectRows(const RowSpanSet& set1, 229 const RowSpanSet& set2, 230 RowSpanSet* output) { 231 RowSpanSet::const_iterator it1 = set1.begin(); 232 RowSpanSet::const_iterator end1 = set1.end(); 233 RowSpanSet::const_iterator it2 = set2.begin(); 234 RowSpanSet::const_iterator end2 = set2.end(); 235 RTC_DCHECK(it1 != end1 && it2 != end2); 236 237 do { 238 // Arrange for `it1` to always be the left-most of the spans. 239 if (it2->left < it1->left) { 240 std::swap(it1, it2); 241 std::swap(end1, end2); 242 } 243 244 // Skip `it1` if it doesn't intersect `it2` at all. 245 if (it1->right <= it2->left) { 246 ++it1; 247 continue; 248 } 249 250 int32_t left = it2->left; 251 int32_t right = std::min(it1->right, it2->right); 252 RTC_DCHECK_LT(left, right); 253 254 output->push_back(RowSpan(left, right)); 255 256 // If `it1` was completely consumed, move to the next one. 257 if (it1->right == right) 258 ++it1; 259 // If `it2` was completely consumed, move to the next one. 260 if (it2->right == right) 261 ++it2; 262 } while (it1 != end1 && it2 != end2); 263 } 264 265 void DesktopRegion::IntersectWith(const DesktopRegion& region) { 266 DesktopRegion old_region; 267 Swap(&old_region); 268 Intersect(old_region, region); 269 } 270 271 void DesktopRegion::IntersectWith(const DesktopRect& rect) { 272 DesktopRegion region; 273 region.AddRect(rect); 274 IntersectWith(region); 275 } 276 277 void DesktopRegion::Subtract(const DesktopRegion& region) { 278 if (region.rows_.empty()) 279 return; 280 281 // `row_b` refers to the current row being subtracted. 282 Rows::const_iterator row_b = region.rows_.begin(); 283 284 // Current vertical position at which subtraction is happening. 285 int top = row_b->second->top; 286 287 // `row_a` refers to the current row we are subtracting from. Skip all rows 288 // above `top`. 289 Rows::iterator row_a = rows_.upper_bound(top); 290 291 // Step through rows of the both regions subtracting content of `row_b` from 292 // `row_a`. 293 while (row_a != rows_.end() && row_b != region.rows_.end()) { 294 // Skip `row_a` if it doesn't intersect with the `row_b`. 295 if (row_a->second->bottom <= top) { 296 // Each output row is merged with previously-processed rows before further 297 // rows are processed. 298 MergeWithPrecedingRow(row_a); 299 ++row_a; 300 continue; 301 } 302 303 if (top > row_a->second->top) { 304 // If `top` falls in the middle of `row_a` then split `row_a` into two, at 305 // `top`, and leave `row_a` referring to the lower of the two, ready to 306 // subtract spans from. 307 RTC_DCHECK_LE(top, row_a->second->bottom); 308 Rows::iterator new_row = rows_.insert( 309 row_a, Rows::value_type(top, new Row(row_a->second->top, top))); 310 row_a->second->top = top; 311 new_row->second->spans = row_a->second->spans; 312 } else if (top < row_a->second->top) { 313 // If the `top` is above `row_a` then skip the range between `top` and 314 // top of `row_a` because it's empty. 315 top = row_a->second->top; 316 if (top >= row_b->second->bottom) { 317 ++row_b; 318 if (row_b != region.rows_.end()) 319 top = row_b->second->top; 320 continue; 321 } 322 } 323 324 if (row_b->second->bottom < row_a->second->bottom) { 325 // If the bottom of `row_b` falls in the middle of the `row_a` split 326 // `row_a` into two, at `top`, and leave `row_a` referring to the upper of 327 // the two, ready to subtract spans from. 328 int bottom = row_b->second->bottom; 329 Rows::iterator new_row = 330 rows_.insert(row_a, Rows::value_type(bottom, new Row(top, bottom))); 331 row_a->second->top = bottom; 332 new_row->second->spans = row_a->second->spans; 333 row_a = new_row; 334 } 335 336 // At this point the vertical range covered by `row_a` lays within the 337 // range covered by `row_b`. Subtract `row_b` spans from `row_a`. 338 RowSpanSet new_spans; 339 SubtractRows(row_a->second->spans, row_b->second->spans, &new_spans); 340 new_spans.swap(row_a->second->spans); 341 top = row_a->second->bottom; 342 343 if (top >= row_b->second->bottom) { 344 ++row_b; 345 if (row_b != region.rows_.end()) 346 top = row_b->second->top; 347 } 348 349 // Check if the row is empty after subtraction and delete it. Otherwise move 350 // to the next one. 351 if (row_a->second->spans.empty()) { 352 Rows::iterator row_to_delete = row_a; 353 ++row_a; 354 delete row_to_delete->second; 355 rows_.erase(row_to_delete); 356 } else { 357 MergeWithPrecedingRow(row_a); 358 ++row_a; 359 } 360 } 361 362 if (row_a != rows_.end()) 363 MergeWithPrecedingRow(row_a); 364 } 365 366 void DesktopRegion::Subtract(const DesktopRect& rect) { 367 DesktopRegion region; 368 region.AddRect(rect); 369 Subtract(region); 370 } 371 372 void DesktopRegion::Translate(int32_t dx, int32_t dy) { 373 Rows new_rows; 374 375 for (Rows::iterator it = rows_.begin(); it != rows_.end(); ++it) { 376 Row* row = it->second; 377 378 row->top += dy; 379 row->bottom += dy; 380 381 if (dx != 0) { 382 // Translate each span. 383 for (RowSpanSet::iterator span = row->spans.begin(); 384 span != row->spans.end(); ++span) { 385 span->left += dx; 386 span->right += dx; 387 } 388 } 389 390 if (dy != 0) 391 new_rows.insert(new_rows.end(), Rows::value_type(row->bottom, row)); 392 } 393 394 if (dy != 0) 395 new_rows.swap(rows_); 396 } 397 398 void DesktopRegion::Swap(DesktopRegion* region) { 399 rows_.swap(region->rows_); 400 } 401 402 // static 403 bool DesktopRegion::CompareSpanRight(const RowSpan& r, int32_t value) { 404 return r.right < value; 405 } 406 407 // static 408 bool DesktopRegion::CompareSpanLeft(const RowSpan& r, int32_t value) { 409 return r.left < value; 410 } 411 412 // static 413 void DesktopRegion::AddSpanToRow(Row* row, int left, int right) { 414 // First check if the new span is located to the right of all existing spans. 415 // This is an optimization to avoid binary search in the case when rectangles 416 // are inserted sequentially from left to right. 417 if (row->spans.empty() || left > row->spans.back().right) { 418 row->spans.push_back(RowSpan(left, right)); 419 return; 420 } 421 422 // Find the first span that ends at or after `left`. 423 RowSpanSet::iterator start = std::lower_bound( 424 row->spans.begin(), row->spans.end(), left, CompareSpanRight); 425 RTC_DCHECK(start < row->spans.end()); 426 427 // Find the first span that starts after `right`. 428 RowSpanSet::iterator end = 429 std::lower_bound(start, row->spans.end(), right + 1, CompareSpanLeft); 430 if (end == row->spans.begin()) { 431 // There are no overlaps. Just insert the new span at the beginning. 432 row->spans.insert(row->spans.begin(), RowSpan(left, right)); 433 return; 434 } 435 436 // Move end to the left, so that it points the last span that ends at or 437 // before `right`. 438 end--; 439 440 // At this point [start, end] is the range of spans that intersect with the 441 // new one. 442 if (end < start) { 443 // There are no overlaps. Just insert the new span at the correct position. 444 row->spans.insert(start, RowSpan(left, right)); 445 return; 446 } 447 448 left = std::min(left, start->left); 449 right = std::max(right, end->right); 450 451 // Replace range [start, end] with the new span. 452 *start = RowSpan(left, right); 453 ++start; 454 ++end; 455 if (start < end) 456 row->spans.erase(start, end); 457 } 458 459 // static 460 bool DesktopRegion::IsSpanInRow(const Row& row, const RowSpan& span) { 461 // Find the first span that starts at or after `span.left` and then check if 462 // it's the same span. 463 RowSpanSet::const_iterator it = std::lower_bound( 464 row.spans.begin(), row.spans.end(), span.left, CompareSpanLeft); 465 return it != row.spans.end() && *it == span; 466 } 467 468 // static 469 void DesktopRegion::SubtractRows(const RowSpanSet& set_a, 470 const RowSpanSet& set_b, 471 RowSpanSet* output) { 472 RTC_DCHECK(!set_a.empty() && !set_b.empty()); 473 474 RowSpanSet::const_iterator it_b = set_b.begin(); 475 476 // Iterate over all spans in `set_a` adding parts of it that do not intersect 477 // with `set_b` to the `output`. 478 for (RowSpanSet::const_iterator it_a = set_a.begin(); it_a != set_a.end(); 479 ++it_a) { 480 // If there is no intersection then append the current span and continue. 481 if (it_b == set_b.end() || it_a->right < it_b->left) { 482 output->push_back(*it_a); 483 continue; 484 } 485 486 // Iterate over `set_b` spans that may intersect with `it_a`. 487 int pos = it_a->left; 488 while (it_b != set_b.end() && it_b->left < it_a->right) { 489 if (it_b->left > pos) 490 output->push_back(RowSpan(pos, it_b->left)); 491 if (it_b->right > pos) { 492 pos = it_b->right; 493 if (pos >= it_a->right) 494 break; 495 } 496 ++it_b; 497 } 498 if (pos < it_a->right) 499 output->push_back(RowSpan(pos, it_a->right)); 500 } 501 } 502 503 DesktopRegion::Iterator::Iterator(const DesktopRegion& region) 504 : region_(region), 505 row_(region.rows_.begin()), 506 previous_row_(region.rows_.end()) { 507 if (!IsAtEnd()) { 508 RTC_DCHECK_GT(row_->second->spans.size(), 0); 509 row_span_ = row_->second->spans.begin(); 510 UpdateCurrentRect(); 511 } 512 } 513 514 DesktopRegion::Iterator::~Iterator() {} 515 516 bool DesktopRegion::Iterator::IsAtEnd() const { 517 return row_ == region_.rows_.end(); 518 } 519 520 void DesktopRegion::Iterator::Advance() { 521 RTC_DCHECK(!IsAtEnd()); 522 523 while (true) { 524 ++row_span_; 525 if (row_span_ == row_->second->spans.end()) { 526 previous_row_ = row_; 527 ++row_; 528 if (row_ != region_.rows_.end()) { 529 RTC_DCHECK_GT(row_->second->spans.size(), 0); 530 row_span_ = row_->second->spans.begin(); 531 } 532 } 533 534 if (IsAtEnd()) 535 return; 536 537 // If the same span exists on the previous row then skip it, as we've 538 // already returned this span merged into the previous one, via 539 // UpdateCurrentRect(). 540 if (previous_row_ != region_.rows_.end() && 541 previous_row_->second->bottom == row_->second->top && 542 IsSpanInRow(*previous_row_->second, *row_span_)) { 543 continue; 544 } 545 546 break; 547 } 548 549 RTC_DCHECK(!IsAtEnd()); 550 UpdateCurrentRect(); 551 } 552 553 void DesktopRegion::Iterator::UpdateCurrentRect() { 554 // Merge the current rectangle with the matching spans from later rows. 555 int bottom; 556 Rows::const_iterator bottom_row = row_; 557 Rows::const_iterator previous; 558 do { 559 bottom = bottom_row->second->bottom; 560 previous = bottom_row; 561 ++bottom_row; 562 } while (bottom_row != region_.rows_.end() && 563 previous->second->bottom == bottom_row->second->top && 564 IsSpanInRow(*bottom_row->second, *row_span_)); 565 rect_ = DesktopRect::MakeLTRB(row_span_->left, row_->second->top, 566 row_span_->right, bottom); 567 } 568 569 } // namespace webrtc