DisplayItemClip.cpp (17821B)
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 "DisplayItemClip.h" 8 9 #include "gfxContext.h" 10 #include "gfxUtils.h" 11 #include "mozilla/StaticPtr.h" 12 #include "mozilla/gfx/2D.h" 13 #include "mozilla/gfx/PathHelpers.h" 14 #include "mozilla/layers/StackingContextHelper.h" 15 #include "mozilla/webrender/WebRenderTypes.h" 16 #include "nsCSSRendering.h" 17 #include "nsLayoutUtils.h" 18 #include "nsPresContext.h" 19 #include "nsRegion.h" 20 21 using namespace mozilla::gfx; 22 23 namespace mozilla { 24 25 void DisplayItemClip::SetTo(const nsRect& aRect) { SetTo(aRect, nullptr); } 26 27 void DisplayItemClip::SetTo(const nsRect& aRect, 28 const nsRectCornerRadii* aRadii) { 29 mHaveClipRect = true; 30 mClipRect = aRect; 31 if (aRadii) { 32 mRoundedClipRects.Clear(); 33 mRoundedClipRects.AppendElement(RoundedRect{aRect, *aRadii}); 34 } else { 35 mRoundedClipRects.Clear(); 36 } 37 } 38 39 void DisplayItemClip::SetTo(const nsRect& aRect, const nsRect& aRoundedRect, 40 const nsRectCornerRadii* aRadii) { 41 mHaveClipRect = true; 42 mClipRect = aRect; 43 mRoundedClipRects.Clear(); 44 mRoundedClipRects.AppendElement(RoundedRect{aRoundedRect, *aRadii}); 45 } 46 47 bool DisplayItemClip::MayIntersect(const nsRect& aRect) const { 48 if (!mHaveClipRect) { 49 return !aRect.IsEmpty(); 50 } 51 nsRect r = aRect.Intersect(mClipRect); 52 if (r.IsEmpty()) { 53 return false; 54 } 55 for (const RoundedRect& rr : mRoundedClipRects) { 56 if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) { 57 return false; 58 } 59 } 60 return true; 61 } 62 63 void DisplayItemClip::IntersectWith(const DisplayItemClip& aOther) { 64 if (!aOther.mHaveClipRect) { 65 return; 66 } 67 if (!mHaveClipRect) { 68 *this = aOther; 69 return; 70 } 71 if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) { 72 mRoundedClipRects.Clear(); 73 return; 74 } 75 mRoundedClipRects.AppendElements(aOther.mRoundedClipRects); 76 } 77 78 void DisplayItemClip::ApplyTo(gfxContext* aContext, int32_t A2D) const { 79 ApplyRectTo(aContext, A2D); 80 ApplyRoundedRectClipsTo(aContext, A2D, 0, mRoundedClipRects.Length()); 81 } 82 83 void DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const { 84 aContext->NewPath(); 85 gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D); 86 aContext->SnappedRectangle(clip); 87 aContext->Clip(); 88 } 89 90 void DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2D, 91 uint32_t aBegin, 92 uint32_t aEnd) const { 93 DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); 94 95 aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length()); 96 97 for (uint32_t i = aBegin; i < aEnd; ++i) { 98 RefPtr<Path> roundedRect = 99 MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]); 100 aContext->Clip(roundedRect); 101 } 102 } 103 104 void DisplayItemClip::FillIntersectionOfRoundedRectClips( 105 gfxContext* aContext, const DeviceColor& aColor, 106 int32_t aAppUnitsPerDevPixel) const { 107 DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); 108 109 uint32_t end = mRoundedClipRects.Length(); 110 if (!end) { 111 return; 112 } 113 114 // Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any: 115 ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, 0, end - 1); 116 117 // Now fill the rect at |aEnd - 1|: 118 RefPtr<Path> roundedRect = MakeRoundedRectPath( 119 aDrawTarget, aAppUnitsPerDevPixel, mRoundedClipRects[end - 1]); 120 aDrawTarget.Fill(roundedRect, ColorPattern(aColor)); 121 122 // Finally, pop any clips that we may have pushed: 123 for (uint32_t i = 0; i < end - 1; ++i) { 124 aContext->PopClip(); 125 } 126 } 127 128 already_AddRefed<Path> DisplayItemClip::MakeRoundedRectPath( 129 DrawTarget& aDrawTarget, int32_t A2D, const RoundedRect& aRoundRect) const { 130 RectCornerRadii pixelRadii; 131 nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii); 132 133 Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget); 134 135 return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii); 136 } 137 138 nsRect DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const { 139 nsRect r = aRect; 140 if (mHaveClipRect) { 141 r.IntersectRect(r, mClipRect); 142 } 143 for (const RoundedRect& rr : mRoundedClipRects) { 144 nsRegion rgn = 145 nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r); 146 r = rgn.GetLargestRectangle(); 147 } 148 return r; 149 } 150 151 // Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter) 152 // and radii aXRadius, aYRadius. 153 static bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint, 154 nscoord aYRadius, nscoord aYCenter, 155 nscoord aYPoint) { 156 float scaledX = float(aXPoint - aXCenter) / float(aXRadius); 157 float scaledY = float(aYPoint - aYCenter) / float(aYRadius); 158 return scaledX * scaledX + scaledY * scaledY < 1.0f; 159 } 160 161 bool DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const { 162 if (mRoundedClipRects.IsEmpty()) { 163 return false; 164 } 165 166 nsRect rect; 167 rect.IntersectRect(aRect, NonRoundedIntersection()); 168 for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { 169 const RoundedRect& rr = mRoundedClipRects[i]; 170 // top left 171 if (rect.x < rr.mRect.x + rr.mRadii[eCornerTopLeftX] && 172 rect.y < rr.mRect.y + rr.mRadii[eCornerTopLeftY]) { 173 if (!IsInsideEllipse(rr.mRadii[eCornerTopLeftX], 174 rr.mRect.x + rr.mRadii[eCornerTopLeftX], rect.x, 175 rr.mRadii[eCornerTopLeftY], 176 rr.mRect.y + rr.mRadii[eCornerTopLeftY], rect.y)) { 177 return true; 178 } 179 } 180 // top right 181 if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerTopRightX] && 182 rect.y < rr.mRect.y + rr.mRadii[eCornerTopRightY]) { 183 if (!IsInsideEllipse(rr.mRadii[eCornerTopRightX], 184 rr.mRect.XMost() - rr.mRadii[eCornerTopRightX], 185 rect.XMost(), rr.mRadii[eCornerTopRightY], 186 rr.mRect.y + rr.mRadii[eCornerTopRightY], rect.y)) { 187 return true; 188 } 189 } 190 // bottom left 191 if (rect.x < rr.mRect.x + rr.mRadii[eCornerBottomLeftX] && 192 rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY]) { 193 if (!IsInsideEllipse(rr.mRadii[eCornerBottomLeftX], 194 rr.mRect.x + rr.mRadii[eCornerBottomLeftX], rect.x, 195 rr.mRadii[eCornerBottomLeftY], 196 rr.mRect.YMost() - rr.mRadii[eCornerBottomLeftY], 197 rect.YMost())) { 198 return true; 199 } 200 } 201 // bottom right 202 if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX] && 203 rect.YMost() > rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY]) { 204 if (!IsInsideEllipse(rr.mRadii[eCornerBottomRightX], 205 rr.mRect.XMost() - rr.mRadii[eCornerBottomRightX], 206 rect.XMost(), rr.mRadii[eCornerBottomRightY], 207 rr.mRect.YMost() - rr.mRadii[eCornerBottomRightY], 208 rect.YMost())) { 209 return true; 210 } 211 } 212 } 213 return false; 214 } 215 216 nsRect DisplayItemClip::NonRoundedIntersection() const { 217 NS_ASSERTION(mHaveClipRect, "Must have a clip rect!"); 218 nsRect result = mClipRect; 219 for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { 220 result.IntersectRect(result, mRoundedClipRects[i].mRect); 221 } 222 return result; 223 } 224 225 bool DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const { 226 if (mHaveClipRect && !mClipRect.Contains(aRect)) { 227 return true; 228 } 229 for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { 230 const RoundedRect& rr = mRoundedClipRects[i]; 231 nsRegion rgn = 232 nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect); 233 if (!rgn.Contains(aRect)) { 234 return true; 235 } 236 } 237 return false; 238 } 239 240 bool DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect, 241 float aXScale, float aYScale, 242 int32_t A2D) const { 243 if (mHaveClipRect) { 244 nsIntRect pixelClipRect = 245 mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D); 246 if (!pixelClipRect.Contains(aRect)) { 247 return true; 248 } 249 } 250 251 // Rounded rect clipping only snaps to user-space pixels, not device space. 252 nsIntRect unscaled = aRect; 253 unscaled.Scale(1 / aXScale, 1 / aYScale); 254 255 for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { 256 const RoundedRect& rr = mRoundedClipRects[i]; 257 258 nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D); 259 260 RectCornerRadii pixelRadii; 261 nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii); 262 263 nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect( 264 pixelRect, pixelRadii, unscaled); 265 if (!rgn.Contains(unscaled)) { 266 return true; 267 } 268 } 269 return false; 270 } 271 272 nsRect DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const { 273 if (!mHaveClipRect) { 274 return aRect; 275 } 276 277 nsRect result = aRect.Intersect(mClipRect); 278 for (uint32_t i = 0, iEnd = mRoundedClipRects.Length(); i < iEnd; ++i) { 279 result = result.Intersect(mRoundedClipRects[i].mRect); 280 } 281 return result; 282 } 283 284 void DisplayItemClip::RemoveRoundedCorners() { 285 if (mRoundedClipRects.IsEmpty()) { 286 return; 287 } 288 289 mClipRect = NonRoundedIntersection(); 290 mRoundedClipRects.Clear(); 291 } 292 293 // Computes the difference between aR1 and aR2, limited to aBounds. 294 static void AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, 295 const nsRect& aBounds, nsRegion* aOut) { 296 if (aR1.IsEqualInterior(aR2)) { 297 return; 298 } 299 nsRegion r; 300 r.Xor(aR1, aR2); 301 r.And(r, aBounds); 302 aOut->Or(*aOut, r); 303 } 304 305 static void AccumulateRoundedRectDifference( 306 const DisplayItemClip::RoundedRect& aR1, 307 const DisplayItemClip::RoundedRect& aR2, const nsRect& aBounds, 308 const nsRect& aOtherBounds, nsRegion* aOut) { 309 const nsRect& rect1 = aR1.mRect; 310 const nsRect& rect2 = aR2.mRect; 311 312 // If the two rectangles are totally disjoint, just add them both - otherwise 313 // we'd end up adding one big enclosing rect 314 if (!rect1.Intersects(rect2) || aR1.mRadii != aR2.mRadii) { 315 aOut->Or(*aOut, rect1.Intersect(aBounds)); 316 aOut->Or(*aOut, rect2.Intersect(aOtherBounds)); 317 return; 318 } 319 320 nscoord lowestBottom = std::max(rect1.YMost(), rect2.YMost()); 321 nscoord highestTop = std::min(rect1.Y(), rect2.Y()); 322 nscoord maxRight = std::max(rect1.XMost(), rect2.XMost()); 323 nscoord minLeft = std::min(rect1.X(), rect2.X()); 324 325 // At this point, we know that the radii haven't changed, and that the bounds 326 // are different in some way. To explain how this works, consider the case 327 // where the rounded rect has just been translated along the X direction. 328 // | ______________________ _ _ _ _ _ _ | 329 // | / / \ \ | 330 // | | | | 331 // | | aR1 | | aR2 | | 332 // | | | | 333 // | \ __________\___________ / _ _ _ _ _ / | 334 // | | 335 // The invalidation region will be as if we lopped off the left rounded part 336 // of aR2, and the right rounded part of aR1, and XOR'd them: 337 // | ______________________ _ _ _ _ _ _ | 338 // | -/-----------/- -\-----------\- | 339 // | |-------------- --|------------ | 340 // | |-----aR1---|-- --|-----aR2---| | 341 // | |-------------- --|------------ | 342 // | -\ __________\-__________-/ _ _ _ _ _ /- | 343 // | | 344 // The logic below just implements this idea, but generalized to both the 345 // X and Y dimensions. The "(...)Adjusted(...)" values represent the lopped 346 // off sides. 347 nscoord highestAdjustedBottom = std::min( 348 rect1.YMost() - aR1.mRadii[eCornerBottomLeftY], 349 std::min(rect1.YMost() - aR1.mRadii[eCornerBottomRightY], 350 std::min(rect2.YMost() - aR2.mRadii[eCornerBottomLeftY], 351 rect2.YMost() - aR2.mRadii[eCornerBottomRightY]))); 352 nscoord lowestAdjustedTop = 353 std::max(rect1.Y() + aR1.mRadii[eCornerTopLeftY], 354 std::max(rect1.Y() + aR1.mRadii[eCornerTopRightY], 355 std::max(rect2.Y() + aR2.mRadii[eCornerTopLeftY], 356 rect2.Y() + aR2.mRadii[eCornerTopRightY]))); 357 358 nscoord minAdjustedRight = std::min( 359 rect1.XMost() - aR1.mRadii[eCornerTopRightX], 360 std::min(rect1.XMost() - aR1.mRadii[eCornerBottomRightX], 361 std::min(rect2.XMost() - aR2.mRadii[eCornerTopRightX], 362 rect2.XMost() - aR2.mRadii[eCornerBottomRightX]))); 363 nscoord maxAdjustedLeft = 364 std::max(rect1.X() + aR1.mRadii[eCornerTopLeftX], 365 std::max(rect1.X() + aR1.mRadii[eCornerBottomLeftX], 366 std::max(rect2.X() + aR2.mRadii[eCornerTopLeftX], 367 rect2.X() + aR2.mRadii[eCornerBottomLeftX]))); 368 369 // We only want to add an invalidation rect if the bounds have changed. If we 370 // always added all of the 4 rects below, we would always be invalidating a 371 // border around the rects, even in cases where we just translated along the X 372 // or Y axis. 373 nsRegion r; 374 // First, or with the Y delta rects, wide along the X axis 375 if (rect1.Y() != rect2.Y()) { 376 r.Or(r, nsRect(minLeft, highestTop, maxRight - minLeft, 377 lowestAdjustedTop - highestTop)); 378 } 379 if (rect1.YMost() != rect2.YMost()) { 380 r.Or(r, nsRect(minLeft, highestAdjustedBottom, maxRight - minLeft, 381 lowestBottom - highestAdjustedBottom)); 382 } 383 // Then, or with the X delta rects, wide along the Y axis 384 if (rect1.X() != rect2.X()) { 385 r.Or(r, nsRect(minLeft, highestTop, maxAdjustedLeft - minLeft, 386 lowestBottom - highestTop)); 387 } 388 if (rect1.XMost() != rect2.XMost()) { 389 r.Or(r, nsRect(minAdjustedRight, highestTop, maxRight - minAdjustedRight, 390 lowestBottom - highestTop)); 391 } 392 393 r.And(r, aBounds.Union(aOtherBounds)); 394 aOut->Or(*aOut, r); 395 } 396 397 void DisplayItemClip::AddOffsetAndComputeDifference( 398 const nsPoint& aOffset, const nsRect& aBounds, 399 const DisplayItemClip& aOther, const nsRect& aOtherBounds, 400 nsRegion* aDifference) { 401 if (mHaveClipRect != aOther.mHaveClipRect || 402 mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) { 403 aDifference->Or(*aDifference, aBounds); 404 aDifference->Or(*aDifference, aOtherBounds); 405 return; 406 } 407 if (mHaveClipRect) { 408 AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect, 409 aBounds.Union(aOtherBounds), aDifference); 410 } 411 for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { 412 if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) { 413 AccumulateRoundedRectDifference(mRoundedClipRects[i] + aOffset, 414 aOther.mRoundedClipRects[i], aBounds, 415 aOtherBounds, aDifference); 416 } 417 } 418 } 419 420 void DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray) const { 421 aArray->AppendElements(mRoundedClipRects.Elements(), 422 mRoundedClipRects.Length()); 423 } 424 425 bool DisplayItemClip::ComputeRegionInClips(const DisplayItemClip* aOldClip, 426 const nsPoint& aShift, 427 nsRegion* aCombined) const { 428 if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) { 429 return false; 430 } 431 432 if (aOldClip) { 433 *aCombined = aOldClip->NonRoundedIntersection(); 434 aCombined->MoveBy(aShift); 435 aCombined->Or(*aCombined, NonRoundedIntersection()); 436 } else { 437 *aCombined = NonRoundedIntersection(); 438 } 439 return true; 440 } 441 442 void DisplayItemClip::MoveBy(const nsPoint& aPoint) { 443 if (!mHaveClipRect) { 444 return; 445 } 446 mClipRect += aPoint; 447 for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) { 448 mRoundedClipRects[i].mRect += aPoint; 449 } 450 } 451 452 static StaticAutoPtr<DisplayItemClip> gNoClip; 453 454 const DisplayItemClip& DisplayItemClip::NoClip() { 455 if (!gNoClip) { 456 gNoClip = new DisplayItemClip(); 457 } 458 return *gNoClip; 459 } 460 461 void DisplayItemClip::Shutdown() { gNoClip = nullptr; } 462 463 nsCString DisplayItemClip::ToString() const { 464 nsAutoCString str; 465 if (mHaveClipRect) { 466 str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y, mClipRect.width, 467 mClipRect.height); 468 for (const RoundedRect& r : mRoundedClipRects) { 469 str.AppendPrintf( 470 " [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]", r.mRect.x, 471 r.mRect.y, r.mRect.width, r.mRect.height, r.mRadii.TopLeft().width, 472 r.mRadii.TopLeft().height, r.mRadii.TopRight().width, 473 r.mRadii.TopRight().height, r.mRadii.BottomLeft().width, 474 r.mRadii.BottomLeft().height, r.mRadii.BottomRight().width, 475 r.mRadii.BottomRight().height); 476 } 477 } 478 return std::move(str); 479 } 480 481 void DisplayItemClip::ToComplexClipRegions( 482 int32_t aAppUnitsPerDevPixel, 483 nsTArray<wr::ComplexClipRegion>& aOutArray) const { 484 for (const auto& clipRect : mRoundedClipRects) { 485 aOutArray.AppendElement(wr::ToComplexClipRegion( 486 clipRect.mRect, clipRect.mRadii, aAppUnitsPerDevPixel)); 487 } 488 } 489 490 } // namespace mozilla