gfxBlur.cpp (46950B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "gfxBlur.h" 7 8 #include "gfx2DGlue.h" 9 #include "gfxContext.h" 10 #include "gfxPlatform.h" 11 #include "mozilla/gfx/2D.h" 12 #include "mozilla/gfx/Blur.h" 13 #include "mozilla/gfx/PathHelpers.h" 14 #include "mozilla/Maybe.h" 15 #include "nsExpirationTracker.h" 16 #include "nsClassHashtable.h" 17 #include "gfxUtils.h" 18 #include <cmath> 19 20 using namespace mozilla; 21 using namespace mozilla::gfx; 22 23 gfxGaussianBlur::~gfxGaussianBlur() = default; 24 25 UniquePtr<gfxContext> gfxGaussianBlur::Init( 26 gfxContext* aDestinationCtx, const gfxRect& aRect, 27 const IntSize& aSpreadRadius, const Point& aBlurSigma, 28 const gfxRect* aDirtyRect, const gfxRect* aSkipRect, bool aClamp) { 29 DrawTarget* refDT = aDestinationCtx->GetDrawTarget(); 30 Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing(); 31 Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing(); 32 RefPtr<DrawTarget> dt = 33 InitDrawTarget(refDT, ToRect(aRect), aSpreadRadius, aBlurSigma, 34 dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aClamp); 35 if (!dt || !dt->IsValid()) { 36 return nullptr; 37 } 38 39 auto context = MakeUnique<gfxContext>(dt); 40 context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft())); 41 return context; 42 } 43 44 UniquePtr<gfxContext> gfxGaussianBlur::Init( 45 gfxContext* aDestinationCtx, const gfxRect& aRect, 46 const IntSize& aSpreadRadius, const IntSize& aBlurRadius, 47 const gfxRect* aDirtyRect, const gfxRect* aSkipRect, bool aClamp) { 48 return Init(aDestinationCtx, aRect, aSpreadRadius, 49 CalculateBlurSigma(aBlurRadius), aDirtyRect, aSkipRect, aClamp); 50 } 51 52 already_AddRefed<DrawTarget> gfxGaussianBlur::InitDrawTarget( 53 const DrawTarget* aReferenceDT, const Rect& aRect, 54 const IntSize& aSpreadRadius, const Point& aBlurSigma, 55 const Rect* aDirtyRect, const Rect* aSkipRect, bool aClamp) { 56 mBlur.Init(aRect, aSpreadRadius, aBlurSigma, aDirtyRect, aSkipRect, 57 SurfaceFormat::A8, aClamp); 58 size_t blurDataSize = mBlur.GetSurfaceAllocationSize(); 59 if (blurDataSize == 0) { 60 return nullptr; 61 } 62 63 BackendType backend = aReferenceDT->GetBackendType(); 64 65 // Make an alpha-only surface to draw on. We will play with the data after 66 // everything is drawn to create a blur effect. 67 // This will be freed when the DrawTarget dies 68 mData = static_cast<uint8_t*>(calloc(1, blurDataSize)); 69 if (!mData) { 70 return nullptr; 71 } 72 mDrawTarget = 73 Factory::DoesBackendSupportDataDrawtarget(backend) 74 ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(), 75 mBlur.GetStride(), 76 mBlur.GetFormat()) 77 : gfxPlatform::CreateDrawTargetForData( 78 mData, mBlur.GetSize(), mBlur.GetStride(), mBlur.GetFormat()); 79 80 if (!mDrawTarget || !mDrawTarget->IsValid()) { 81 if (mData) { 82 free(mData); 83 } 84 85 return nullptr; 86 } 87 88 if (mData) { 89 mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()), 90 mData, free); 91 } 92 93 mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft())); 94 return do_AddRef(mDrawTarget); 95 } 96 97 already_AddRefed<SourceSurface> gfxGaussianBlur::DoBlur( 98 const sRGBColor* aShadowColor, IntPoint* aOutTopLeft) { 99 if (aOutTopLeft) { 100 *aOutTopLeft = mBlur.GetRect().TopLeft(); 101 } 102 103 RefPtr<SourceSurface> blurMask; 104 if (mData) { 105 mDrawTarget->Blur(mBlur); 106 blurMask = mDrawTarget->Snapshot(); 107 } 108 109 if (!aShadowColor) { 110 return blurMask.forget(); 111 } 112 113 RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget( 114 blurMask->GetSize(), SurfaceFormat::B8G8R8A8); 115 if (!shadowDT) { 116 return nullptr; 117 } 118 ColorPattern shadowColor(ToDeviceColor(*aShadowColor)); 119 shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0)); 120 121 return shadowDT->Snapshot(); 122 } 123 124 void gfxGaussianBlur::Paint(gfxContext* aDestinationCtx) { 125 if (mDrawTarget && !mData) { 126 return; 127 } 128 129 DrawTarget* dest = aDestinationCtx->GetDrawTarget(); 130 if (!dest) { 131 NS_WARNING("Blurring not supported for Thebes contexts!"); 132 return; 133 } 134 135 RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern(); 136 Pattern* pat = thebesPat->GetPattern(dest, nullptr); 137 if (!pat) { 138 NS_WARNING("Failed to get pattern for blur!"); 139 return; 140 } 141 142 IntPoint topLeft; 143 RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft); 144 if (!mask) { 145 NS_ERROR("Failed to create mask!"); 146 return; 147 } 148 149 // Avoid a semi-expensive clip operation if we can, otherwise 150 // clip to the dirty rect 151 Rect* dirtyRect = mBlur.GetDirtyRect(); 152 if (dirtyRect) { 153 dest->PushClipRect(*dirtyRect); 154 } 155 156 Matrix oldTransform = dest->GetTransform(); 157 Matrix newTransform = oldTransform; 158 newTransform.PreTranslate(topLeft); 159 dest->SetTransform(newTransform); 160 161 dest->MaskSurface(*pat, mask, Point(0, 0)); 162 163 dest->SetTransform(oldTransform); 164 165 if (dirtyRect) { 166 dest->PopClip(); 167 } 168 } 169 170 IntSize gfxGaussianBlur::CalculateBlurRadius(const gfxPoint& aStd) { 171 Point std(Float(aStd.x), Float(aStd.y)); 172 IntSize size = GaussianBlur::CalculateBlurRadius(std); 173 return IntSize(size.width, size.height); 174 } 175 176 Point gfxGaussianBlur::CalculateBlurSigma(const IntSize& aBlurRadius) { 177 return Point(GaussianBlur::CalculateBlurSigma(aBlurRadius.width), 178 GaussianBlur::CalculateBlurSigma(aBlurRadius.height)); 179 } 180 181 struct BlurCacheKey : public PLDHashEntryHdr { 182 typedef const BlurCacheKey& KeyType; 183 typedef const BlurCacheKey* KeyTypePointer; 184 enum { ALLOW_MEMMOVE = true }; 185 186 IntSize mMinSize; 187 IntSize mBlurRadius; 188 sRGBColor mShadowColor; 189 BackendType mBackend; 190 RectCornerRadii mCornerRadii; 191 bool mIsInset; 192 193 // Only used for inset blurs 194 IntSize mInnerMinSize; 195 196 BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius, 197 const RectCornerRadii* aCornerRadii, 198 const sRGBColor& aShadowColor, BackendType aBackendType) 199 : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii, 200 aShadowColor, false, aBackendType) {} 201 202 explicit BlurCacheKey(const BlurCacheKey* aOther) 203 : mMinSize(aOther->mMinSize), 204 mBlurRadius(aOther->mBlurRadius), 205 mShadowColor(aOther->mShadowColor), 206 mBackend(aOther->mBackend), 207 mCornerRadii(aOther->mCornerRadii), 208 mIsInset(aOther->mIsInset), 209 mInnerMinSize(aOther->mInnerMinSize) {} 210 211 explicit BlurCacheKey(const IntSize& aOuterMinSize, 212 const IntSize& aInnerMinSize, 213 const IntSize& aBlurRadius, 214 const RectCornerRadii* aCornerRadii, 215 const sRGBColor& aShadowColor, bool aIsInset, 216 BackendType aBackendType) 217 : mMinSize(aOuterMinSize), 218 mBlurRadius(aBlurRadius), 219 mShadowColor(aShadowColor), 220 mBackend(aBackendType), 221 mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()), 222 mIsInset(aIsInset), 223 mInnerMinSize(aInnerMinSize) {} 224 225 BlurCacheKey(BlurCacheKey&&) = default; 226 227 static PLDHashNumber HashKey(const KeyTypePointer aKey) { 228 PLDHashNumber hash = 0; 229 hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height); 230 hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height); 231 232 hash = AddToHash( 233 hash, HashBytes(&aKey->mShadowColor.r, sizeof(aKey->mShadowColor.r))); 234 hash = AddToHash( 235 hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g))); 236 hash = AddToHash( 237 hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b))); 238 hash = AddToHash( 239 hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a))); 240 241 for (auto corner : mozilla::AllPhysicalCorners()) { 242 hash = AddToHash(hash, aKey->mCornerRadii[corner].width, 243 aKey->mCornerRadii[corner].height); 244 } 245 246 hash = AddToHash(hash, (uint32_t)aKey->mBackend); 247 248 if (aKey->mIsInset) { 249 hash = AddToHash(hash, aKey->mInnerMinSize.width, 250 aKey->mInnerMinSize.height); 251 } 252 return hash; 253 } 254 255 bool KeyEquals(KeyTypePointer aKey) const { 256 if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius && 257 aKey->mCornerRadii == mCornerRadii && 258 aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) { 259 if (mIsInset) { 260 return (mInnerMinSize == aKey->mInnerMinSize); 261 } 262 263 return true; 264 } 265 266 return false; 267 } 268 269 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } 270 }; 271 272 /** 273 * This class is what is cached. It need to be allocated in an object separated 274 * to the cache entry to be able to be tracked by the nsExpirationTracker. 275 * */ 276 struct BlurCacheData { 277 BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin, 278 BlurCacheKey&& aKey) 279 : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {} 280 281 BlurCacheData(BlurCacheData&& aOther) = default; 282 283 nsExpirationState* GetExpirationState() { return &mExpirationState; } 284 285 nsExpirationState mExpirationState; 286 RefPtr<SourceSurface> mBlur; 287 IntMargin mBlurMargin; 288 BlurCacheKey mKey; 289 }; 290 291 /** 292 * This class implements a cache with no maximum size, that retains the 293 * SourceSurfaces used to draw the blurs. 294 * 295 * An entry stays in the cache as long as it is used often. 296 */ 297 class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> { 298 public: 299 BlurCache() 300 : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache"_ns) {} 301 302 virtual void NotifyExpired(BlurCacheData* aObject) override { 303 RemoveObject(aObject); 304 mHashEntries.Remove(aObject->mKey); 305 } 306 307 BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius, 308 const RectCornerRadii* aCornerRadii, 309 const sRGBColor& aShadowColor, 310 BackendType aBackendType) { 311 BlurCacheData* blur = mHashEntries.Get(BlurCacheKey( 312 aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType)); 313 if (blur) { 314 MarkUsed(blur); 315 } 316 317 return blur; 318 } 319 320 BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize, 321 const IntSize& aInnerMinSize, 322 const IntSize& aBlurRadius, 323 const RectCornerRadii* aCornerRadii, 324 const sRGBColor& aShadowColor, 325 BackendType aBackendType) { 326 bool insetBoxShadow = true; 327 BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii, 328 aShadowColor, insetBoxShadow, aBackendType); 329 BlurCacheData* blur = mHashEntries.Get(key); 330 if (blur) { 331 MarkUsed(blur); 332 } 333 334 return blur; 335 } 336 337 void RegisterEntry(UniquePtr<BlurCacheData> aValue) { 338 nsresult rv = AddObject(aValue.get()); 339 if (NS_FAILED(rv)) { 340 // We are OOM, and we cannot track this object. We don't want stall 341 // entries in the hash table (since the expiration tracker is responsible 342 // for removing the cache entries), so we avoid putting that entry in the 343 // table, which is a good thing considering we are short on memory 344 // anyway, we probably don't want to retain things. 345 return; 346 } 347 mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue)); 348 } 349 350 protected: 351 static const uint32_t GENERATION_MS = 1000; 352 /** 353 * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey. 354 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47 355 */ 356 nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries; 357 }; 358 359 static BlurCache* gBlurCache = nullptr; 360 361 static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, 362 const IntSize& aBlurRadius, 363 IntMargin& aOutSlice, 364 const IntSize& aRectSize) { 365 Size cornerSize(0, 0); 366 if (aCornerRadii) { 367 const RectCornerRadii& corners = *aCornerRadii; 368 for (const auto i : mozilla::AllPhysicalCorners()) { 369 cornerSize.width = std::max(cornerSize.width, corners[i].width); 370 cornerSize.height = std::max(cornerSize.height, corners[i].height); 371 } 372 } 373 374 IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; 375 aOutSlice = 376 IntMargin(margin.height, margin.width, margin.height, margin.width); 377 378 IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1); 379 380 // If aRectSize is smaller than minSize, the border-image approach won't 381 // work; there's no way to squeeze parts of the min box-shadow source 382 // image such that the result looks correct. So we need to adjust minSize 383 // in such a way that we can later draw it without stretching in the affected 384 // dimension. We also need to adjust "slice" to ensure that we're not trying 385 // to slice away more than we have. 386 if (aRectSize.width < minSize.width) { 387 minSize.width = aRectSize.width; 388 aOutSlice.left = 0; 389 aOutSlice.right = 0; 390 } 391 if (aRectSize.height < minSize.height) { 392 minSize.height = aRectSize.height; 393 aOutSlice.top = 0; 394 aOutSlice.bottom = 0; 395 } 396 397 MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); 398 MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); 399 return minSize; 400 } 401 402 static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize, 403 const IntSize& aBlurRadius, 404 const RectCornerRadii* aCornerRadii, 405 const sRGBColor& aShadowColor, 406 const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) { 407 gBlurCache->RegisterEntry(MakeUnique<BlurCacheData>( 408 aBoxShadow, aBlurMargin, 409 BlurCacheKey(aMinSize, aBlurRadius, aCornerRadii, aShadowColor, 410 aDT->GetBackendType()))); 411 } 412 413 // Blurs a small surface and creates the colored box shadow. 414 static already_AddRefed<SourceSurface> CreateBoxShadow( 415 DrawTarget* aDestDrawTarget, const IntSize& aMinSize, 416 const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius, 417 const sRGBColor& aShadowColor, bool aMirrorCorners, 418 IntMargin& aOutBlurMargin) { 419 gfxGaussianBlur blur; 420 Rect minRect(Point(0, 0), Size(aMinSize)); 421 Rect blurRect(minRect); 422 // If mirroring corners, we only need to draw the top-left quadrant. 423 // Use ceil to preserve the remaining 1x1 middle area for minimized box 424 // shadows. 425 if (aMirrorCorners) { 426 blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), 427 ceil(blurRect.Height() * 0.5f)); 428 } 429 IntSize zeroSpread(0, 0); 430 RefPtr<DrawTarget> blurDT = 431 blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, 432 gfxGaussianBlur::CalculateBlurSigma(aBlurRadius)); 433 if (!blurDT) { 434 return nullptr; 435 } 436 437 ColorPattern black(DeviceColor::MaskOpaqueBlack()); 438 439 if (aCornerRadii) { 440 RefPtr<Path> roundedRect = 441 MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii); 442 blurDT->Fill(roundedRect, black); 443 } else { 444 blurDT->FillRect(minRect, black); 445 } 446 447 IntPoint topLeft; 448 RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft); 449 if (!result) { 450 return nullptr; 451 } 452 453 // Since blurRect is at (0, 0), we can find the inflated margin by 454 // negating the new rect origin, which would have been negative if 455 // the rect was inflated. 456 aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x); 457 458 return result.forget(); 459 } 460 461 static already_AddRefed<SourceSurface> GetBlur( 462 gfxContext* aDestinationCtx, const IntSize& aRectSize, 463 const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii, 464 const sRGBColor& aShadowColor, bool aMirrorCorners, 465 IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) { 466 if (!gBlurCache) { 467 gBlurCache = new BlurCache(); 468 } 469 470 IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, 471 aOutSlice, aRectSize); 472 473 // We can get seams using the min size rect when drawing to the destination 474 // rect if we have a non-pixel aligned destination transformation. In those 475 // cases, fallback to just rendering the destination rect. During printing, we 476 // record all the Moz 2d commands and replay them on the parent side with 477 // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However, 478 // since our source image is only 1px for some parts, we make thousands of 479 // calls. Instead just render the blur ourself here as one image and send it 480 // over for printing. 481 // TODO: May need to change this with the blob renderer in WR since it also 482 // records. 483 Matrix destMatrix = aDestinationCtx->CurrentMatrix(); 484 bool useDestRect = !destMatrix.IsRectilinear() || 485 destMatrix.HasNonIntegerTranslation() || 486 aDestinationCtx->GetDrawTarget()->IsRecording(); 487 if (useDestRect) { 488 minSize = aRectSize; 489 } 490 491 int32_t maxTextureSize = gfxPlatform::MaxTextureSize(); 492 if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) { 493 return nullptr; 494 } 495 496 aOutMinSize = minSize; 497 498 DrawTarget* destDT = aDestinationCtx->GetDrawTarget(); 499 500 if (!useDestRect) { 501 BlurCacheData* cached = 502 gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, 503 destDT->GetBackendType()); 504 if (cached) { 505 // See CreateBoxShadow() for these values 506 aOutBlurMargin = cached->mBlurMargin; 507 RefPtr<SourceSurface> blur = cached->mBlur; 508 return blur.forget(); 509 } 510 } 511 512 RefPtr<SourceSurface> boxShadow = 513 CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor, 514 aMirrorCorners, aOutBlurMargin); 515 if (!boxShadow) { 516 return nullptr; 517 } 518 519 if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) { 520 boxShadow = opt; 521 } 522 523 if (!useDestRect) { 524 CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, 525 aOutBlurMargin, boxShadow); 526 } 527 return boxShadow.forget(); 528 } 529 530 void gfxGaussianBlur::ShutdownBlurCache() { 531 delete gBlurCache; 532 gBlurCache = nullptr; 533 } 534 535 static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom, 536 Float aLeft) { 537 return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); 538 } 539 540 static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) { 541 // Use stretching if possible, since it leads to less seams when the 542 // destination is transformed. However, don't do this if we're using cairo, 543 // because if cairo is using pixman it won't render anything for large 544 // stretch factors because pixman's internal fixed point precision is not 545 // high enough to handle those scale factors. 546 return aDT->GetBackendType() != BackendType::CAIRO; 547 } 548 549 static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface, 550 const Rect& aDest, const Rect& aSrc, 551 const Rect& aSkipRect) { 552 if (aSkipRect.Contains(aDest)) { 553 return; 554 } 555 556 if (ShouldStretchSurface(aDT, aSurface)) { 557 aDT->DrawSurface(aSurface, aDest, aSrc); 558 return; 559 } 560 561 SurfacePattern pattern(aSurface, ExtendMode::REPEAT, 562 Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()), 563 SamplingFilter::GOOD, RoundedToInt(aSrc)); 564 aDT->FillRect(aDest, pattern); 565 } 566 567 static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface, 568 const Rect& aDest, const Rect& aSrc, 569 const Rect& aSkipRect) { 570 if (aSkipRect.Contains(aDest)) { 571 return; 572 } 573 574 aDT->DrawSurface(aSurface, aDest, aSrc); 575 } 576 577 static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget, 578 SourceSurface* aSourceBlur, const Rect& aDstOuter, 579 const Rect& aDstInner, const Rect& aSrcOuter, 580 const Rect& aSrcInner, const Rect& aSkipRect, 581 bool aMiddle = false) { 582 // Corners: top left, top right, bottom left, bottom right 583 DrawCorner(aDestDrawTarget, aSourceBlur, 584 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(), 585 aDstOuter.X()), 586 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(), 587 aSrcOuter.X()), 588 aSkipRect); 589 590 DrawCorner(aDestDrawTarget, aSourceBlur, 591 RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(), 592 aDstInner.XMost()), 593 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(), 594 aSrcInner.XMost()), 595 aSkipRect); 596 597 DrawCorner(aDestDrawTarget, aSourceBlur, 598 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(), 599 aDstOuter.YMost(), aDstOuter.X()), 600 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(), 601 aSrcOuter.YMost(), aSrcOuter.X()), 602 aSkipRect); 603 604 DrawCorner(aDestDrawTarget, aSourceBlur, 605 RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(), 606 aDstOuter.YMost(), aDstInner.XMost()), 607 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(), 608 aSrcOuter.YMost(), aSrcInner.XMost()), 609 aSkipRect); 610 611 // Edges: top, left, right, bottom 612 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, 613 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), 614 aDstInner.Y(), aDstInner.X()), 615 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), 616 aSrcInner.Y(), aSrcInner.X()), 617 aSkipRect); 618 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, 619 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), 620 aDstInner.YMost(), aDstOuter.X()), 621 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), 622 aSrcInner.YMost(), aSrcOuter.X()), 623 aSkipRect); 624 625 RepeatOrStretchSurface( 626 aDestDrawTarget, aSourceBlur, 627 RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(), 628 aDstInner.XMost()), 629 RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(), 630 aSrcInner.XMost()), 631 aSkipRect); 632 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, 633 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), 634 aDstOuter.YMost(), aDstInner.X()), 635 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(), 636 aSrcOuter.YMost(), aSrcInner.X()), 637 aSkipRect); 638 639 // Middle part 640 if (aMiddle) { 641 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, 642 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), 643 aDstInner.YMost(), aDstInner.X()), 644 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), 645 aSrcInner.YMost(), aSrcInner.X()), 646 aSkipRect); 647 } 648 } 649 650 static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface, 651 const Rect& aDest, const Point& aSrc, 652 Float aScaleX, Float aScaleY) { 653 SurfacePattern pattern( 654 aSurface, ExtendMode::CLAMP, 655 Matrix::Scaling(aScaleX, aScaleY) 656 .PreTranslate(-aSrc) 657 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), 658 aScaleY < 0 ? aDest.YMost() : aDest.Y())); 659 aDT->FillRect(aDest, pattern); 660 } 661 662 static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface, 663 const Rect& aDestRect) { 664 Point center(ceil(aDestRect.X() + aDestRect.Width() / 2), 665 ceil(aDestRect.Y() + aDestRect.Height() / 2)); 666 Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(), 667 center.y - aDestRect.Y()); 668 Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size()); 669 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), 670 topLeft.Height()); 671 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), 672 bottomRight.Height()); 673 DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1); 674 DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1); 675 DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1); 676 DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1); 677 } 678 679 static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface, 680 const Rect& aDest, const Point& aSrc, 681 const Rect& aSkipRect, Float aScaleX, 682 Float aScaleY) { 683 if (aSkipRect.Contains(aDest)) { 684 return; 685 } 686 687 DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY); 688 } 689 690 static void RepeatOrStretchMirroredSurface(DrawTarget* aDT, 691 SourceSurface* aSurface, 692 const Rect& aDest, const Rect& aSrc, 693 const Rect& aSkipRect, Float aScaleX, 694 Float aScaleY) { 695 if (aSkipRect.Contains(aDest)) { 696 return; 697 } 698 699 if (ShouldStretchSurface(aDT, aSurface)) { 700 aScaleX *= aDest.Width() / aSrc.Width(); 701 aScaleY *= aDest.Height() / aSrc.Height(); 702 DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY); 703 return; 704 } 705 706 SurfacePattern pattern( 707 aSurface, ExtendMode::REPEAT, 708 Matrix::Scaling(aScaleX, aScaleY) 709 .PreTranslate(-aSrc.TopLeft()) 710 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(), 711 aScaleY < 0 ? aDest.YMost() : aDest.Y()), 712 SamplingFilter::GOOD, RoundedToInt(aSrc)); 713 aDT->FillRect(aDest, pattern); 714 } 715 716 static void DrawMirroredMinBoxShadow( 717 DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur, 718 const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter, 719 const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) { 720 // Corners: top left, top right, bottom left, bottom right 721 // Compute quadrant bounds and then clip them to corners along 722 // dimensions where we need to stretch from min size. 723 Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2), 724 ceil(aDstOuter.Y() + aDstOuter.Height() / 2)); 725 Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(), 726 center.y - aDstOuter.Y()); 727 Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size()); 728 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(), 729 topLeft.Height()); 730 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(), 731 bottomRight.Height()); 732 733 // Check if the middle part has been minimized along each dimension. 734 // If so, those will be strecthed/drawn separately and need to be clipped out. 735 if (aSrcInner.Width() == 1) { 736 topLeft.SetRightEdge(aDstInner.X()); 737 topRight.SetLeftEdge(aDstInner.XMost()); 738 bottomLeft.SetRightEdge(aDstInner.X()); 739 bottomRight.SetLeftEdge(aDstInner.XMost()); 740 } 741 if (aSrcInner.Height() == 1) { 742 topLeft.SetBottomEdge(aDstInner.Y()); 743 topRight.SetBottomEdge(aDstInner.Y()); 744 bottomLeft.SetTopEdge(aDstInner.YMost()); 745 bottomRight.SetTopEdge(aDstInner.YMost()); 746 } 747 748 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(), 749 aSkipRect, 1, 1); 750 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight, 751 aSrcOuter.TopLeft(), aSkipRect, -1, 1); 752 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft, 753 aSrcOuter.TopLeft(), aSkipRect, 1, -1); 754 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight, 755 aSrcOuter.TopLeft(), aSkipRect, -1, -1); 756 757 // Edges: top, bottom, left, right 758 // Draw middle edges where they need to be stretched. The top and left 759 // sections that are part of the top-left quadrant will be mirrored to 760 // the bottom and right sections, respectively. 761 if (aSrcInner.Width() == 1) { 762 Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(), 763 aDstInner.Y(), aDstInner.X()); 764 Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), 765 aSrcInner.Y(), aSrcInner.X()); 766 Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(), 767 aDstOuter.YMost(), aDstInner.X()); 768 Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(), 769 aSrcInner.Y(), aSrcInner.X()); 770 // If we only need to stretch along the X axis and we're drawing 771 // the middle section, just sample all the way to the center of the 772 // source on the Y axis to avoid extra draw calls. 773 if (aMiddle && aSrcInner.Height() != 1) { 774 dstTop.SetBottomEdge(center.y); 775 srcTop.SetHeight(dstTop.Height()); 776 dstBottom.SetTopEdge(dstTop.YMost()); 777 srcBottom.SetHeight(dstBottom.Height()); 778 } 779 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop, 780 aSkipRect, 1, 1); 781 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom, 782 srcBottom, aSkipRect, 1, -1); 783 } 784 785 if (aSrcInner.Height() == 1) { 786 Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(), 787 aDstInner.YMost(), aDstOuter.X()); 788 Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), 789 aSrcInner.YMost(), aSrcOuter.X()); 790 Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), 791 aDstInner.YMost(), aDstInner.XMost()); 792 Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(), 793 aSrcInner.YMost(), aSrcOuter.X()); 794 // Only stretching on Y axis, so sample source to the center of the X axis. 795 if (aMiddle && aSrcInner.Width() != 1) { 796 dstLeft.SetRightEdge(center.x); 797 srcLeft.SetWidth(dstLeft.Width()); 798 dstRight.SetLeftEdge(dstLeft.XMost()); 799 srcRight.SetWidth(dstRight.Width()); 800 } 801 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft, 802 srcLeft, aSkipRect, 1, 1); 803 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight, 804 srcRight, aSkipRect, -1, 1); 805 } 806 807 // If we need to stretch along both dimensions, then the middle part 808 // must be drawn separately. 809 if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) { 810 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur, 811 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(), 812 aDstInner.YMost(), aDstInner.X()), 813 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(), 814 aSrcInner.YMost(), aSrcInner.X()), 815 aSkipRect); 816 } 817 } 818 819 /*** 820 * We draw a blurred a rectangle by only blurring a smaller rectangle and 821 * splitting the rectangle into 9 parts. 822 * First, a small minimum source rect is calculated and used to create a blur 823 * mask since the actual blurring itself is expensive. Next, we use the mask 824 * with the given shadow color to create a minimally-sized box shadow of the 825 * right color. Finally, we cut out the 9 parts from the box-shadow source and 826 * paint each part in the right place, stretching the non-corner parts to fill 827 * the space between the corners. 828 */ 829 830 /* static */ 831 void gfxGaussianBlur::BlurRectangle(gfxContext* aDestinationCtx, 832 const gfxRect& aRect, 833 const RectCornerRadii* aCornerRadii, 834 const gfxPoint& aBlurStdDev, 835 const sRGBColor& aShadowColor, 836 const gfxRect& aDirtyRect, 837 const gfxRect& aSkipRect) { 838 if (!RectIsInt32Safe(ToRect(aRect))) { 839 return; 840 } 841 842 IntSize blurRadius = CalculateBlurRadius(aBlurStdDev); 843 bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame(); 844 845 IntRect rect = RoundedToInt(ToRect(aRect)); 846 IntMargin blurMargin; 847 IntMargin slice; 848 IntSize minSize; 849 RefPtr<SourceSurface> boxShadow = 850 GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii, 851 aShadowColor, mirrorCorners, blurMargin, slice, minSize); 852 if (!boxShadow) { 853 return; 854 } 855 856 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); 857 destDrawTarget->PushClipRect(ToRect(aDirtyRect)); 858 859 // Copy the right parts from boxShadow into destDrawTarget. The middle parts 860 // will be stretched, border-image style. 861 862 Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize)); 863 Rect srcInner(srcOuter); 864 srcOuter.Inflate(Margin(blurMargin)); 865 srcInner.Deflate(Margin(slice)); 866 867 Rect dstOuter(rect); 868 Rect dstInner(rect); 869 dstOuter.Inflate(Margin(blurMargin)); 870 dstInner.Deflate(Margin(slice)); 871 872 Rect skipRect = ToRect(aSkipRect); 873 874 if (minSize == rect.Size()) { 875 // The target rect is smaller than the minimal size so just draw the surface 876 if (mirrorCorners) { 877 DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter); 878 } else { 879 destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter); 880 } 881 } else { 882 if (mirrorCorners) { 883 DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, 884 srcOuter, srcInner, skipRect, true); 885 } else { 886 DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter, 887 srcInner, skipRect, true); 888 } 889 } 890 891 // A note about anti-aliasing and seems between adjacent parts: 892 // We don't explicitly disable anti-aliasing in the DrawSurface calls above, 893 // so if there's a transform on destDrawTarget that is not pixel-aligned, 894 // there will be seams between adjacent parts of the box-shadow. It's hard to 895 // avoid those without the use of an intermediate surface. 896 // You might think that we could avoid those by just turning off AA, but there 897 // is a problem with that: Box-shadow rendering needs to clip out the 898 // element's border box, and we'd like that clip to have anti-aliasing - 899 // especially if the element has rounded corners! So we can't do that unless 900 // we have a way to say "Please anti-alias the clip, but don't antialias the 901 // destination rect of the DrawSurface call". 902 903 destDrawTarget->PopClip(); 904 } 905 906 static already_AddRefed<Path> GetBoxShadowInsetPath( 907 DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect, 908 const RectCornerRadii* aInnerClipRadii) { 909 /*** 910 * We create an inset path by having two rects. 911 * 912 * ----------------------- 913 * | ________________ | 914 * | | | | 915 * | | | | 916 * | ------------------ | 917 * |_____________________| 918 * 919 * The outer rect and the inside rect. The path 920 * creates a frame around the content where we draw the inset shadow. 921 */ 922 RefPtr<PathBuilder> builder = 923 aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD); 924 AppendRectToPath(builder, aOuterRect, true); 925 926 if (aInnerClipRadii) { 927 AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false); 928 } else { 929 AppendRectToPath(builder, aInnerRect, false); 930 } 931 return builder->Finish(); 932 } 933 934 static void FillDestinationPath( 935 gfxContext* aDestinationCtx, const Rect& aDestinationRect, 936 const Rect& aShadowClipRect, const sRGBColor& aShadowColor, 937 const RectCornerRadii* aInnerClipRadii = nullptr) { 938 // When there is no blur radius, fill the path onto the destination 939 // surface. 940 aDestinationCtx->SetColor(aShadowColor); 941 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); 942 RefPtr<Path> shadowPath = GetBoxShadowInsetPath( 943 destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii); 944 945 aDestinationCtx->SetPath(shadowPath); 946 aDestinationCtx->Fill(); 947 } 948 949 static void CacheInsetBlur(const IntSize& aMinOuterSize, 950 const IntSize& aMinInnerSize, 951 const IntSize& aBlurRadius, 952 const RectCornerRadii* aCornerRadii, 953 const sRGBColor& aShadowColor, 954 BackendType aBackendType, 955 SourceSurface* aBoxShadow) { 956 bool isInsetBlur = true; 957 BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii, 958 aShadowColor, isInsetBlur, aBackendType); 959 IntMargin blurMargin(0, 0, 0, 0); 960 961 gBlurCache->RegisterEntry( 962 MakeUnique<BlurCacheData>(aBoxShadow, blurMargin, std::move(key))); 963 } 964 965 already_AddRefed<SourceSurface> gfxGaussianBlur::GetInsetBlur( 966 const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect, 967 const sRGBColor& aShadowColor, const IntSize& aBlurRadius, 968 const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget, 969 bool aMirrorCorners) { 970 if (!gBlurCache) { 971 gBlurCache = new BlurCache(); 972 } 973 974 IntSize outerSize = IntSize::Truncate(aOuterRect.Size()); 975 IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size()); 976 if (!aIsDestRect) { 977 BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow( 978 outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor, 979 aDestDrawTarget->GetBackendType()); 980 if (cached) { 981 // So we don't forget the actual cached blur 982 RefPtr<SourceSurface> cachedBlur = cached->mBlur; 983 return cachedBlur.forget(); 984 } 985 } 986 987 // If we can do a min rect, the whitespace rect will be expanded in Init to 988 // aOuterRect. 989 Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect; 990 // If mirroring corners, we only need to draw the top-left quadrant. 991 // Use ceil to preserve the remaining 1x1 middle area for minimized box 992 // shadows. 993 if (aMirrorCorners) { 994 blurRect.SizeTo(ceil(blurRect.Width() * 0.5f), 995 ceil(blurRect.Height() * 0.5f)); 996 } 997 IntSize zeroSpread(0, 0); 998 RefPtr<DrawTarget> minDrawTarget = 999 InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, 1000 CalculateBlurSigma(aBlurRadius), nullptr, nullptr, true); 1001 if (!minDrawTarget) { 1002 return nullptr; 1003 } 1004 1005 // This is really annoying. When we create the GaussianBlur, the DrawTarget 1006 // has a translation applied to it that is the topLeft point. This is actually 1007 // the rect we gave it plus the blur radius. The rects we give this for the 1008 // outer and whitespace rects are based at (0, 0). We could either translate 1009 // those rects when we don't have a destination rect or ignore the translation 1010 // when using the dest rect. The dest rects layout gives us expect this 1011 // translation. 1012 if (!aIsDestRect) { 1013 minDrawTarget->SetTransform(Matrix()); 1014 } 1015 1016 // Fill in the path between the inside white space / outer rects 1017 // NOT the inner frame 1018 RefPtr<Path> maskPath = GetBoxShadowInsetPath( 1019 minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii); 1020 1021 ColorPattern black(DeviceColor::MaskOpaqueBlack()); 1022 minDrawTarget->Fill(maskPath, black); 1023 1024 // Blur and fill in with the color we actually wanted 1025 RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor); 1026 if (!minInsetBlur) { 1027 return nullptr; 1028 } 1029 1030 if (RefPtr<SourceSurface> opt = 1031 aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) { 1032 minInsetBlur = opt; 1033 } 1034 1035 if (!aIsDestRect) { 1036 CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, 1037 aShadowColor, aDestDrawTarget->GetBackendType(), 1038 minInsetBlur); 1039 } 1040 1041 return minInsetBlur.forget(); 1042 } 1043 1044 /*** 1045 * We create our minimal rect with 2 rects. 1046 * The first is the inside whitespace rect, that is "cut out" 1047 * from the box. This is (1). This must be the size 1048 * of the blur radius + corner radius so we can have a big enough 1049 * inside cut. 1050 * 1051 * The second (2) is one blur radius surrounding the inner 1052 * frame of (1). This is the amount of blur space required 1053 * to get a proper blend. 1054 * 1055 * B = one blur size 1056 * W = one blur + corner radii - known as inner margin 1057 * ___________________________________ 1058 * | | 1059 * | | | | 1060 * | (2) | (1) | (2) | 1061 * | B | W | B | 1062 * | | | | 1063 * | | | | 1064 * | | | 1065 * |________________________________| 1066 */ 1067 static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii, 1068 const IntSize& aBlurRadius, Margin& aOutBlurMargin, 1069 Margin& aOutInnerMargin) { 1070 Size cornerSize(0, 0); 1071 if (aInnerClipRadii) { 1072 const RectCornerRadii& corners = *aInnerClipRadii; 1073 for (const auto i : mozilla::AllPhysicalCorners()) { 1074 cornerSize.width = std::max(cornerSize.width, corners[i].width); 1075 cornerSize.height = std::max(cornerSize.height, corners[i].height); 1076 } 1077 } 1078 1079 // Only the inside whitespace size cares about the border radius size. 1080 // Outer sizes only care about blur. 1081 IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; 1082 1083 aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height, 1084 margin.width); 1085 aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width, 1086 aBlurRadius.height, aBlurRadius.width); 1087 } 1088 1089 static bool GetInsetBoxShadowRects(const Margin& aBlurMargin, 1090 const Margin& aInnerMargin, 1091 const Rect& aShadowClipRect, 1092 const Rect& aDestinationRect, 1093 Rect& aOutWhitespaceRect, 1094 Rect& aOutOuterRect) { 1095 // We always copy (2 * blur radius) + corner radius worth of data to the 1096 // destination rect This covers the blend of the path + the actual blur Need 1097 // +1 so that we copy the edges correctly as we'll copy over the min box 1098 // shadow corners then the +1 for the edges between Note, the (x,y) 1099 // coordinates are from the blur margin since the frame outside the whitespace 1100 // rect is 1 blur radius extra space. 1101 Rect insideWhiteSpace(aBlurMargin.left, aBlurMargin.top, 1102 aInnerMargin.LeftRight() + 1, 1103 aInnerMargin.TopBottom() + 1); 1104 1105 // If the inner white space rect is larger than the shadow clip rect 1106 // our approach does not work as we'll just copy one corner 1107 // and cover the destination. In those cases, fallback to the destination rect 1108 bool useDestRect = (aShadowClipRect.Width() <= aInnerMargin.LeftRight()) || 1109 (aShadowClipRect.Height() <= aInnerMargin.TopBottom()); 1110 1111 if (useDestRect) { 1112 aOutWhitespaceRect = aShadowClipRect; 1113 aOutOuterRect = aDestinationRect; 1114 } else { 1115 aOutWhitespaceRect = insideWhiteSpace; 1116 aOutOuterRect = aOutWhitespaceRect; 1117 aOutOuterRect.Inflate(aBlurMargin); 1118 } 1119 1120 return useDestRect; 1121 } 1122 1123 void gfxGaussianBlur::BlurInsetBox( 1124 gfxContext* aDestinationCtx, const Rect& aDestinationRect, 1125 const Rect& aShadowClipRect, const IntSize& aBlurRadius, 1126 const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii, 1127 const Rect& aSkipRect, const Point& aShadowOffset) { 1128 if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) || 1129 aShadowClipRect.IsEmpty()) { 1130 FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect, 1131 aShadowColor, aInnerClipRadii); 1132 return; 1133 } 1134 1135 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget(); 1136 1137 Margin innerMargin; 1138 Margin blurMargin; 1139 GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin); 1140 1141 Rect whitespaceRect; 1142 Rect outerRect; 1143 bool useDestRect = 1144 GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect, 1145 aDestinationRect, whitespaceRect, outerRect); 1146 1147 // Check that the inset margin between the outer and whitespace rects is 1148 // symmetric, and that all corner radii are the same, in which case the blur 1149 // can be mirrored. 1150 Margin checkMargin = outerRect - whitespaceRect; 1151 bool mirrorCorners = checkMargin.left == checkMargin.right && 1152 checkMargin.top == checkMargin.bottom && 1153 (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame()); 1154 RefPtr<SourceSurface> minBlur = 1155 GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor, 1156 aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners); 1157 if (!minBlur) { 1158 return; 1159 } 1160 1161 if (useDestRect) { 1162 Rect destBlur = aDestinationRect; 1163 destBlur.Inflate(blurMargin); 1164 if (mirrorCorners) { 1165 DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur); 1166 } else { 1167 Rect srcBlur(Point(0, 0), Size(minBlur->GetSize())); 1168 MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(destBlur).Size()); 1169 destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur); 1170 } 1171 } else { 1172 Rect srcOuter(outerRect); 1173 Rect srcInner(srcOuter); 1174 srcInner.Deflate(blurMargin); // The outer color fill 1175 srcInner.Deflate(innerMargin); // The inner whitespace 1176 1177 // The shadow clip rect already takes into account the spread radius 1178 Rect outerFillRect(aShadowClipRect); 1179 outerFillRect.Inflate(blurMargin); 1180 FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect, 1181 aShadowColor); 1182 1183 // Inflate once for the frame around the whitespace 1184 Rect destRect(aShadowClipRect); 1185 destRect.Inflate(blurMargin); 1186 1187 // Deflate for the blurred in white space 1188 Rect destInnerRect(aShadowClipRect); 1189 destInnerRect.Deflate(innerMargin); 1190 1191 if (mirrorCorners) { 1192 DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, 1193 srcOuter, srcInner, aSkipRect); 1194 } else { 1195 DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect, 1196 srcOuter, srcInner, aSkipRect); 1197 } 1198 } 1199 }