Coverage Report

Created: 2018-09-25 14:53

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