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