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