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