LCOV - code coverage report
Current view: top level - gfx/thebes - gfxBlur.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 287 580 49.5 %
Date: 2017-07-14 16:53:18 Functions: 31 46 67.4 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13