tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gfxBlur.cpp (46950B)


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