nsRegion.cpp (32817B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsRegion.h" 8 #include "nsTArray.h" 9 #include "gfxUtils.h" 10 #include "gfx2DGlue.h" 11 #include "mozilla/ToString.h" 12 13 void nsRegion::AssertStateInternal() const { 14 bool failed = false; 15 // Verify consistent state inside the region. 16 int32_t lastY = INT32_MIN; 17 int32_t lowestX = INT32_MAX; 18 int32_t highestX = INT32_MIN; 19 for (auto iter = mBands.begin(); iter != mBands.end(); iter++) { 20 const Band& band = *iter; 21 if (band.bottom <= band.top) { 22 failed = true; 23 break; 24 } 25 if (band.top < lastY) { 26 failed = true; 27 break; 28 } 29 lastY = band.bottom; 30 31 lowestX = std::min(lowestX, band.mStrips.begin()->left); 32 highestX = std::max(highestX, band.mStrips.LastElement().right); 33 34 int32_t lastX = INT32_MIN; 35 if (iter != mBands.begin()) { 36 auto prev = iter; 37 prev--; 38 39 if (prev->bottom == iter->top) { 40 if (band.EqualStrips(*prev)) { 41 failed = true; 42 break; 43 } 44 } 45 } 46 for (const Strip& strip : band.mStrips) { 47 if (strip.right <= strip.left) { 48 failed = true; 49 break; 50 } 51 if (strip.left <= lastX) { 52 failed = true; 53 break; 54 } 55 lastX = strip.right; 56 } 57 if (failed) { 58 break; 59 } 60 } 61 62 if (!(mBounds.IsEqualEdges(CalculateBounds()))) { 63 failed = true; 64 } 65 66 if (failed) { 67 #ifdef DEBUG_REGIONS 68 if (mCurrentOpGenerator) { 69 mCurrentOpGenerator->OutputOp(); 70 } 71 #endif 72 MOZ_ASSERT(false); 73 } 74 } 75 76 bool nsRegion::Contains(const nsRegion& aRgn) const { 77 // XXX this could be made faster by iterating over 78 // both regions at the same time some how 79 for (auto iter = aRgn.RectIter(); !iter.Done(); iter.Next()) { 80 if (!Contains(iter.Get())) { 81 return false; 82 } 83 } 84 return true; 85 } 86 87 bool nsRegion::Intersects(const nsRectAbsolute& aRect) const { 88 if (mBands.IsEmpty()) { 89 return mBounds.Intersects(aRect); 90 } 91 92 if (!mBounds.Intersects(aRect)) { 93 return false; 94 } 95 96 Strip rectStrip(aRect.X(), aRect.XMost()); 97 98 auto iter = mBands.begin(); 99 while (iter != mBands.end()) { 100 if (iter->top >= aRect.YMost()) { 101 return false; 102 } 103 104 if (iter->bottom <= aRect.Y()) { 105 // This band is entirely before aRect, move on. 106 iter++; 107 continue; 108 } 109 110 if (!iter->Intersects(rectStrip)) { 111 // This band does not intersect aRect horizontally. Move on. 112 iter++; 113 continue; 114 } 115 116 // This band intersects with aRect. 117 return true; 118 } 119 120 return false; 121 } 122 123 void nsRegion::Inflate(const nsMargin& aMargin) { 124 nsRegion newRegion; 125 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 126 nsRectAbsolute rect = iter.GetAbsolute(); 127 rect.Inflate(aMargin); 128 newRegion.AddRect(rect); 129 } 130 131 *this = std::move(newRegion); 132 } 133 134 void nsRegion::SimplifyOutward(uint32_t aMaxRects) { 135 MOZ_ASSERT(aMaxRects >= 1, "Invalid max rect count"); 136 137 if (GetNumRects() <= aMaxRects) { 138 return; 139 } 140 141 // Try combining rects in horizontal bands into a single rect 142 // The goal here is to try to keep groups of rectangles that are vertically 143 // discontiguous as separate rectangles in the final region. This is 144 // simple and fast to implement and page contents tend to vary more 145 // vertically than horizontally (which is why our rectangles are stored 146 // sorted by y-coordinate, too). 147 // 148 // Note: if boxes share y1 because of the canonical representation they 149 // will share y2 150 151 size_t idx = 0; 152 153 while (idx < mBands.Length()) { 154 size_t oldIdx = idx; 155 mBands[idx].mStrips.begin()->right = 156 mBands[idx].mStrips.LastElement().right; 157 mBands[idx].mStrips.TruncateLength(1); 158 idx++; 159 160 // Merge any bands with the same bounds. 161 while (idx < mBands.Length() && 162 mBands[idx].mStrips.begin()->left == 163 mBands[oldIdx].mStrips.begin()->left && 164 mBands[idx].mStrips.LastElement().right == 165 mBands[oldIdx].mStrips.begin()->right) { 166 mBands[oldIdx].bottom = mBands[idx].bottom; 167 mBands.RemoveElementAt(idx); 168 } 169 } 170 171 AssertState(); 172 173 // mBands.size() is now equal to our rect count. 174 if (mBands.Length() > aMaxRects) { 175 *this = GetBounds(); 176 } 177 } 178 179 // compute the covered area difference between two rows. 180 // by iterating over both rows simultaneously and adding up 181 // the additional increase in area caused by extending each 182 // of the rectangles to the combined height of both rows 183 uint32_t nsRegion::ComputeMergedAreaIncrease(const Band& aTopBand, 184 const Band& aBottomBand) { 185 uint32_t totalArea = 0; 186 187 uint32_t topHeight = aBottomBand.top - aTopBand.top; 188 uint32_t bottomHeight = aBottomBand.bottom - aTopBand.bottom; 189 uint32_t currentStripBottom = 0; 190 191 // This could be done with slightly better worse case performance by merging 192 // these two for-loops, but this makes the code a lot easier to understand. 193 for (auto& strip : aTopBand.mStrips) { 194 if (currentStripBottom == aBottomBand.mStrips.Length() || 195 strip.right < aBottomBand.mStrips[currentStripBottom].left) { 196 totalArea += bottomHeight * strip.Size(); 197 continue; 198 } 199 200 int32_t currentX = strip.left; 201 while (currentStripBottom != aBottomBand.mStrips.Length() && 202 aBottomBand.mStrips[currentStripBottom].left < strip.right) { 203 if (currentX >= strip.right) { 204 break; 205 } 206 if (currentX < aBottomBand.mStrips[currentStripBottom].left) { 207 // Add the part that's not intersecting. 208 totalArea += (aBottomBand.mStrips[currentStripBottom].left - currentX) * 209 bottomHeight; 210 } 211 212 currentX = 213 std::max(aBottomBand.mStrips[currentStripBottom].right, currentX); 214 currentStripBottom++; 215 } 216 217 // Add remainder of this strip. 218 if (currentX < strip.right) { 219 totalArea += (strip.right - currentX) * bottomHeight; 220 } 221 if (currentStripBottom) { 222 currentStripBottom--; 223 } 224 } 225 uint32_t currentStripTop = 0; 226 for (auto& strip : aBottomBand.mStrips) { 227 if (currentStripTop == aTopBand.mStrips.Length() || 228 strip.right < aTopBand.mStrips[currentStripTop].left) { 229 totalArea += topHeight * strip.Size(); 230 continue; 231 } 232 233 int32_t currentX = strip.left; 234 while (currentStripTop != aTopBand.mStrips.Length() && 235 aTopBand.mStrips[currentStripTop].left < strip.right) { 236 if (currentX >= strip.right) { 237 break; 238 } 239 if (currentX < aTopBand.mStrips[currentStripTop].left) { 240 // Add the part that's not intersecting. 241 totalArea += 242 (aTopBand.mStrips[currentStripTop].left - currentX) * topHeight; 243 } 244 245 currentX = std::max(aTopBand.mStrips[currentStripTop].right, currentX); 246 currentStripTop++; 247 } 248 249 // Add remainder of this strip. 250 if (currentX < strip.right) { 251 totalArea += (strip.right - currentX) * topHeight; 252 } 253 if (currentStripTop) { 254 currentStripTop--; 255 } 256 } 257 return totalArea; 258 } 259 260 void nsRegion::SimplifyOutwardByArea(uint32_t aThreshold) { 261 if (mBands.Length() < 2) { 262 // We have only one or no row and we're done. 263 return; 264 } 265 266 uint32_t currentBand = 0; 267 do { 268 Band& band = mBands[currentBand]; 269 270 uint32_t totalArea = 271 ComputeMergedAreaIncrease(band, mBands[currentBand + 1]); 272 273 if (totalArea <= aThreshold) { 274 for (Strip& strip : mBands[currentBand + 1].mStrips) { 275 // This could use an optimized function to merge two bands. 276 band.InsertStrip(strip); 277 } 278 band.bottom = mBands[currentBand + 1].bottom; 279 mBands.RemoveElementAt(currentBand + 1); 280 } else { 281 currentBand++; 282 } 283 } while (currentBand + 1 < mBands.Length()); 284 285 EnsureSimplified(); 286 AssertState(); 287 } 288 289 typedef void (*visit_fn)(void* closure, VisitSide side, int x1, int y1, int x2, 290 int y2); 291 292 void nsRegion::VisitEdges(visit_fn visit, void* closure) const { 293 if (mBands.IsEmpty()) { 294 visit(closure, VisitSide::LEFT, mBounds.X(), mBounds.Y(), mBounds.X(), 295 mBounds.YMost()); 296 visit(closure, VisitSide::RIGHT, mBounds.XMost(), mBounds.Y(), 297 mBounds.XMost(), mBounds.YMost()); 298 visit(closure, VisitSide::TOP, mBounds.X() - 1, mBounds.Y(), 299 mBounds.XMost() + 1, mBounds.Y()); 300 visit(closure, VisitSide::BOTTOM, mBounds.X() - 1, mBounds.YMost(), 301 mBounds.XMost() + 1, mBounds.YMost()); 302 return; 303 } 304 305 auto band = std::begin(mBands); 306 auto bandFinal = std::end(mBands); 307 bandFinal--; 308 for (const Strip& strip : band->mStrips) { 309 visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, 310 band->bottom); 311 visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, 312 band->bottom); 313 visit(closure, VisitSide::TOP, strip.left - 1, band->top, strip.right + 1, 314 band->top); 315 } 316 317 if (band != bandFinal) { 318 do { 319 const Band& topBand = *band; 320 band++; 321 322 for (const Strip& strip : band->mStrips) { 323 visit(closure, VisitSide::LEFT, strip.left, band->top, strip.left, 324 band->bottom); 325 visit(closure, VisitSide::RIGHT, strip.right, band->top, strip.right, 326 band->bottom); 327 } 328 329 if (band->top == topBand.bottom) { 330 // Two bands touching each other vertically. 331 const Band& bottomBand = *band; 332 auto topStrip = std::begin(topBand.mStrips); 333 auto bottomStrip = std::begin(bottomBand.mStrips); 334 335 int y = topBand.bottom; 336 337 // State from this point on along the vertical edge: 338 // 0 - Empty 339 // 1 - Touched by top rect 340 // 2 - Touched by bottom rect 341 // 3 - Touched on both sides 342 int state; 343 const int TouchedByNothing = 0; 344 const int TouchedByTop = 1; 345 const int TouchedByBottom = 2; 346 // We always start with nothing. 347 int oldState = TouchedByNothing; 348 // Last state change, adjusted by -1 if the last state change was 349 // a change away from 0. 350 int lastX = std::min(topStrip->left, bottomStrip->left) - 1; 351 352 // Current edge being considered for top and bottom, 353 // 0 - left, 1 - right. 354 bool topEdgeIsLeft = true; 355 bool bottomEdgeIsLeft = true; 356 while (topStrip != std::end(topBand.mStrips) && 357 bottomStrip != std::end(bottomBand.mStrips)) { 358 int topPos; 359 int bottomPos; 360 if (topEdgeIsLeft) { 361 topPos = topStrip->left; 362 } else { 363 topPos = topStrip->right; 364 } 365 if (bottomEdgeIsLeft) { 366 bottomPos = bottomStrip->left; 367 } else { 368 bottomPos = bottomStrip->right; 369 } 370 371 int currentX = std::min(topPos, bottomPos); 372 if (topPos < bottomPos) { 373 if (topEdgeIsLeft) { 374 state = oldState | TouchedByTop; 375 } else { 376 state = oldState ^ TouchedByTop; 377 topStrip++; 378 } 379 topEdgeIsLeft = !topEdgeIsLeft; 380 } else if (bottomPos < topPos) { 381 if (bottomEdgeIsLeft) { 382 state = oldState | TouchedByBottom; 383 } else { 384 state = oldState ^ TouchedByBottom; 385 bottomStrip++; 386 } 387 bottomEdgeIsLeft = !bottomEdgeIsLeft; 388 } else { 389 // bottomPos == topPos 390 state = TouchedByNothing; 391 if (bottomEdgeIsLeft) { 392 state = TouchedByBottom; 393 } else { 394 bottomStrip++; 395 } 396 if (topEdgeIsLeft) { 397 state |= TouchedByTop; 398 } else { 399 topStrip++; 400 } 401 topEdgeIsLeft = !topEdgeIsLeft; 402 bottomEdgeIsLeft = !bottomEdgeIsLeft; 403 } 404 405 MOZ_ASSERT(state != oldState); 406 if (oldState == TouchedByNothing) { 407 // We had nothing before, make sure the left edge will be padded. 408 lastX = currentX - 1; 409 } else if (oldState == TouchedByTop) { 410 if (state == TouchedByNothing) { 411 visit(closure, VisitSide::BOTTOM, lastX, y, currentX + 1, y); 412 } else { 413 visit(closure, VisitSide::BOTTOM, lastX, y, currentX, y); 414 lastX = currentX; 415 } 416 } else if (oldState == TouchedByBottom) { 417 if (state == TouchedByNothing) { 418 visit(closure, VisitSide::TOP, lastX, y, currentX + 1, y); 419 } else { 420 visit(closure, VisitSide::TOP, lastX, y, currentX, y); 421 lastX = currentX; 422 } 423 } else { 424 lastX = currentX; 425 } 426 oldState = state; 427 } 428 429 MOZ_ASSERT(!state || (topEdgeIsLeft || bottomEdgeIsLeft)); 430 if (topStrip != std::end(topBand.mStrips)) { 431 if (!topEdgeIsLeft) { 432 visit(closure, VisitSide::BOTTOM, lastX, y, topStrip->right + 1, y); 433 topStrip++; 434 } 435 while (topStrip != std::end(topBand.mStrips)) { 436 visit(closure, VisitSide::BOTTOM, topStrip->left - 1, y, 437 topStrip->right + 1, y); 438 topStrip++; 439 } 440 } else if (bottomStrip != std::end(bottomBand.mStrips)) { 441 if (!bottomEdgeIsLeft) { 442 visit(closure, VisitSide::TOP, lastX, y, bottomStrip->right + 1, y); 443 bottomStrip++; 444 } 445 while (bottomStrip != std::end(bottomBand.mStrips)) { 446 visit(closure, VisitSide::TOP, bottomStrip->left - 1, y, 447 bottomStrip->right + 1, y); 448 bottomStrip++; 449 } 450 } 451 } else { 452 for (const Strip& strip : topBand.mStrips) { 453 visit(closure, VisitSide::BOTTOM, strip.left - 1, topBand.bottom, 454 strip.right + 1, topBand.bottom); 455 } 456 for (const Strip& strip : band->mStrips) { 457 visit(closure, VisitSide::TOP, strip.left - 1, band->top, 458 strip.right + 1, band->top); 459 } 460 } 461 } while (band != bandFinal); 462 } 463 464 for (const Strip& strip : band->mStrips) { 465 visit(closure, VisitSide::BOTTOM, strip.left - 1, band->bottom, 466 strip.right + 1, band->bottom); 467 } 468 } 469 470 void nsRegion::SimplifyInward(uint32_t aMaxRects) { 471 NS_ASSERTION(aMaxRects >= 1, "Invalid max rect count"); 472 473 if (GetNumRects() <= aMaxRects) return; 474 475 SetEmpty(); 476 } 477 478 uint64_t nsRegion::Area() const { 479 if (mBands.IsEmpty()) { 480 return mBounds.Area(); 481 } 482 483 uint64_t area = 0; 484 for (const Band& band : mBands) { 485 uint32_t height = band.bottom - band.top; 486 for (const Strip& strip : band.mStrips) { 487 area += (strip.right - strip.left) * height; 488 } 489 } 490 491 return area; 492 } 493 494 nsRegion& nsRegion::ScaleRoundOut(float aXScale, float aYScale) { 495 if (mozilla::gfx::FuzzyEqual(aXScale, 1.0f) && 496 mozilla::gfx::FuzzyEqual(aYScale, 1.0f)) { 497 return *this; 498 } 499 500 nsRegion newRegion; 501 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 502 nsRectAbsolute rect = iter.GetAbsolute(); 503 rect.ScaleRoundOut(aXScale, aYScale); 504 newRegion.AddRect(rect); 505 } 506 507 *this = std::move(newRegion); 508 return *this; 509 } 510 511 nsRegion& nsRegion::ScaleInverseRoundOut(float aXScale, float aYScale) { 512 nsRegion newRegion; 513 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 514 nsRectAbsolute rect = iter.GetAbsolute(); 515 rect.ScaleInverseRoundOut(aXScale, aYScale); 516 newRegion.AddRect(rect); 517 } 518 519 *this = std::move(newRegion); 520 return *this; 521 } 522 523 static mozilla::gfx::IntRect TransformRect( 524 const mozilla::gfx::IntRect& aRect, 525 const mozilla::gfx::Matrix4x4& aTransform) { 526 if (aRect.IsEmpty()) { 527 return mozilla::gfx::IntRect(); 528 } 529 530 mozilla::gfx::RectDouble rect(aRect.X(), aRect.Y(), aRect.Width(), 531 aRect.Height()); 532 rect = aTransform.TransformAndClipBounds( 533 rect, mozilla::gfx::RectDouble::MaxIntRect()); 534 rect.RoundOut(); 535 536 mozilla::gfx::IntRect intRect; 537 if (!gfxUtils::GfxRectToIntRect(ThebesRect(rect), &intRect)) { 538 return mozilla::gfx::IntRect(); 539 } 540 541 return intRect; 542 } 543 544 nsRegion& nsRegion::Transform(const mozilla::gfx::Matrix4x4& aTransform) { 545 nsRegion newRegion; 546 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 547 nsRect rect = nsIntRegion::ToRect( 548 TransformRect(nsIntRegion::FromRect(iter.Get()), aTransform)); 549 newRegion.AddRect(nsRectAbsolute::FromRect(rect)); 550 } 551 552 *this = std::move(newRegion); 553 return *this; 554 } 555 556 nsRegion nsRegion::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, 557 int32_t aToAPP) const { 558 if (aFromAPP == aToAPP) { 559 return *this; 560 } 561 nsRegion newRegion; 562 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 563 nsRect rect = iter.Get(); 564 rect = rect.ScaleToOtherAppUnitsRoundOut(aFromAPP, aToAPP); 565 newRegion.AddRect(nsRectAbsolute::FromRect(rect)); 566 } 567 568 return newRegion; 569 } 570 571 nsRegion nsRegion::ScaleToOtherAppUnitsRoundIn(int32_t aFromAPP, 572 int32_t aToAPP) const { 573 if (aFromAPP == aToAPP) { 574 return *this; 575 } 576 577 nsRegion newRegion; 578 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 579 nsRect rect = iter.Get(); 580 rect = rect.ScaleToOtherAppUnitsRoundIn(aFromAPP, aToAPP); 581 newRegion.AddRect(nsRectAbsolute::FromRect(rect)); 582 } 583 584 return newRegion; 585 } 586 587 nsIntRegion nsRegion::ToPixels(nscoord aAppUnitsPerPixel, 588 bool aOutsidePixels) const { 589 nsIntRegion intRegion; 590 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 591 mozilla::gfx::IntRect deviceRect; 592 nsRect rect = iter.Get(); 593 if (aOutsidePixels) 594 deviceRect = rect.ToOutsidePixels(aAppUnitsPerPixel); 595 else 596 deviceRect = rect.ToNearestPixels(aAppUnitsPerPixel); 597 intRegion.OrWith(deviceRect); 598 } 599 600 return intRegion; 601 } 602 603 nsIntRegion nsRegion::ToOutsidePixels(nscoord aAppUnitsPerPixel) const { 604 return ToPixels(aAppUnitsPerPixel, true); 605 } 606 607 nsIntRegion nsRegion::ToNearestPixels(nscoord aAppUnitsPerPixel) const { 608 return ToPixels(aAppUnitsPerPixel, false); 609 } 610 611 nsIntRegion nsRegion::ScaleToNearestPixels(float aScaleX, float aScaleY, 612 nscoord aAppUnitsPerPixel) const { 613 nsIntRegion result; 614 for (auto iter = RectIter(); !iter.Done(); iter.Next()) { 615 mozilla::gfx::IntRect deviceRect = 616 iter.Get().ScaleToNearestPixels(aScaleX, aScaleY, aAppUnitsPerPixel); 617 result.Or(result, deviceRect); 618 } 619 return result; 620 } 621 622 nsIntRegion nsRegion::ScaleToOutsidePixels(float aScaleX, float aScaleY, 623 nscoord aAppUnitsPerPixel) const { 624 // make a copy of the region so that we can mutate it inplace 625 nsIntRegion intRegion; 626 for (RectIterator iter = RectIterator(*this); !iter.Done(); iter.Next()) { 627 nsRect rect = iter.Get(); 628 intRegion.OrWith( 629 rect.ScaleToOutsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel)); 630 } 631 return intRegion; 632 } 633 634 nsIntRegion nsRegion::ScaleToInsidePixels(float aScaleX, float aScaleY, 635 nscoord aAppUnitsPerPixel) const { 636 /* When scaling a rect, walk forward through the rect list up until the y 637 * value is greater than the current rect's YMost() value. 638 * 639 * For each rect found, check if the rects have a touching edge (in unscaled 640 * coordinates), and if one edge is entirely contained within the other. 641 * 642 * If it is, then the contained edge can be moved (in scaled pixels) to ensure 643 * that no gap exists. 644 * 645 * Since this could be potentially expensive - O(n^2), we only attempt this 646 * algorithm for the first rect. 647 */ 648 649 if (mBands.IsEmpty()) { 650 nsIntRect rect = mBounds.ToNSRect().ScaleToInsidePixels(aScaleX, aScaleY, 651 aAppUnitsPerPixel); 652 return nsIntRegion(rect); 653 } 654 655 nsIntRegion intRegion; 656 RectIterator iter = RectIterator(*this); 657 658 nsRect first = iter.Get(); 659 660 mozilla::gfx::IntRect firstDeviceRect = 661 first.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); 662 663 for (iter.Next(); !iter.Done(); iter.Next()) { 664 nsRect rect = iter.Get(); 665 mozilla::gfx::IntRect deviceRect = 666 rect.ScaleToInsidePixels(aScaleX, aScaleY, aAppUnitsPerPixel); 667 668 if (rect.Y() <= first.YMost()) { 669 if (rect.XMost() == first.X() && rect.YMost() <= first.YMost()) { 670 // rect is touching on the left edge of the first rect and contained 671 // within the length of its left edge 672 deviceRect.SetRightEdge(firstDeviceRect.X()); 673 } else if (rect.X() == first.XMost() && rect.YMost() <= first.YMost()) { 674 // rect is touching on the right edge of the first rect and contained 675 // within the length of its right edge 676 deviceRect.SetLeftEdge(firstDeviceRect.XMost()); 677 } else if (rect.Y() == first.YMost()) { 678 // The bottom of the first rect is on the same line as the top of rect, 679 // but they aren't necessarily contained. 680 if (rect.X() <= first.X() && rect.XMost() >= first.XMost()) { 681 // The top of rect contains the bottom of the first rect 682 firstDeviceRect.SetBottomEdge(deviceRect.Y()); 683 } else if (rect.X() >= first.X() && rect.XMost() <= first.XMost()) { 684 // The bottom of the first contains the top of rect 685 deviceRect.SetTopEdge(firstDeviceRect.YMost()); 686 } 687 } 688 } 689 690 intRegion.OrWith(deviceRect); 691 } 692 693 intRegion.OrWith(firstDeviceRect); 694 return intRegion; 695 } 696 697 // A cell's "value" is a pair consisting of 698 // a) the area of the subrectangle it corresponds to, if it's in 699 // aContainingRect and in the region, 0 otherwise 700 // b) the area of the subrectangle it corresponds to, if it's in the region, 701 // 0 otherwise 702 // Addition, subtraction and identity are defined on these values in the 703 // obvious way. Partial order is lexicographic. 704 // A "large negative value" is defined with large negative numbers for both 705 // fields of the pair. This negative value has the property that adding any 706 // number of non-negative values to it always results in a negative value. 707 // 708 // The GetLargestRectangle algorithm works in three phases: 709 // 1) Convert the region into a grid by adding vertical/horizontal lines for 710 // each edge of each rectangle in the region. 711 // 2) For each rectangle in the region, for each cell it contains, set that 712 // cells's value as described above. 713 // 3) Calculate the submatrix with the largest sum such that none of its cells 714 // contain any 0s (empty regions). The rectangle represented by the 715 // submatrix is the largest rectangle in the region. 716 // 717 // Let k be the number of rectangles in the region. 718 // Let m be the height of the grid generated in step 1. 719 // Let n be the width of the grid generated in step 1. 720 // 721 // Step 1 is O(k) in time and O(m+n) in space for the sparse grid. 722 // Step 2 is O(mn) in time and O(mn) in additional space for the full grid. 723 // Step 3 is O(m^2 n) in time and O(mn) in additional space 724 // 725 // The implementation of steps 1 and 2 are rather straightforward. However our 726 // implementation of step 3 uses dynamic programming to achieve its efficiency. 727 // 728 // Psuedo code for step 3 is as follows where G is the grid from step 1 and A 729 // is the array from step 2: 730 // Phase3 = function (G, A, m, n) { 731 // let (t,b,l,r,_) = MaxSum2D(A,m,n) 732 // return rect(G[t],G[l],G[r],G[b]); 733 // } 734 // MaxSum2D = function (A, m, n) { 735 // S = array(m+1,n+1) 736 // S[0][i] = 0 for i in [0,n] 737 // S[j][0] = 0 for j in [0,m] 738 // S[j][i] = (if A[j-1][i-1] = 0 then some large negative value 739 // else A[j-1][i-1]) 740 // + S[j-1][n] + S[j][i-1] - S[j-1][i-1] 741 // 742 // // top, bottom, left, right, area 743 // var maxRect = (-1, -1, -1, -1, 0); 744 // 745 // for all (m',m'') in [0, m]^2 { 746 // let B = { S[m'][i] - S[m''][i] | 0 <= i <= n } 747 // let ((l,r),area) = MaxSum1D(B,n+1) 748 // if (area > maxRect.area) { 749 // maxRect := (m', m'', l, r, area) 750 // } 751 // } 752 // 753 // return maxRect; 754 // } 755 // 756 // Originally taken from Improved algorithms for the k-maximum subarray problem 757 // for small k - SE Bae, T Takaoka but modified to show the explicit tracking 758 // of indices and we already have the prefix sums from our one call site so 759 // there's no need to construct them. 760 // MaxSum1D = function (A,n) { 761 // var minIdx = 0; 762 // var min = 0; 763 // var maxIndices = (0,0); 764 // var max = 0; 765 // for i in range(n) { 766 // let cand = A[i] - min; 767 // if (cand > max) { 768 // max := cand; 769 // maxIndices := (minIdx, i) 770 // } 771 // if (min > A[i]) { 772 // min := A[i]; 773 // minIdx := i; 774 // } 775 // } 776 // return (minIdx, maxIdx, max); 777 // } 778 779 namespace { 780 // This class represents a partitioning of an axis delineated by coordinates. 781 // It internally maintains a sorted array of coordinates. 782 class AxisPartition { 783 public: 784 // Adds a new partition at the given coordinate to this partitioning. If 785 // the coordinate is already present in the partitioning, this does nothing. 786 void InsertCoord(nscoord c) { 787 uint32_t i = mStops.IndexOfFirstElementGt(c); 788 if (i == 0 || mStops[i - 1] != c) { 789 mStops.InsertElementAt(i, c); 790 } 791 } 792 793 // Returns the array index of the given partition point. The partition 794 // point must already be present in the partitioning. 795 int32_t IndexOf(nscoord p) const { return mStops.BinaryIndexOf(p); } 796 797 // Returns the partition at the given index which must be non-zero and 798 // less than the number of partitions in this partitioning. 799 nscoord StopAt(int32_t index) const { return mStops[index]; } 800 801 // Returns the size of the gap between the partition at the given index and 802 // the next partition in this partitioning. If the index is the last index 803 // in the partitioning, the result is undefined. 804 nscoord StopSize(int32_t index) const { 805 return mStops[index + 1] - mStops[index]; 806 } 807 808 // Returns the number of partitions in this partitioning. 809 int32_t GetNumStops() const { return mStops.Length(); } 810 811 private: 812 nsTArray<nscoord> mStops; 813 }; 814 815 const int64_t kVeryLargeNegativeNumber = 0xffff000000000000ll; 816 817 struct SizePair { 818 int64_t mSizeContainingRect; 819 int64_t mSize; 820 821 SizePair() : mSizeContainingRect(0), mSize(0) {} 822 823 static SizePair VeryLargeNegative() { 824 SizePair result; 825 result.mSize = result.mSizeContainingRect = kVeryLargeNegativeNumber; 826 return result; 827 } 828 bool operator<(const SizePair& aOther) const { 829 if (mSizeContainingRect < aOther.mSizeContainingRect) return true; 830 if (mSizeContainingRect > aOther.mSizeContainingRect) return false; 831 return mSize < aOther.mSize; 832 } 833 bool operator>(const SizePair& aOther) const { 834 return aOther.operator<(*this); 835 } 836 SizePair operator+(const SizePair& aOther) const { 837 SizePair result = *this; 838 result.mSizeContainingRect += aOther.mSizeContainingRect; 839 result.mSize += aOther.mSize; 840 return result; 841 } 842 SizePair operator-(const SizePair& aOther) const { 843 SizePair result = *this; 844 result.mSizeContainingRect -= aOther.mSizeContainingRect; 845 result.mSize -= aOther.mSize; 846 return result; 847 } 848 }; 849 850 // Returns the sum and indices of the subarray with the maximum sum of the 851 // given array (A,n), assuming the array is already in prefix sum form. 852 SizePair MaxSum1D(const nsTArray<SizePair>& A, int32_t n, int32_t* minIdx, 853 int32_t* maxIdx) { 854 // The min/max indicies of the largest subarray found so far 855 SizePair min, max; 856 int32_t currentMinIdx = 0; 857 858 *minIdx = 0; 859 *maxIdx = 0; 860 861 // Because we're given the array in prefix sum form, we know the first 862 // element is 0 863 for (int32_t i = 1; i < n; i++) { 864 SizePair cand = A[i] - min; 865 if (cand > max) { 866 max = cand; 867 *minIdx = currentMinIdx; 868 *maxIdx = i; 869 } 870 if (min > A[i]) { 871 min = A[i]; 872 currentMinIdx = i; 873 } 874 } 875 876 return max; 877 } 878 } // namespace 879 880 nsRect nsRegion::GetLargestRectangle(const nsRect& aContainingRect) const { 881 nsRect bestRect; 882 883 if (GetNumRects() <= 1) { 884 bestRect = GetBounds(); 885 return bestRect; 886 } 887 888 AxisPartition xaxis, yaxis; 889 890 // Step 1: Calculate the grid lines 891 for (auto iter = RectIter(); !iter.Done(); iter.Next()) { 892 const nsRect& rect = iter.Get(); 893 xaxis.InsertCoord(rect.X()); 894 xaxis.InsertCoord(rect.XMost()); 895 yaxis.InsertCoord(rect.Y()); 896 yaxis.InsertCoord(rect.YMost()); 897 } 898 if (!aContainingRect.IsEmpty()) { 899 xaxis.InsertCoord(aContainingRect.X()); 900 xaxis.InsertCoord(aContainingRect.XMost()); 901 yaxis.InsertCoord(aContainingRect.Y()); 902 yaxis.InsertCoord(aContainingRect.YMost()); 903 } 904 905 // Step 2: Fill out the grid with the areas 906 // Note: due to the ordering of rectangles in the region, it is not always 907 // possible to combine steps 2 and 3 so we don't try to be clever. 908 int32_t matrixHeight = yaxis.GetNumStops() - 1; 909 int32_t matrixWidth = xaxis.GetNumStops() - 1; 910 int32_t matrixSize = matrixHeight * matrixWidth; 911 nsTArray<SizePair> areas(matrixSize); 912 areas.SetLength(matrixSize); 913 914 for (auto iter = RectIter(); !iter.Done(); iter.Next()) { 915 const nsRect& rect = iter.Get(); 916 int32_t xstart = xaxis.IndexOf(rect.X()); 917 int32_t xend = xaxis.IndexOf(rect.XMost()); 918 int32_t y = yaxis.IndexOf(rect.Y()); 919 int32_t yend = yaxis.IndexOf(rect.YMost()); 920 921 for (; y < yend; y++) { 922 nscoord height = yaxis.StopSize(y); 923 for (int32_t x = xstart; x < xend; x++) { 924 nscoord width = xaxis.StopSize(x); 925 int64_t size = width * int64_t(height); 926 if (rect.Intersects(aContainingRect)) { 927 areas[y * matrixWidth + x].mSizeContainingRect = size; 928 } 929 areas[y * matrixWidth + x].mSize = size; 930 } 931 } 932 } 933 934 // Step 3: Find the maximum submatrix sum that does not contain a rectangle 935 { 936 // First get the prefix sum array 937 int32_t m = matrixHeight + 1; 938 int32_t n = matrixWidth + 1; 939 nsTArray<SizePair> pareas(m * n); 940 pareas.SetLength(m * n); 941 for (int32_t y = 1; y < m; y++) { 942 for (int32_t x = 1; x < n; x++) { 943 SizePair area = areas[(y - 1) * matrixWidth + x - 1]; 944 if (!area.mSize) { 945 area = SizePair::VeryLargeNegative(); 946 } 947 area = area + pareas[y * n + x - 1] + pareas[(y - 1) * n + x] - 948 pareas[(y - 1) * n + x - 1]; 949 pareas[y * n + x] = area; 950 } 951 } 952 953 // No longer need the grid 954 areas.SetLength(0); 955 956 SizePair bestArea; 957 struct { 958 int32_t left, top, right, bottom; 959 } bestRectIndices = {0, 0, 0, 0}; 960 for (int32_t m1 = 0; m1 < m; m1++) { 961 for (int32_t m2 = m1 + 1; m2 < m; m2++) { 962 nsTArray<SizePair> B; 963 B.SetLength(n); 964 for (int32_t i = 0; i < n; i++) { 965 B[i] = pareas[m2 * n + i] - pareas[m1 * n + i]; 966 } 967 int32_t minIdx, maxIdx; 968 SizePair area = MaxSum1D(B, n, &minIdx, &maxIdx); 969 if (area > bestArea) { 970 bestRectIndices.left = minIdx; 971 bestRectIndices.top = m1; 972 bestRectIndices.right = maxIdx; 973 bestRectIndices.bottom = m2; 974 bestArea = area; 975 } 976 } 977 } 978 979 bestRect.MoveTo(xaxis.StopAt(bestRectIndices.left), 980 yaxis.StopAt(bestRectIndices.top)); 981 bestRect.SizeTo(xaxis.StopAt(bestRectIndices.right) - bestRect.X(), 982 yaxis.StopAt(bestRectIndices.bottom) - bestRect.Y()); 983 } 984 985 return bestRect; 986 } 987 988 std::ostream& operator<<(std::ostream& stream, const nsRegion& m) { 989 stream << "["; 990 991 bool first = true; 992 for (auto iter = m.RectIter(); !iter.Done(); iter.Next()) { 993 if (!first) { 994 stream << "; "; 995 } else { 996 first = false; 997 } 998 const nsRect& rect = iter.Get(); 999 stream << rect.X() << "," << rect.Y() << "," << rect.XMost() << "," 1000 << rect.YMost(); 1001 } 1002 1003 stream << "]"; 1004 return stream; 1005 } 1006 1007 void nsRegion::OutputToStream(std::string aObjName, 1008 std::ostream& stream) const { 1009 auto iter = RectIter(); 1010 nsRect r = iter.Get(); 1011 stream << "nsRegion " << aObjName << "(nsRect(" << r.X() << ", " << r.Y() 1012 << ", " << r.Width() << ", " << r.Height() << "));\n"; 1013 iter.Next(); 1014 1015 for (; !iter.Done(); iter.Next()) { 1016 nsRect r = iter.Get(); 1017 stream << aObjName << ".OrWith(nsRect(" << r.X() << ", " << r.Y() << ", " 1018 << r.Width() << ", " << r.Height() << "));\n"; 1019 } 1020 } 1021 1022 nsCString nsRegion::ToString() const { 1023 return nsCString(mozilla::ToString(*this).c_str()); 1024 }