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
RegisterEntry(UniquePtr<BlurCacheData> aValue)376 void RegisterEntry(UniquePtr<BlurCacheData> aValue) {
377 nsresult rv = AddObject(aValue.get());
378 if (NS_FAILED(rv)) {
379 // We are OOM, and we cannot track this object. We don't want stall
380 // entries in the hash table (since the expiration tracker is responsible
381 // for removing the cache entries), so we avoid putting that entry in the
382 // table, which is a good thing considering we are short on memory
383 // anyway, we probably don't want to retain things.
384 return;
385 }
386 mHashEntries.InsertOrUpdate(aValue->mKey, std::move(aValue));
387 }
388
389 protected:
390 static const uint32_t GENERATION_MS = 1000;
391 /**
392 * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
393 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
394 */
395 nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
396 };
397
398 static BlurCache* gBlurCache = nullptr;
399
ComputeMinSizeForShadowShape(const RectCornerRadii * aCornerRadii,const IntSize & aBlurRadius,IntMargin & aOutSlice,const IntSize & aRectSize)400 static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii,
401 const IntSize& aBlurRadius,
402 IntMargin& aOutSlice,
403 const IntSize& aRectSize) {
404 Size cornerSize(0, 0);
405 if (aCornerRadii) {
406 const RectCornerRadii& corners = *aCornerRadii;
407 for (const auto i : mozilla::AllPhysicalCorners()) {
408 cornerSize.width = std::max(cornerSize.width, corners[i].width);
409 cornerSize.height = std::max(cornerSize.height, corners[i].height);
410 }
411 }
412
413 IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
414 aOutSlice =
415 IntMargin(margin.height, margin.width, margin.height, margin.width);
416
417 IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1);
418
419 // If aRectSize is smaller than minSize, the border-image approach won't
420 // work; there's no way to squeeze parts of the min box-shadow source
421 // image such that the result looks correct. So we need to adjust minSize
422 // in such a way that we can later draw it without stretching in the affected
423 // dimension. We also need to adjust "slice" to ensure that we're not trying
424 // to slice away more than we have.
425 if (aRectSize.width < minSize.width) {
426 minSize.width = aRectSize.width;
427 aOutSlice.left = 0;
428 aOutSlice.right = 0;
429 }
430 if (aRectSize.height < minSize.height) {
431 minSize.height = aRectSize.height;
432 aOutSlice.top = 0;
433 aOutSlice.bottom = 0;
434 }
435
436 MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width);
437 MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height);
438 return minSize;
439 }
440
CacheBlur(DrawTarget * aDT,const IntSize & aMinSize,const IntSize & aBlurRadius,const RectCornerRadii * aCornerRadii,const sRGBColor & aShadowColor,const IntMargin & aBlurMargin,SourceSurface * aBoxShadow)441 static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize,
442 const IntSize& aBlurRadius,
443 const RectCornerRadii* aCornerRadii,
444 const sRGBColor& aShadowColor,
445 const IntMargin& aBlurMargin, SourceSurface* aBoxShadow) {
446 gBlurCache->RegisterEntry(MakeUnique<BlurCacheData>(
447 aBoxShadow, aBlurMargin,
448 BlurCacheKey(aMinSize, aBlurRadius, aCornerRadii, aShadowColor,
449 aDT->GetBackendType())));
450 }
451
452 // 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)453 static already_AddRefed<SourceSurface> CreateBoxShadow(
454 DrawTarget* aDestDrawTarget, const IntSize& aMinSize,
455 const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius,
456 const sRGBColor& aShadowColor, bool aMirrorCorners,
457 IntMargin& aOutBlurMargin) {
458 gfxAlphaBoxBlur blur;
459 Rect minRect(Point(0, 0), Size(aMinSize));
460 Rect blurRect(minRect);
461 // If mirroring corners, we only need to draw the top-left quadrant.
462 // Use ceil to preserve the remaining 1x1 middle area for minimized box
463 // shadows.
464 if (aMirrorCorners) {
465 blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
466 ceil(blurRect.Height() * 0.5f));
467 }
468 IntSize zeroSpread(0, 0);
469 RefPtr<DrawTarget> blurDT =
470 blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
471 if (!blurDT) {
472 return nullptr;
473 }
474
475 ColorPattern black(DeviceColor::MaskOpaqueBlack());
476
477 if (aCornerRadii) {
478 RefPtr<Path> roundedRect =
479 MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii);
480 blurDT->Fill(roundedRect, black);
481 } else {
482 blurDT->FillRect(minRect, black);
483 }
484
485 IntPoint topLeft;
486 RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft);
487 if (!result) {
488 return nullptr;
489 }
490
491 // Since blurRect is at (0, 0), we can find the inflated margin by
492 // negating the new rect origin, which would have been negative if
493 // the rect was inflated.
494 aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x);
495
496 return result.forget();
497 }
498
GetBlur(gfxContext * aDestinationCtx,const IntSize & aRectSize,const IntSize & aBlurRadius,const RectCornerRadii * aCornerRadii,const sRGBColor & aShadowColor,bool aMirrorCorners,IntMargin & aOutBlurMargin,IntMargin & aOutSlice,IntSize & aOutMinSize)499 static already_AddRefed<SourceSurface> GetBlur(
500 gfxContext* aDestinationCtx, const IntSize& aRectSize,
501 const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii,
502 const sRGBColor& aShadowColor, bool aMirrorCorners,
503 IntMargin& aOutBlurMargin, IntMargin& aOutSlice, IntSize& aOutMinSize) {
504 if (!gBlurCache) {
505 gBlurCache = new BlurCache();
506 }
507
508 IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius,
509 aOutSlice, aRectSize);
510
511 // We can get seams using the min size rect when drawing to the destination
512 // rect if we have a non-pixel aligned destination transformation. In those
513 // cases, fallback to just rendering the destination rect. During printing, we
514 // record all the Moz 2d commands and replay them on the parent side with
515 // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However,
516 // since our source image is only 1px for some parts, we make thousands of
517 // calls. Instead just render the blur ourself here as one image and send it
518 // over for printing.
519 // TODO: May need to change this with the blob renderer in WR since it also
520 // records.
521 Matrix destMatrix = aDestinationCtx->CurrentMatrix();
522 bool useDestRect = !destMatrix.IsRectilinear() ||
523 destMatrix.HasNonIntegerTranslation() ||
524 aDestinationCtx->GetDrawTarget()->IsRecording();
525 if (useDestRect) {
526 minSize = aRectSize;
527 }
528
529 int32_t maxTextureSize = gfxPlatform::MaxTextureSize();
530 if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) {
531 return nullptr;
532 }
533
534 aOutMinSize = minSize;
535
536 DrawTarget* destDT = aDestinationCtx->GetDrawTarget();
537
538 if (!useDestRect) {
539 BlurCacheData* cached =
540 gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor,
541 destDT->GetBackendType());
542 if (cached) {
543 // See CreateBoxShadow() for these values
544 aOutBlurMargin = cached->mBlurMargin;
545 RefPtr<SourceSurface> blur = cached->mBlur;
546 return blur.forget();
547 }
548 }
549
550 RefPtr<SourceSurface> boxShadow =
551 CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor,
552 aMirrorCorners, aOutBlurMargin);
553 if (!boxShadow) {
554 return nullptr;
555 }
556
557 if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) {
558 boxShadow = opt;
559 }
560
561 if (!useDestRect) {
562 CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
563 aOutBlurMargin, boxShadow);
564 }
565 return boxShadow.forget();
566 }
567
ShutdownBlurCache()568 void gfxAlphaBoxBlur::ShutdownBlurCache() {
569 delete gBlurCache;
570 gBlurCache = nullptr;
571 }
572
RectWithEdgesTRBL(Float aTop,Float aRight,Float aBottom,Float aLeft)573 static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom,
574 Float aLeft) {
575 return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
576 }
577
ShouldStretchSurface(DrawTarget * aDT,SourceSurface * aSurface)578 static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) {
579 // Use stretching if possible, since it leads to less seams when the
580 // destination is transformed. However, don't do this if we're using cairo,
581 // because if cairo is using pixman it won't render anything for large
582 // stretch factors because pixman's internal fixed point precision is not
583 // high enough to handle those scale factors.
584 return aDT->GetBackendType() != BackendType::CAIRO;
585 }
586
RepeatOrStretchSurface(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDest,const Rect & aSrc,const Rect & aSkipRect)587 static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface,
588 const Rect& aDest, const Rect& aSrc,
589 const Rect& aSkipRect) {
590 if (aSkipRect.Contains(aDest)) {
591 return;
592 }
593
594 if (ShouldStretchSurface(aDT, aSurface)) {
595 aDT->DrawSurface(aSurface, aDest, aSrc);
596 return;
597 }
598
599 SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
600 Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
601 SamplingFilter::GOOD, RoundedToInt(aSrc));
602 aDT->FillRect(aDest, pattern);
603 }
604
DrawCorner(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDest,const Rect & aSrc,const Rect & aSkipRect)605 static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface,
606 const Rect& aDest, const Rect& aSrc,
607 const Rect& aSkipRect) {
608 if (aSkipRect.Contains(aDest)) {
609 return;
610 }
611
612 aDT->DrawSurface(aSurface, aDest, aSrc);
613 }
614
DrawMinBoxShadow(DrawTarget * aDestDrawTarget,SourceSurface * aSourceBlur,const Rect & aDstOuter,const Rect & aDstInner,const Rect & aSrcOuter,const Rect & aSrcInner,const Rect & aSkipRect,bool aMiddle=false)615 static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget,
616 SourceSurface* aSourceBlur, const Rect& aDstOuter,
617 const Rect& aDstInner, const Rect& aSrcOuter,
618 const Rect& aSrcInner, const Rect& aSkipRect,
619 bool aMiddle = false) {
620 // Corners: top left, top right, bottom left, bottom right
621 DrawCorner(aDestDrawTarget, aSourceBlur,
622 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(),
623 aDstOuter.X()),
624 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(),
625 aSrcOuter.X()),
626 aSkipRect);
627
628 DrawCorner(aDestDrawTarget, aSourceBlur,
629 RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(),
630 aDstInner.XMost()),
631 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(),
632 aSrcInner.XMost()),
633 aSkipRect);
634
635 DrawCorner(aDestDrawTarget, aSourceBlur,
636 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
637 aDstOuter.YMost(), aDstOuter.X()),
638 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
639 aSrcOuter.YMost(), aSrcOuter.X()),
640 aSkipRect);
641
642 DrawCorner(aDestDrawTarget, aSourceBlur,
643 RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
644 aDstOuter.YMost(), aDstInner.XMost()),
645 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
646 aSrcOuter.YMost(), aSrcInner.XMost()),
647 aSkipRect);
648
649 // Edges: top, left, right, bottom
650 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
651 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
652 aDstInner.Y(), aDstInner.X()),
653 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
654 aSrcInner.Y(), aSrcInner.X()),
655 aSkipRect);
656 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
657 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
658 aDstInner.YMost(), aDstOuter.X()),
659 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
660 aSrcInner.YMost(), aSrcOuter.X()),
661 aSkipRect);
662
663 RepeatOrStretchSurface(
664 aDestDrawTarget, aSourceBlur,
665 RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(),
666 aDstInner.XMost()),
667 RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(),
668 aSrcInner.XMost()),
669 aSkipRect);
670 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
671 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
672 aDstOuter.YMost(), aDstInner.X()),
673 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
674 aSrcOuter.YMost(), aSrcInner.X()),
675 aSkipRect);
676
677 // Middle part
678 if (aMiddle) {
679 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
680 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
681 aDstInner.YMost(), aDstInner.X()),
682 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
683 aSrcInner.YMost(), aSrcInner.X()),
684 aSkipRect);
685 }
686 }
687
DrawMirroredRect(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDest,const Point & aSrc,Float aScaleX,Float aScaleY)688 static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface,
689 const Rect& aDest, const Point& aSrc,
690 Float aScaleX, Float aScaleY) {
691 SurfacePattern pattern(
692 aSurface, ExtendMode::CLAMP,
693 Matrix::Scaling(aScaleX, aScaleY)
694 .PreTranslate(-aSrc)
695 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
696 aScaleY < 0 ? aDest.YMost() : aDest.Y()));
697 aDT->FillRect(aDest, pattern);
698 }
699
DrawMirroredBoxShadow(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDestRect)700 static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface,
701 const Rect& aDestRect) {
702 Point center(ceil(aDestRect.X() + aDestRect.Width() / 2),
703 ceil(aDestRect.Y() + aDestRect.Height() / 2));
704 Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(),
705 center.y - aDestRect.Y());
706 Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size());
707 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
708 topLeft.Height());
709 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
710 bottomRight.Height());
711 DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1);
712 DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1);
713 DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1);
714 DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1);
715 }
716
DrawMirroredCorner(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDest,const Point & aSrc,const Rect & aSkipRect,Float aScaleX,Float aScaleY)717 static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface,
718 const Rect& aDest, const Point& aSrc,
719 const Rect& aSkipRect, Float aScaleX,
720 Float aScaleY) {
721 if (aSkipRect.Contains(aDest)) {
722 return;
723 }
724
725 DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY);
726 }
727
RepeatOrStretchMirroredSurface(DrawTarget * aDT,SourceSurface * aSurface,const Rect & aDest,const Rect & aSrc,const Rect & aSkipRect,Float aScaleX,Float aScaleY)728 static void RepeatOrStretchMirroredSurface(DrawTarget* aDT,
729 SourceSurface* aSurface,
730 const Rect& aDest, const Rect& aSrc,
731 const Rect& aSkipRect, Float aScaleX,
732 Float aScaleY) {
733 if (aSkipRect.Contains(aDest)) {
734 return;
735 }
736
737 if (ShouldStretchSurface(aDT, aSurface)) {
738 aScaleX *= aDest.Width() / aSrc.Width();
739 aScaleY *= aDest.Height() / aSrc.Height();
740 DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY);
741 return;
742 }
743
744 SurfacePattern pattern(
745 aSurface, ExtendMode::REPEAT,
746 Matrix::Scaling(aScaleX, aScaleY)
747 .PreTranslate(-aSrc.TopLeft())
748 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
749 aScaleY < 0 ? aDest.YMost() : aDest.Y()),
750 SamplingFilter::GOOD, RoundedToInt(aSrc));
751 aDT->FillRect(aDest, pattern);
752 }
753
DrawMirroredMinBoxShadow(DrawTarget * aDestDrawTarget,SourceSurface * aSourceBlur,const Rect & aDstOuter,const Rect & aDstInner,const Rect & aSrcOuter,const Rect & aSrcInner,const Rect & aSkipRect,bool aMiddle=false)754 static void DrawMirroredMinBoxShadow(
755 DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur,
756 const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter,
757 const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) {
758 // Corners: top left, top right, bottom left, bottom right
759 // Compute quadrant bounds and then clip them to corners along
760 // dimensions where we need to stretch from min size.
761 Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2),
762 ceil(aDstOuter.Y() + aDstOuter.Height() / 2));
763 Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(),
764 center.y - aDstOuter.Y());
765 Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size());
766 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
767 topLeft.Height());
768 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
769 bottomRight.Height());
770
771 // Check if the middle part has been minimized along each dimension.
772 // If so, those will be strecthed/drawn separately and need to be clipped out.
773 if (aSrcInner.Width() == 1) {
774 topLeft.SetRightEdge(aDstInner.X());
775 topRight.SetLeftEdge(aDstInner.XMost());
776 bottomLeft.SetRightEdge(aDstInner.X());
777 bottomRight.SetLeftEdge(aDstInner.XMost());
778 }
779 if (aSrcInner.Height() == 1) {
780 topLeft.SetBottomEdge(aDstInner.Y());
781 topRight.SetBottomEdge(aDstInner.Y());
782 bottomLeft.SetTopEdge(aDstInner.YMost());
783 bottomRight.SetTopEdge(aDstInner.YMost());
784 }
785
786 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(),
787 aSkipRect, 1, 1);
788 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight,
789 aSrcOuter.TopLeft(), aSkipRect, -1, 1);
790 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft,
791 aSrcOuter.TopLeft(), aSkipRect, 1, -1);
792 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight,
793 aSrcOuter.TopLeft(), aSkipRect, -1, -1);
794
795 // Edges: top, bottom, left, right
796 // Draw middle edges where they need to be stretched. The top and left
797 // sections that are part of the top-left quadrant will be mirrored to
798 // the bottom and right sections, respectively.
799 if (aSrcInner.Width() == 1) {
800 Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
801 aDstInner.Y(), aDstInner.X());
802 Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
803 aSrcInner.Y(), aSrcInner.X());
804 Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
805 aDstOuter.YMost(), aDstInner.X());
806 Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
807 aSrcInner.Y(), aSrcInner.X());
808 // If we only need to stretch along the X axis and we're drawing
809 // the middle section, just sample all the way to the center of the
810 // source on the Y axis to avoid extra draw calls.
811 if (aMiddle && aSrcInner.Height() != 1) {
812 dstTop.SetBottomEdge(center.y);
813 srcTop.SetHeight(dstTop.Height());
814 dstBottom.SetTopEdge(dstTop.YMost());
815 srcBottom.SetHeight(dstBottom.Height());
816 }
817 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop,
818 aSkipRect, 1, 1);
819 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom,
820 srcBottom, aSkipRect, 1, -1);
821 }
822
823 if (aSrcInner.Height() == 1) {
824 Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
825 aDstInner.YMost(), aDstOuter.X());
826 Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
827 aSrcInner.YMost(), aSrcOuter.X());
828 Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
829 aDstInner.YMost(), aDstInner.XMost());
830 Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
831 aSrcInner.YMost(), aSrcOuter.X());
832 // Only stretching on Y axis, so sample source to the center of the X axis.
833 if (aMiddle && aSrcInner.Width() != 1) {
834 dstLeft.SetRightEdge(center.x);
835 srcLeft.SetWidth(dstLeft.Width());
836 dstRight.SetLeftEdge(dstLeft.XMost());
837 srcRight.SetWidth(dstRight.Width());
838 }
839 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft,
840 srcLeft, aSkipRect, 1, 1);
841 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight,
842 srcRight, aSkipRect, -1, 1);
843 }
844
845 // If we need to stretch along both dimensions, then the middle part
846 // must be drawn separately.
847 if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) {
848 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
849 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
850 aDstInner.YMost(), aDstInner.X()),
851 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
852 aSrcInner.YMost(), aSrcInner.X()),
853 aSkipRect);
854 }
855 }
856
857 /***
858 * We draw a blurred a rectangle by only blurring a smaller rectangle and
859 * splitting the rectangle into 9 parts.
860 * First, a small minimum source rect is calculated and used to create a blur
861 * mask since the actual blurring itself is expensive. Next, we use the mask
862 * with the given shadow color to create a minimally-sized box shadow of the
863 * right color. Finally, we cut out the 9 parts from the box-shadow source and
864 * paint each part in the right place, stretching the non-corner parts to fill
865 * the space between the corners.
866 */
867
868 /* static */
BlurRectangle(gfxContext * aDestinationCtx,const gfxRect & aRect,const RectCornerRadii * aCornerRadii,const gfxPoint & aBlurStdDev,const sRGBColor & aShadowColor,const gfxRect & aDirtyRect,const gfxRect & aSkipRect)869 void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
870 const gfxRect& aRect,
871 const RectCornerRadii* aCornerRadii,
872 const gfxPoint& aBlurStdDev,
873 const sRGBColor& aShadowColor,
874 const gfxRect& aDirtyRect,
875 const gfxRect& aSkipRect) {
876 if (!RectIsInt32Safe(ToRect(aRect))) {
877 return;
878 }
879
880 IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
881 bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame();
882
883 IntRect rect = RoundedToInt(ToRect(aRect));
884 IntMargin blurMargin;
885 IntMargin slice;
886 IntSize minSize;
887 RefPtr<SourceSurface> boxShadow =
888 GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii,
889 aShadowColor, mirrorCorners, blurMargin, slice, minSize);
890 if (!boxShadow) {
891 return;
892 }
893
894 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
895 destDrawTarget->PushClipRect(ToRect(aDirtyRect));
896
897 // Copy the right parts from boxShadow into destDrawTarget. The middle parts
898 // will be stretched, border-image style.
899
900 Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize));
901 Rect srcInner(srcOuter);
902 srcOuter.Inflate(Margin(blurMargin));
903 srcInner.Deflate(Margin(slice));
904
905 Rect dstOuter(rect);
906 Rect dstInner(rect);
907 dstOuter.Inflate(Margin(blurMargin));
908 dstInner.Deflate(Margin(slice));
909
910 Rect skipRect = ToRect(aSkipRect);
911
912 if (minSize == rect.Size()) {
913 // The target rect is smaller than the minimal size so just draw the surface
914 if (mirrorCorners) {
915 DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter);
916 } else {
917 destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter);
918 }
919 } else {
920 if (mirrorCorners) {
921 DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner,
922 srcOuter, srcInner, skipRect, true);
923 } else {
924 DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter,
925 srcInner, skipRect, true);
926 }
927 }
928
929 // A note about anti-aliasing and seems between adjacent parts:
930 // We don't explicitly disable anti-aliasing in the DrawSurface calls above,
931 // so if there's a transform on destDrawTarget that is not pixel-aligned,
932 // there will be seams between adjacent parts of the box-shadow. It's hard to
933 // avoid those without the use of an intermediate surface.
934 // You might think that we could avoid those by just turning off AA, but there
935 // is a problem with that: Box-shadow rendering needs to clip out the
936 // element's border box, and we'd like that clip to have anti-aliasing -
937 // especially if the element has rounded corners! So we can't do that unless
938 // we have a way to say "Please anti-alias the clip, but don't antialias the
939 // destination rect of the DrawSurface call".
940
941 destDrawTarget->PopClip();
942 }
943
GetBoxShadowInsetPath(DrawTarget * aDrawTarget,const Rect aOuterRect,const Rect aInnerRect,const RectCornerRadii * aInnerClipRadii)944 static already_AddRefed<Path> GetBoxShadowInsetPath(
945 DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect,
946 const RectCornerRadii* aInnerClipRadii) {
947 /***
948 * We create an inset path by having two rects.
949 *
950 * -----------------------
951 * | ________________ |
952 * | | | |
953 * | | | |
954 * | ------------------ |
955 * |_____________________|
956 *
957 * The outer rect and the inside rect. The path
958 * creates a frame around the content where we draw the inset shadow.
959 */
960 RefPtr<PathBuilder> builder =
961 aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
962 AppendRectToPath(builder, aOuterRect, true);
963
964 if (aInnerClipRadii) {
965 AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false);
966 } else {
967 AppendRectToPath(builder, aInnerRect, false);
968 }
969 return builder->Finish();
970 }
971
FillDestinationPath(gfxContext * aDestinationCtx,const Rect & aDestinationRect,const Rect & aShadowClipRect,const sRGBColor & aShadowColor,const RectCornerRadii * aInnerClipRadii=nullptr)972 static void FillDestinationPath(
973 gfxContext* aDestinationCtx, const Rect& aDestinationRect,
974 const Rect& aShadowClipRect, const sRGBColor& aShadowColor,
975 const RectCornerRadii* aInnerClipRadii = nullptr) {
976 // When there is no blur radius, fill the path onto the destination
977 // surface.
978 aDestinationCtx->SetColor(aShadowColor);
979 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
980 RefPtr<Path> shadowPath = GetBoxShadowInsetPath(
981 destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii);
982
983 aDestinationCtx->SetPath(shadowPath);
984 aDestinationCtx->Fill();
985 }
986
CacheInsetBlur(const IntSize & aMinOuterSize,const IntSize & aMinInnerSize,const IntSize & aBlurRadius,const RectCornerRadii * aCornerRadii,const sRGBColor & aShadowColor,BackendType aBackendType,SourceSurface * aBoxShadow)987 static void CacheInsetBlur(const IntSize& aMinOuterSize,
988 const IntSize& aMinInnerSize,
989 const IntSize& aBlurRadius,
990 const RectCornerRadii* aCornerRadii,
991 const sRGBColor& aShadowColor,
992 BackendType aBackendType,
993 SourceSurface* aBoxShadow) {
994 bool isInsetBlur = true;
995 BlurCacheKey key(aMinOuterSize, aMinInnerSize, aBlurRadius, aCornerRadii,
996 aShadowColor, isInsetBlur, aBackendType);
997 IntMargin blurMargin(0, 0, 0, 0);
998
999 gBlurCache->RegisterEntry(
1000 MakeUnique<BlurCacheData>(aBoxShadow, blurMargin, std::move(key)));
1001 }
1002
GetInsetBlur(const Rect & aOuterRect,const Rect & aWhitespaceRect,bool aIsDestRect,const sRGBColor & aShadowColor,const IntSize & aBlurRadius,const RectCornerRadii * aInnerClipRadii,DrawTarget * aDestDrawTarget,bool aMirrorCorners)1003 already_AddRefed<SourceSurface> gfxAlphaBoxBlur::GetInsetBlur(
1004 const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect,
1005 const sRGBColor& aShadowColor, const IntSize& aBlurRadius,
1006 const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget,
1007 bool aMirrorCorners) {
1008 if (!gBlurCache) {
1009 gBlurCache = new BlurCache();
1010 }
1011
1012 IntSize outerSize = IntSize::Truncate(aOuterRect.Size());
1013 IntSize whitespaceSize = IntSize::Truncate(aWhitespaceRect.Size());
1014 if (!aIsDestRect) {
1015 BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(
1016 outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii, aShadowColor,
1017 aDestDrawTarget->GetBackendType());
1018 if (cached) {
1019 // So we don't forget the actual cached blur
1020 RefPtr<SourceSurface> cachedBlur = cached->mBlur;
1021 return cachedBlur.forget();
1022 }
1023 }
1024
1025 // If we can do a min rect, the whitespace rect will be expanded in Init to
1026 // aOuterRect.
1027 Rect blurRect = aIsDestRect ? aOuterRect : aWhitespaceRect;
1028 // If mirroring corners, we only need to draw the top-left quadrant.
1029 // Use ceil to preserve the remaining 1x1 middle area for minimized box
1030 // shadows.
1031 if (aMirrorCorners) {
1032 blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
1033 ceil(blurRect.Height() * 0.5f));
1034 }
1035 IntSize zeroSpread(0, 0);
1036 RefPtr<DrawTarget> minDrawTarget =
1037 InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
1038 if (!minDrawTarget) {
1039 return nullptr;
1040 }
1041
1042 // This is really annoying. When we create the AlphaBoxBlur, the DrawTarget
1043 // has a translation applied to it that is the topLeft point. This is actually
1044 // the rect we gave it plus the blur radius. The rects we give this for the
1045 // outer and whitespace rects are based at (0, 0). We could either translate
1046 // those rects when we don't have a destination rect or ignore the translation
1047 // when using the dest rect. The dest rects layout gives us expect this
1048 // translation.
1049 if (!aIsDestRect) {
1050 minDrawTarget->SetTransform(Matrix());
1051 }
1052
1053 // Fill in the path between the inside white space / outer rects
1054 // NOT the inner frame
1055 RefPtr<Path> maskPath = GetBoxShadowInsetPath(
1056 minDrawTarget, aOuterRect, aWhitespaceRect, aInnerClipRadii);
1057
1058 ColorPattern black(DeviceColor::MaskOpaqueBlack());
1059 minDrawTarget->Fill(maskPath, black);
1060
1061 // Blur and fill in with the color we actually wanted
1062 RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor);
1063 if (!minInsetBlur) {
1064 return nullptr;
1065 }
1066
1067 if (RefPtr<SourceSurface> opt =
1068 aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) {
1069 minInsetBlur = opt;
1070 }
1071
1072 if (!aIsDestRect) {
1073 CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii,
1074 aShadowColor, aDestDrawTarget->GetBackendType(),
1075 minInsetBlur);
1076 }
1077
1078 return minInsetBlur.forget();
1079 }
1080
1081 /***
1082 * We create our minimal rect with 2 rects.
1083 * The first is the inside whitespace rect, that is "cut out"
1084 * from the box. This is (1). This must be the size
1085 * of the blur radius + corner radius so we can have a big enough
1086 * inside cut.
1087 *
1088 * The second (2) is one blur radius surrounding the inner
1089 * frame of (1). This is the amount of blur space required
1090 * to get a proper blend.
1091 *
1092 * B = one blur size
1093 * W = one blur + corner radii - known as inner margin
1094 * ___________________________________
1095 * | |
1096 * | | | |
1097 * | (2) | (1) | (2) |
1098 * | B | W | B |
1099 * | | | |
1100 * | | | |
1101 * | | |
1102 * |________________________________|
1103 */
GetBlurMargins(const RectCornerRadii * aInnerClipRadii,const IntSize & aBlurRadius,Margin & aOutBlurMargin,Margin & aOutInnerMargin)1104 static void GetBlurMargins(const RectCornerRadii* aInnerClipRadii,
1105 const IntSize& aBlurRadius, Margin& aOutBlurMargin,
1106 Margin& aOutInnerMargin) {
1107 Size cornerSize(0, 0);
1108 if (aInnerClipRadii) {
1109 const RectCornerRadii& corners = *aInnerClipRadii;
1110 for (const auto i : mozilla::AllPhysicalCorners()) {
1111 cornerSize.width = std::max(cornerSize.width, corners[i].width);
1112 cornerSize.height = std::max(cornerSize.height, corners[i].height);
1113 }
1114 }
1115
1116 // Only the inside whitespace size cares about the border radius size.
1117 // Outer sizes only care about blur.
1118 IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
1119
1120 aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height,
1121 margin.width);
1122 aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width,
1123 aBlurRadius.height, aBlurRadius.width);
1124 }
1125
GetInsetBoxShadowRects(const Margin & aBlurMargin,const Margin & aInnerMargin,const Rect & aShadowClipRect,const Rect & aDestinationRect,Rect & aOutWhitespaceRect,Rect & aOutOuterRect)1126 static bool GetInsetBoxShadowRects(const Margin& aBlurMargin,
1127 const Margin& aInnerMargin,
1128 const Rect& aShadowClipRect,
1129 const Rect& aDestinationRect,
1130 Rect& aOutWhitespaceRect,
1131 Rect& aOutOuterRect) {
1132 // We always copy (2 * blur radius) + corner radius worth of data to the
1133 // destination rect This covers the blend of the path + the actual blur Need
1134 // +1 so that we copy the edges correctly as we'll copy over the min box
1135 // shadow corners then the +1 for the edges between Note, the (x,y)
1136 // coordinates are from the blur margin since the frame outside the whitespace
1137 // rect is 1 blur radius extra space.
1138 Rect insideWhiteSpace(aBlurMargin.left, aBlurMargin.top,
1139 aInnerMargin.LeftRight() + 1,
1140 aInnerMargin.TopBottom() + 1);
1141
1142 // If the inner white space rect is larger than the shadow clip rect
1143 // our approach does not work as we'll just copy one corner
1144 // and cover the destination. In those cases, fallback to the destination rect
1145 bool useDestRect = (aShadowClipRect.Width() <= aInnerMargin.LeftRight()) ||
1146 (aShadowClipRect.Height() <= aInnerMargin.TopBottom());
1147
1148 if (useDestRect) {
1149 aOutWhitespaceRect = aShadowClipRect;
1150 aOutOuterRect = aDestinationRect;
1151 } else {
1152 aOutWhitespaceRect = insideWhiteSpace;
1153 aOutOuterRect = aOutWhitespaceRect;
1154 aOutOuterRect.Inflate(aBlurMargin);
1155 }
1156
1157 return useDestRect;
1158 }
1159
BlurInsetBox(gfxContext * aDestinationCtx,const Rect & aDestinationRect,const Rect & aShadowClipRect,const IntSize & aBlurRadius,const sRGBColor & aShadowColor,const RectCornerRadii * aInnerClipRadii,const Rect & aSkipRect,const Point & aShadowOffset)1160 void gfxAlphaBoxBlur::BlurInsetBox(
1161 gfxContext* aDestinationCtx, const Rect& aDestinationRect,
1162 const Rect& aShadowClipRect, const IntSize& aBlurRadius,
1163 const sRGBColor& aShadowColor, const RectCornerRadii* aInnerClipRadii,
1164 const Rect& aSkipRect, const Point& aShadowOffset) {
1165 if ((aBlurRadius.width == 0 && aBlurRadius.height == 0) ||
1166 aShadowClipRect.IsEmpty()) {
1167 FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
1168 aShadowColor, aInnerClipRadii);
1169 return;
1170 }
1171
1172 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
1173
1174 Margin innerMargin;
1175 Margin blurMargin;
1176 GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin);
1177
1178 Rect whitespaceRect;
1179 Rect outerRect;
1180 bool useDestRect =
1181 GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
1182 aDestinationRect, whitespaceRect, outerRect);
1183
1184 // Check that the inset margin between the outer and whitespace rects is
1185 // symmetric, and that all corner radii are the same, in which case the blur
1186 // can be mirrored.
1187 Margin checkMargin = outerRect - whitespaceRect;
1188 bool mirrorCorners = checkMargin.left == checkMargin.right &&
1189 checkMargin.top == checkMargin.bottom &&
1190 (!aInnerClipRadii || aInnerClipRadii->AreRadiiSame());
1191 RefPtr<SourceSurface> minBlur =
1192 GetInsetBlur(outerRect, whitespaceRect, useDestRect, aShadowColor,
1193 aBlurRadius, aInnerClipRadii, destDrawTarget, mirrorCorners);
1194 if (!minBlur) {
1195 return;
1196 }
1197
1198 if (useDestRect) {
1199 Rect destBlur = aDestinationRect;
1200 destBlur.Inflate(blurMargin);
1201 if (mirrorCorners) {
1202 DrawMirroredBoxShadow(destDrawTarget, minBlur.get(), destBlur);
1203 } else {
1204 Rect srcBlur(Point(0, 0), Size(minBlur->GetSize()));
1205 MOZ_ASSERT(RoundedOut(srcBlur).Size() == RoundedOut(destBlur).Size());
1206 destDrawTarget->DrawSurface(minBlur, destBlur, srcBlur);
1207 }
1208 } else {
1209 Rect srcOuter(outerRect);
1210 Rect srcInner(srcOuter);
1211 srcInner.Deflate(blurMargin); // The outer color fill
1212 srcInner.Deflate(innerMargin); // The inner whitespace
1213
1214 // The shadow clip rect already takes into account the spread radius
1215 Rect outerFillRect(aShadowClipRect);
1216 outerFillRect.Inflate(blurMargin);
1217 FillDestinationPath(aDestinationCtx, aDestinationRect, outerFillRect,
1218 aShadowColor);
1219
1220 // Inflate once for the frame around the whitespace
1221 Rect destRect(aShadowClipRect);
1222 destRect.Inflate(blurMargin);
1223
1224 // Deflate for the blurred in white space
1225 Rect destInnerRect(aShadowClipRect);
1226 destInnerRect.Deflate(innerMargin);
1227
1228 if (mirrorCorners) {
1229 DrawMirroredMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
1230 srcOuter, srcInner, aSkipRect);
1231 } else {
1232 DrawMinBoxShadow(destDrawTarget, minBlur, destRect, destInnerRect,
1233 srcOuter, srcInner, aSkipRect);
1234 }
1235 }
1236 }
1237