Bug 1567650 [wpt PR 17950] - [ElementTiming] Replace responseEnd with loadTime, a...
[gecko.git] / gfx / thebes / gfxBlur.cpp
blob9431c3bc02dae666798dcccbc3e2d73ac15890a2
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/. */
6 #include "gfxBlur.h"
8 #include "gfx2DGlue.h"
9 #include "gfxContext.h"
10 #include "gfxPlatform.h"
11 #include "mozilla/gfx/2D.h"
12 #include "mozilla/gfx/Blur.h"
13 #include "mozilla/gfx/PathHelpers.h"
14 #include "mozilla/Maybe.h"
15 #include "mozilla/SystemGroup.h"
16 #include "nsExpirationTracker.h"
17 #include "nsClassHashtable.h"
18 #include "gfxUtils.h"
19 #include <limits>
20 #include <cmath>
22 using namespace mozilla;
23 using namespace mozilla::gfx;
25 gfxAlphaBoxBlur::~gfxAlphaBoxBlur() {}
27 already_AddRefed<gfxContext> gfxAlphaBoxBlur::Init(gfxContext* aDestinationCtx,
28 const gfxRect& aRect,
29 const IntSize& aSpreadRadius,
30 const IntSize& aBlurRadius,
31 const gfxRect* aDirtyRect,
32 const gfxRect* aSkipRect,
33 bool aUseHardwareAccel) {
34 DrawTarget* refDT = aDestinationCtx->GetDrawTarget();
35 Maybe<Rect> dirtyRect = aDirtyRect ? Some(ToRect(*aDirtyRect)) : Nothing();
36 Maybe<Rect> skipRect = aSkipRect ? Some(ToRect(*aSkipRect)) : Nothing();
37 RefPtr<DrawTarget> dt = InitDrawTarget(
38 refDT, ToRect(aRect), aSpreadRadius, aBlurRadius,
39 dirtyRect.ptrOr(nullptr), skipRect.ptrOr(nullptr), aUseHardwareAccel);
40 if (!dt) {
41 return nullptr;
44 RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
45 MOZ_ASSERT(context); // already checked for target above
46 context->SetMatrix(Matrix::Translation(-mBlur.GetRect().TopLeft()));
47 return context.forget();
50 already_AddRefed<DrawTarget> gfxAlphaBoxBlur::InitDrawTarget(
51 const DrawTarget* aReferenceDT, const Rect& aRect,
52 const IntSize& aSpreadRadius, const IntSize& aBlurRadius,
53 const Rect* aDirtyRect, const Rect* aSkipRect, bool aUseHardwareAccel) {
54 mBlur.Init(aRect, aSpreadRadius, aBlurRadius, aDirtyRect, aSkipRect);
55 size_t blurDataSize = mBlur.GetSurfaceAllocationSize();
56 if (blurDataSize == 0) {
57 return nullptr;
60 BackendType backend = aReferenceDT->GetBackendType();
62 // Check if the backend has an accelerated DrawSurfaceWithShadow.
63 // Currently, only D2D1.1 supports this.
64 // Otherwise, DrawSurfaceWithShadow only supports square blurs without spread.
65 // When blurring small draw targets such as short spans text, the cost of
66 // creating and flushing an accelerated draw target may exceed the speedup
67 // gained from the faster blur. It's up to the users of this blur
68 // to determine whether they want to use hardware acceleration.
69 if (aBlurRadius.IsSquare() && aSpreadRadius.IsEmpty() && aUseHardwareAccel &&
70 backend == BackendType::DIRECT2D1_1) {
71 mAccelerated = true;
74 if (aReferenceDT->IsCaptureDT()) {
75 if (mAccelerated) {
76 mDrawTarget = Factory::CreateCaptureDrawTarget(backend, mBlur.GetSize(),
77 SurfaceFormat::A8);
78 } else {
79 mDrawTarget = Factory::CreateCaptureDrawTargetForData(
80 backend, mBlur.GetSize(), SurfaceFormat::A8, mBlur.GetStride(),
81 blurDataSize);
83 } else if (mAccelerated) {
84 // Note: CreateShadowDrawTarget is only implemented for Cairo, so we don't
85 // care about mimicking this in the DrawTargetCapture case.
86 mDrawTarget = aReferenceDT->CreateShadowDrawTarget(
87 mBlur.GetSize(), SurfaceFormat::A8,
88 AlphaBoxBlur::CalculateBlurSigma(aBlurRadius.width));
89 if (mDrawTarget) {
90 // See Bug 1526045 - this is to force DT initialization.
91 mDrawTarget->ClearRect(gfx::Rect());
93 } else {
94 // Make an alpha-only surface to draw on. We will play with the data after
95 // everything is drawn to create a blur effect.
96 // This will be freed when the DrawTarget dies
97 mData = static_cast<uint8_t*>(calloc(1, blurDataSize));
98 if (!mData) {
99 return nullptr;
101 mDrawTarget =
102 Factory::DoesBackendSupportDataDrawtarget(backend)
103 ? Factory::CreateDrawTargetForData(backend, mData, mBlur.GetSize(),
104 mBlur.GetStride(),
105 SurfaceFormat::A8)
106 : gfxPlatform::CreateDrawTargetForData(
107 mData, mBlur.GetSize(), mBlur.GetStride(), SurfaceFormat::A8);
110 if (!mDrawTarget || !mDrawTarget->IsValid()) {
111 if (mData) {
112 free(mData);
115 return nullptr;
118 if (mData) {
119 mDrawTarget->AddUserData(reinterpret_cast<UserDataKey*>(mDrawTarget.get()),
120 mData, free);
123 mDrawTarget->SetTransform(Matrix::Translation(-mBlur.GetRect().TopLeft()));
124 return do_AddRef(mDrawTarget);
127 already_AddRefed<SourceSurface> gfxAlphaBoxBlur::DoBlur(
128 const Color* aShadowColor, IntPoint* aOutTopLeft) {
129 if (aOutTopLeft) {
130 *aOutTopLeft = mBlur.GetRect().TopLeft();
133 RefPtr<SourceSurface> blurMask;
134 if (mData) {
135 mBlur.Blur(mData);
136 blurMask = mDrawTarget->Snapshot();
137 } else if (mAccelerated) {
138 blurMask = mDrawTarget->Snapshot();
139 RefPtr<DrawTarget> blurDT = mDrawTarget->CreateSimilarDrawTarget(
140 blurMask->GetSize(), SurfaceFormat::A8);
141 if (!blurDT) {
142 return nullptr;
144 blurDT->DrawSurfaceWithShadow(
145 blurMask, Point(0, 0), Color(1, 1, 1), Point(0, 0),
146 AlphaBoxBlur::CalculateBlurSigma(mBlur.GetBlurRadius().width),
147 CompositionOp::OP_OVER);
148 blurMask = blurDT->Snapshot();
149 } else if (mDrawTarget->IsCaptureDT()) {
150 mDrawTarget->Blur(mBlur);
151 blurMask = mDrawTarget->Snapshot();
154 if (!aShadowColor) {
155 return blurMask.forget();
158 RefPtr<DrawTarget> shadowDT = mDrawTarget->CreateSimilarDrawTarget(
159 blurMask->GetSize(), SurfaceFormat::B8G8R8A8);
160 if (!shadowDT) {
161 return nullptr;
163 ColorPattern shadowColor(ToDeviceColor(*aShadowColor));
164 shadowDT->MaskSurface(shadowColor, blurMask, Point(0, 0));
166 return shadowDT->Snapshot();
169 void gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx) {
170 if ((mDrawTarget && !mDrawTarget->IsCaptureDT()) && !mAccelerated && !mData) {
171 return;
174 DrawTarget* dest = aDestinationCtx->GetDrawTarget();
175 if (!dest) {
176 NS_WARNING("Blurring not supported for Thebes contexts!");
177 return;
180 RefPtr<gfxPattern> thebesPat = aDestinationCtx->GetPattern();
181 Pattern* pat = thebesPat->GetPattern(dest, nullptr);
182 if (!pat) {
183 NS_WARNING("Failed to get pattern for blur!");
184 return;
187 IntPoint topLeft;
188 RefPtr<SourceSurface> mask = DoBlur(nullptr, &topLeft);
189 if (!mask) {
190 NS_ERROR("Failed to create mask!");
191 return;
194 // Avoid a semi-expensive clip operation if we can, otherwise
195 // clip to the dirty rect
196 Rect* dirtyRect = mBlur.GetDirtyRect();
197 if (dirtyRect) {
198 dest->PushClipRect(*dirtyRect);
201 Matrix oldTransform = dest->GetTransform();
202 Matrix newTransform = oldTransform;
203 newTransform.PreTranslate(topLeft);
204 dest->SetTransform(newTransform);
206 dest->MaskSurface(*pat, mask, Point(0, 0));
208 dest->SetTransform(oldTransform);
210 if (dirtyRect) {
211 dest->PopClip();
215 IntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd) {
216 mozilla::gfx::Point std(Float(aStd.x), Float(aStd.y));
217 IntSize size = AlphaBoxBlur::CalculateBlurRadius(std);
218 return IntSize(size.width, size.height);
221 struct BlurCacheKey : public PLDHashEntryHdr {
222 typedef const BlurCacheKey& KeyType;
223 typedef const BlurCacheKey* KeyTypePointer;
224 enum { ALLOW_MEMMOVE = true };
226 IntSize mMinSize;
227 IntSize mBlurRadius;
228 Color mShadowColor;
229 BackendType mBackend;
230 RectCornerRadii mCornerRadii;
231 bool mIsInset;
233 // Only used for inset blurs
234 IntSize mInnerMinSize;
236 BlurCacheKey(const IntSize& aMinSize, const IntSize& aBlurRadius,
237 const RectCornerRadii* aCornerRadii, const Color& aShadowColor,
238 BackendType aBackendType)
239 : BlurCacheKey(aMinSize, IntSize(0, 0), aBlurRadius, aCornerRadii,
240 aShadowColor, false, aBackendType) {}
242 explicit BlurCacheKey(const BlurCacheKey* aOther)
243 : mMinSize(aOther->mMinSize),
244 mBlurRadius(aOther->mBlurRadius),
245 mShadowColor(aOther->mShadowColor),
246 mBackend(aOther->mBackend),
247 mCornerRadii(aOther->mCornerRadii),
248 mIsInset(aOther->mIsInset),
249 mInnerMinSize(aOther->mInnerMinSize) {}
251 explicit BlurCacheKey(const IntSize& aOuterMinSize,
252 const IntSize& aInnerMinSize,
253 const IntSize& aBlurRadius,
254 const RectCornerRadii* aCornerRadii,
255 const Color& aShadowColor, bool aIsInset,
256 BackendType aBackendType)
257 : mMinSize(aOuterMinSize),
258 mBlurRadius(aBlurRadius),
259 mShadowColor(aShadowColor),
260 mBackend(aBackendType),
261 mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii()),
262 mIsInset(aIsInset),
263 mInnerMinSize(aInnerMinSize) {}
265 BlurCacheKey(BlurCacheKey&&) = default;
267 static PLDHashNumber HashKey(const KeyTypePointer aKey) {
268 PLDHashNumber hash = 0;
269 hash = AddToHash(hash, aKey->mMinSize.width, aKey->mMinSize.height);
270 hash = AddToHash(hash, aKey->mBlurRadius.width, aKey->mBlurRadius.height);
272 hash = AddToHash(
273 hash, HashBytes(&aKey->mShadowColor.r, sizeof(aKey->mShadowColor.r)));
274 hash = AddToHash(
275 hash, HashBytes(&aKey->mShadowColor.g, sizeof(aKey->mShadowColor.g)));
276 hash = AddToHash(
277 hash, HashBytes(&aKey->mShadowColor.b, sizeof(aKey->mShadowColor.b)));
278 hash = AddToHash(
279 hash, HashBytes(&aKey->mShadowColor.a, sizeof(aKey->mShadowColor.a)));
281 for (int i = 0; i < 4; i++) {
282 hash = AddToHash(hash, aKey->mCornerRadii[i].width,
283 aKey->mCornerRadii[i].height);
286 hash = AddToHash(hash, (uint32_t)aKey->mBackend);
288 if (aKey->mIsInset) {
289 hash = AddToHash(hash, aKey->mInnerMinSize.width,
290 aKey->mInnerMinSize.height);
292 return hash;
295 bool KeyEquals(KeyTypePointer aKey) const {
296 if (aKey->mMinSize == mMinSize && aKey->mBlurRadius == mBlurRadius &&
297 aKey->mCornerRadii == mCornerRadii &&
298 aKey->mShadowColor == mShadowColor && aKey->mBackend == mBackend) {
299 if (mIsInset) {
300 return (mInnerMinSize == aKey->mInnerMinSize);
303 return true;
306 return false;
309 static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
313 * This class is what is cached. It need to be allocated in an object separated
314 * to the cache entry to be able to be tracked by the nsExpirationTracker.
315 * */
316 struct BlurCacheData {
317 BlurCacheData(SourceSurface* aBlur, const IntMargin& aBlurMargin,
318 BlurCacheKey&& aKey)
319 : mBlur(aBlur), mBlurMargin(aBlurMargin), mKey(std::move(aKey)) {}
321 BlurCacheData(BlurCacheData&& aOther) = default;
323 nsExpirationState* GetExpirationState() { return &mExpirationState; }
325 nsExpirationState mExpirationState;
326 RefPtr<SourceSurface> mBlur;
327 IntMargin mBlurMargin;
328 BlurCacheKey mKey;
332 * This class implements a cache with no maximum size, that retains the
333 * SourceSurfaces used to draw the blurs.
335 * An entry stays in the cache as long as it is used often.
337 class BlurCache final : public nsExpirationTracker<BlurCacheData, 4> {
338 public:
339 BlurCache()
340 : nsExpirationTracker<BlurCacheData, 4>(
341 GENERATION_MS, "BlurCache",
342 SystemGroup::EventTargetFor(TaskCategory::Other)) {}
344 virtual void NotifyExpired(BlurCacheData* aObject) override {
345 RemoveObject(aObject);
346 mHashEntries.Remove(aObject->mKey);
349 BlurCacheData* Lookup(const IntSize& aMinSize, const IntSize& aBlurRadius,
350 const RectCornerRadii* aCornerRadii,
351 const Color& aShadowColor, BackendType aBackendType) {
352 BlurCacheData* blur = mHashEntries.Get(BlurCacheKey(
353 aMinSize, aBlurRadius, aCornerRadii, aShadowColor, aBackendType));
354 if (blur) {
355 MarkUsed(blur);
358 return blur;
361 BlurCacheData* LookupInsetBoxShadow(const IntSize& aOuterMinSize,
362 const IntSize& aInnerMinSize,
363 const IntSize& aBlurRadius,
364 const RectCornerRadii* aCornerRadii,
365 const Color& aShadowColor,
366 BackendType aBackendType) {
367 bool insetBoxShadow = true;
368 BlurCacheKey key(aOuterMinSize, aInnerMinSize, aBlurRadius, aCornerRadii,
369 aShadowColor, insetBoxShadow, aBackendType);
370 BlurCacheData* blur = mHashEntries.Get(key);
371 if (blur) {
372 MarkUsed(blur);
375 return blur;
378 // Returns true if we successfully register the blur in the cache, false
379 // otherwise.
380 bool RegisterEntry(BlurCacheData* aValue) {
381 nsresult rv = AddObject(aValue);
382 if (NS_FAILED(rv)) {
383 // We are OOM, and we cannot track this object. We don't want stall
384 // entries in the hash table (since the expiration tracker is responsible
385 // for removing the cache entries), so we avoid putting that entry in the
386 // table, which is a good things considering we are short on memory
387 // anyway, we probably don't want to retain things.
388 return false;
390 mHashEntries.Put(aValue->mKey, aValue);
391 return true;
394 protected:
395 static const uint32_t GENERATION_MS = 1000;
397 * FIXME use nsTHashtable to avoid duplicating the BlurCacheKey.
398 * https://bugzilla.mozilla.org/show_bug.cgi?id=761393#c47
400 nsClassHashtable<BlurCacheKey, BlurCacheData> mHashEntries;
403 static BlurCache* gBlurCache = nullptr;
405 static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii,
406 const IntSize& aBlurRadius,
407 IntMargin& aOutSlice,
408 const IntSize& aRectSize) {
409 Size cornerSize(0, 0);
410 if (aCornerRadii) {
411 const RectCornerRadii& corners = *aCornerRadii;
412 NS_FOR_CSS_FULL_CORNERS(i) {
413 cornerSize.width = std::max(cornerSize.width, corners[i].width);
414 cornerSize.height = std::max(cornerSize.height, corners[i].height);
418 IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius;
419 aOutSlice =
420 IntMargin(margin.height, margin.width, margin.height, margin.width);
422 IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1);
424 // If aRectSize is smaller than minSize, the border-image approach won't
425 // work; there's no way to squeeze parts of the min box-shadow source
426 // image such that the result looks correct. So we need to adjust minSize
427 // in such a way that we can later draw it without stretching in the affected
428 // dimension. We also need to adjust "slice" to ensure that we're not trying
429 // to slice away more than we have.
430 if (aRectSize.width < minSize.width) {
431 minSize.width = aRectSize.width;
432 aOutSlice.left = 0;
433 aOutSlice.right = 0;
435 if (aRectSize.height < minSize.height) {
436 minSize.height = aRectSize.height;
437 aOutSlice.top = 0;
438 aOutSlice.bottom = 0;
441 MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width);
442 MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height);
443 return minSize;
446 static void CacheBlur(DrawTarget* aDT, const IntSize& aMinSize,
447 const IntSize& aBlurRadius,
448 const RectCornerRadii* aCornerRadii,
449 const Color& aShadowColor, const IntMargin& aBlurMargin,
450 SourceSurface* aBoxShadow) {
451 BlurCacheKey key(aMinSize, aBlurRadius, aCornerRadii, aShadowColor,
452 aDT->GetBackendType());
453 BlurCacheData* data =
454 new BlurCacheData(aBoxShadow, aBlurMargin, std::move(key));
455 if (!gBlurCache->RegisterEntry(data)) {
456 delete data;
460 // Blurs a small surface and creates the colored box shadow.
461 static already_AddRefed<SourceSurface> CreateBoxShadow(
462 DrawTarget* aDestDrawTarget, const IntSize& aMinSize,
463 const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius,
464 const Color& aShadowColor, bool aMirrorCorners, IntMargin& aOutBlurMargin) {
465 gfxAlphaBoxBlur blur;
466 Rect minRect(Point(0, 0), Size(aMinSize));
467 Rect blurRect(minRect);
468 // If mirroring corners, we only need to draw the top-left quadrant.
469 // Use ceil to preserve the remaining 1x1 middle area for minimized box
470 // shadows.
471 if (aMirrorCorners) {
472 blurRect.SizeTo(ceil(blurRect.Width() * 0.5f),
473 ceil(blurRect.Height() * 0.5f));
475 IntSize zeroSpread(0, 0);
476 RefPtr<DrawTarget> blurDT =
477 blur.InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
478 if (!blurDT) {
479 return nullptr;
482 ColorPattern black(Color(0.f, 0.f, 0.f, 1.f));
484 if (aCornerRadii) {
485 RefPtr<Path> roundedRect =
486 MakePathForRoundedRect(*blurDT, minRect, *aCornerRadii);
487 blurDT->Fill(roundedRect, black);
488 } else {
489 blurDT->FillRect(minRect, black);
492 IntPoint topLeft;
493 RefPtr<SourceSurface> result = blur.DoBlur(&aShadowColor, &topLeft);
494 if (!result) {
495 return nullptr;
498 // Since blurRect is at (0, 0), we can find the inflated margin by
499 // negating the new rect origin, which would have been negative if
500 // the rect was inflated.
501 aOutBlurMargin = IntMargin(-topLeft.y, -topLeft.x, -topLeft.y, -topLeft.x);
503 return result.forget();
506 static already_AddRefed<SourceSurface> GetBlur(
507 gfxContext* aDestinationCtx, const IntSize& aRectSize,
508 const IntSize& aBlurRadius, const RectCornerRadii* aCornerRadii,
509 const Color& aShadowColor, bool aMirrorCorners, IntMargin& aOutBlurMargin,
510 IntMargin& aOutSlice, IntSize& aOutMinSize) {
511 if (!gBlurCache) {
512 gBlurCache = new BlurCache();
515 IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius,
516 aOutSlice, aRectSize);
518 // We can get seams using the min size rect when drawing to the destination
519 // rect if we have a non-pixel aligned destination transformation. In those
520 // cases, fallback to just rendering the destination rect. During printing, we
521 // record all the Moz 2d commands and replay them on the parent side with
522 // Cairo. Cairo printing uses StretchDIBits to stretch the surface. However,
523 // since our source image is only 1px for some parts, we make thousands of
524 // calls. Instead just render the blur ourself here as one image and send it
525 // over for printing.
526 // TODO: May need to change this with the blob renderer in WR since it also
527 // records.
528 Matrix destMatrix = aDestinationCtx->CurrentMatrix();
529 bool useDestRect = !destMatrix.IsRectilinear() ||
530 destMatrix.HasNonIntegerTranslation() ||
531 aDestinationCtx->GetDrawTarget()->IsRecording();
532 if (useDestRect) {
533 minSize = aRectSize;
536 int32_t maxTextureSize = gfxPlatform::MaxTextureSize();
537 if (minSize.width > maxTextureSize || minSize.height > maxTextureSize) {
538 return nullptr;
541 aOutMinSize = minSize;
543 DrawTarget* destDT = aDestinationCtx->GetDrawTarget();
545 if (!useDestRect) {
546 BlurCacheData* cached =
547 gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor,
548 destDT->GetBackendType());
549 if (cached) {
550 // See CreateBoxShadow() for these values
551 aOutBlurMargin = cached->mBlurMargin;
552 RefPtr<SourceSurface> blur = cached->mBlur;
553 return blur.forget();
557 RefPtr<SourceSurface> boxShadow =
558 CreateBoxShadow(destDT, minSize, aCornerRadii, aBlurRadius, aShadowColor,
559 aMirrorCorners, aOutBlurMargin);
560 if (!boxShadow) {
561 return nullptr;
564 if (RefPtr<SourceSurface> opt = destDT->OptimizeSourceSurface(boxShadow)) {
565 boxShadow = opt;
568 if (!useDestRect) {
569 CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor,
570 aOutBlurMargin, boxShadow);
572 return boxShadow.forget();
575 void gfxAlphaBoxBlur::ShutdownBlurCache() {
576 delete gBlurCache;
577 gBlurCache = nullptr;
580 static Rect RectWithEdgesTRBL(Float aTop, Float aRight, Float aBottom,
581 Float aLeft) {
582 return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop);
585 static bool ShouldStretchSurface(DrawTarget* aDT, SourceSurface* aSurface) {
586 // Use stretching if possible, since it leads to less seams when the
587 // destination is transformed. However, don't do this if we're using cairo,
588 // because if cairo is using pixman it won't render anything for large
589 // stretch factors because pixman's internal fixed point precision is not
590 // high enough to handle those scale factors.
591 return aDT->GetBackendType() != BackendType::CAIRO;
594 static void RepeatOrStretchSurface(DrawTarget* aDT, SourceSurface* aSurface,
595 const Rect& aDest, const Rect& aSrc,
596 const Rect& aSkipRect) {
597 if (aSkipRect.Contains(aDest)) {
598 return;
601 if (ShouldStretchSurface(aDT, aSurface)) {
602 aDT->DrawSurface(aSurface, aDest, aSrc);
603 return;
606 SurfacePattern pattern(aSurface, ExtendMode::REPEAT,
607 Matrix::Translation(aDest.TopLeft() - aSrc.TopLeft()),
608 SamplingFilter::GOOD, RoundedToInt(aSrc));
609 aDT->FillRect(aDest, pattern);
612 static void DrawCorner(DrawTarget* aDT, SourceSurface* aSurface,
613 const Rect& aDest, const Rect& aSrc,
614 const Rect& aSkipRect) {
615 if (aSkipRect.Contains(aDest)) {
616 return;
619 aDT->DrawSurface(aSurface, aDest, aSrc);
622 static void DrawMinBoxShadow(DrawTarget* aDestDrawTarget,
623 SourceSurface* aSourceBlur, const Rect& aDstOuter,
624 const Rect& aDstInner, const Rect& aSrcOuter,
625 const Rect& aSrcInner, const Rect& aSkipRect,
626 bool aMiddle = false) {
627 // Corners: top left, top right, bottom left, bottom right
628 DrawCorner(aDestDrawTarget, aSourceBlur,
629 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(), aDstInner.Y(),
630 aDstOuter.X()),
631 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(), aSrcInner.Y(),
632 aSrcOuter.X()),
633 aSkipRect);
635 DrawCorner(aDestDrawTarget, aSourceBlur,
636 RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(), aDstInner.Y(),
637 aDstInner.XMost()),
638 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(), aSrcInner.Y(),
639 aSrcInner.XMost()),
640 aSkipRect);
642 DrawCorner(aDestDrawTarget, aSourceBlur,
643 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
644 aDstOuter.YMost(), aDstOuter.X()),
645 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
646 aSrcOuter.YMost(), aSrcOuter.X()),
647 aSkipRect);
649 DrawCorner(aDestDrawTarget, aSourceBlur,
650 RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
651 aDstOuter.YMost(), aDstInner.XMost()),
652 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
653 aSrcOuter.YMost(), aSrcInner.XMost()),
654 aSkipRect);
656 // Edges: top, left, right, bottom
657 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
658 RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
659 aDstInner.Y(), aDstInner.X()),
660 RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
661 aSrcInner.Y(), aSrcInner.X()),
662 aSkipRect);
663 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
664 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
665 aDstInner.YMost(), aDstOuter.X()),
666 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
667 aSrcInner.YMost(), aSrcOuter.X()),
668 aSkipRect);
670 RepeatOrStretchSurface(
671 aDestDrawTarget, aSourceBlur,
672 RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(), aDstInner.YMost(),
673 aDstInner.XMost()),
674 RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(), aSrcInner.YMost(),
675 aSrcInner.XMost()),
676 aSkipRect);
677 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
678 RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
679 aDstOuter.YMost(), aDstInner.X()),
680 RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
681 aSrcOuter.YMost(), aSrcInner.X()),
682 aSkipRect);
684 // Middle part
685 if (aMiddle) {
686 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
687 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
688 aDstInner.YMost(), aDstInner.X()),
689 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
690 aSrcInner.YMost(), aSrcInner.X()),
691 aSkipRect);
695 static void DrawMirroredRect(DrawTarget* aDT, SourceSurface* aSurface,
696 const Rect& aDest, const Point& aSrc,
697 Float aScaleX, Float aScaleY) {
698 SurfacePattern pattern(
699 aSurface, ExtendMode::CLAMP,
700 Matrix::Scaling(aScaleX, aScaleY)
701 .PreTranslate(-aSrc)
702 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
703 aScaleY < 0 ? aDest.YMost() : aDest.Y()));
704 aDT->FillRect(aDest, pattern);
707 static void DrawMirroredBoxShadow(DrawTarget* aDT, SourceSurface* aSurface,
708 const Rect& aDestRect) {
709 Point center(ceil(aDestRect.X() + aDestRect.Width() / 2),
710 ceil(aDestRect.Y() + aDestRect.Height() / 2));
711 Rect topLeft(aDestRect.X(), aDestRect.Y(), center.x - aDestRect.X(),
712 center.y - aDestRect.Y());
713 Rect bottomRight(topLeft.BottomRight(), aDestRect.Size() - topLeft.Size());
714 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
715 topLeft.Height());
716 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
717 bottomRight.Height());
718 DrawMirroredRect(aDT, aSurface, topLeft, Point(), 1, 1);
719 DrawMirroredRect(aDT, aSurface, topRight, Point(), -1, 1);
720 DrawMirroredRect(aDT, aSurface, bottomLeft, Point(), 1, -1);
721 DrawMirroredRect(aDT, aSurface, bottomRight, Point(), -1, -1);
724 static void DrawMirroredCorner(DrawTarget* aDT, SourceSurface* aSurface,
725 const Rect& aDest, const Point& aSrc,
726 const Rect& aSkipRect, Float aScaleX,
727 Float aScaleY) {
728 if (aSkipRect.Contains(aDest)) {
729 return;
732 DrawMirroredRect(aDT, aSurface, aDest, aSrc, aScaleX, aScaleY);
735 static void RepeatOrStretchMirroredSurface(DrawTarget* aDT,
736 SourceSurface* aSurface,
737 const Rect& aDest, const Rect& aSrc,
738 const Rect& aSkipRect, Float aScaleX,
739 Float aScaleY) {
740 if (aSkipRect.Contains(aDest)) {
741 return;
744 if (ShouldStretchSurface(aDT, aSurface)) {
745 aScaleX *= aDest.Width() / aSrc.Width();
746 aScaleY *= aDest.Height() / aSrc.Height();
747 DrawMirroredRect(aDT, aSurface, aDest, aSrc.TopLeft(), aScaleX, aScaleY);
748 return;
751 SurfacePattern pattern(
752 aSurface, ExtendMode::REPEAT,
753 Matrix::Scaling(aScaleX, aScaleY)
754 .PreTranslate(-aSrc.TopLeft())
755 .PostTranslate(aScaleX < 0 ? aDest.XMost() : aDest.X(),
756 aScaleY < 0 ? aDest.YMost() : aDest.Y()),
757 SamplingFilter::GOOD, RoundedToInt(aSrc));
758 aDT->FillRect(aDest, pattern);
761 static void DrawMirroredMinBoxShadow(
762 DrawTarget* aDestDrawTarget, SourceSurface* aSourceBlur,
763 const Rect& aDstOuter, const Rect& aDstInner, const Rect& aSrcOuter,
764 const Rect& aSrcInner, const Rect& aSkipRect, bool aMiddle = false) {
765 // Corners: top left, top right, bottom left, bottom right
766 // Compute quadrant bounds and then clip them to corners along
767 // dimensions where we need to stretch from min size.
768 Point center(ceil(aDstOuter.X() + aDstOuter.Width() / 2),
769 ceil(aDstOuter.Y() + aDstOuter.Height() / 2));
770 Rect topLeft(aDstOuter.X(), aDstOuter.Y(), center.x - aDstOuter.X(),
771 center.y - aDstOuter.Y());
772 Rect bottomRight(topLeft.BottomRight(), aDstOuter.Size() - topLeft.Size());
773 Rect topRight(bottomRight.X(), topLeft.Y(), bottomRight.Width(),
774 topLeft.Height());
775 Rect bottomLeft(topLeft.X(), bottomRight.Y(), topLeft.Width(),
776 bottomRight.Height());
778 // Check if the middle part has been minimized along each dimension.
779 // If so, those will be strecthed/drawn separately and need to be clipped out.
780 if (aSrcInner.Width() == 1) {
781 topLeft.SetRightEdge(aDstInner.X());
782 topRight.SetLeftEdge(aDstInner.XMost());
783 bottomLeft.SetRightEdge(aDstInner.X());
784 bottomRight.SetLeftEdge(aDstInner.XMost());
786 if (aSrcInner.Height() == 1) {
787 topLeft.SetBottomEdge(aDstInner.Y());
788 topRight.SetBottomEdge(aDstInner.Y());
789 bottomLeft.SetTopEdge(aDstInner.YMost());
790 bottomRight.SetTopEdge(aDstInner.YMost());
793 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topLeft, aSrcOuter.TopLeft(),
794 aSkipRect, 1, 1);
795 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, topRight,
796 aSrcOuter.TopLeft(), aSkipRect, -1, 1);
797 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomLeft,
798 aSrcOuter.TopLeft(), aSkipRect, 1, -1);
799 DrawMirroredCorner(aDestDrawTarget, aSourceBlur, bottomRight,
800 aSrcOuter.TopLeft(), aSkipRect, -1, -1);
802 // Edges: top, bottom, left, right
803 // Draw middle edges where they need to be stretched. The top and left
804 // sections that are part of the top-left quadrant will be mirrored to
805 // the bottom and right sections, respectively.
806 if (aSrcInner.Width() == 1) {
807 Rect dstTop = RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
808 aDstInner.Y(), aDstInner.X());
809 Rect srcTop = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
810 aSrcInner.Y(), aSrcInner.X());
811 Rect dstBottom = RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
812 aDstOuter.YMost(), aDstInner.X());
813 Rect srcBottom = RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
814 aSrcInner.Y(), aSrcInner.X());
815 // If we only need to stretch along the X axis and we're drawing
816 // the middle section, just sample all the way to the center of the
817 // source on the Y axis to avoid extra draw calls.
818 if (aMiddle && aSrcInner.Height() != 1) {
819 dstTop.SetBottomEdge(center.y);
820 srcTop.SetHeight(dstTop.Height());
821 dstBottom.SetTopEdge(dstTop.YMost());
822 srcBottom.SetHeight(dstBottom.Height());
824 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstTop, srcTop,
825 aSkipRect, 1, 1);
826 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstBottom,
827 srcBottom, aSkipRect, 1, -1);
830 if (aSrcInner.Height() == 1) {
831 Rect dstLeft = RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
832 aDstInner.YMost(), aDstOuter.X());
833 Rect srcLeft = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
834 aSrcInner.YMost(), aSrcOuter.X());
835 Rect dstRight = RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
836 aDstInner.YMost(), aDstInner.XMost());
837 Rect srcRight = RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
838 aSrcInner.YMost(), aSrcOuter.X());
839 // Only stretching on Y axis, so sample source to the center of the X axis.
840 if (aMiddle && aSrcInner.Width() != 1) {
841 dstLeft.SetRightEdge(center.x);
842 srcLeft.SetWidth(dstLeft.Width());
843 dstRight.SetLeftEdge(dstLeft.XMost());
844 srcRight.SetWidth(dstRight.Width());
846 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstLeft,
847 srcLeft, aSkipRect, 1, 1);
848 RepeatOrStretchMirroredSurface(aDestDrawTarget, aSourceBlur, dstRight,
849 srcRight, aSkipRect, -1, 1);
852 // If we need to stretch along both dimensions, then the middle part
853 // must be drawn separately.
854 if (aMiddle && aSrcInner.Width() == 1 && aSrcInner.Height() == 1) {
855 RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
856 RectWithEdgesTRBL(aDstInner.Y(), aDstInner.XMost(),
857 aDstInner.YMost(), aDstInner.X()),
858 RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.XMost(),
859 aSrcInner.YMost(), aSrcInner.X()),
860 aSkipRect);
864 /***
865 * We draw a blurred a rectangle by only blurring a smaller rectangle and
866 * splitting the rectangle into 9 parts.
867 * First, a small minimum source rect is calculated and used to create a blur
868 * mask since the actual blurring itself is expensive. Next, we use the mask
869 * with the given shadow color to create a minimally-sized box shadow of the
870 * right color. Finally, we cut out the 9 parts from the box-shadow source and
871 * paint each part in the right place, stretching the non-corner parts to fill
872 * the space between the corners.
875 /* static */
876 void gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
877 const gfxRect& aRect,
878 const RectCornerRadii* aCornerRadii,
879 const gfxPoint& aBlurStdDev,
880 const Color& aShadowColor,
881 const gfxRect& aDirtyRect,
882 const gfxRect& aSkipRect) {
883 if (!RectIsInt32Safe(ToRect(aRect))) {
884 return;
887 IntSize blurRadius = CalculateBlurRadius(aBlurStdDev);
888 bool mirrorCorners = !aCornerRadii || aCornerRadii->AreRadiiSame();
890 IntRect rect = RoundedToInt(ToRect(aRect));
891 IntMargin blurMargin;
892 IntMargin slice;
893 IntSize minSize;
894 RefPtr<SourceSurface> boxShadow =
895 GetBlur(aDestinationCtx, rect.Size(), blurRadius, aCornerRadii,
896 aShadowColor, mirrorCorners, blurMargin, slice, minSize);
897 if (!boxShadow) {
898 return;
901 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
902 destDrawTarget->PushClipRect(ToRect(aDirtyRect));
904 // Copy the right parts from boxShadow into destDrawTarget. The middle parts
905 // will be stretched, border-image style.
907 Rect srcOuter(Point(blurMargin.left, blurMargin.top), Size(minSize));
908 Rect srcInner(srcOuter);
909 srcOuter.Inflate(Margin(blurMargin));
910 srcInner.Deflate(Margin(slice));
912 Rect dstOuter(rect);
913 Rect dstInner(rect);
914 dstOuter.Inflate(Margin(blurMargin));
915 dstInner.Deflate(Margin(slice));
917 Rect skipRect = ToRect(aSkipRect);
919 if (minSize == rect.Size()) {
920 // The target rect is smaller than the minimal size so just draw the surface
921 if (mirrorCorners) {
922 DrawMirroredBoxShadow(destDrawTarget, boxShadow, dstOuter);
923 } else {
924 destDrawTarget->DrawSurface(boxShadow, dstOuter, srcOuter);
926 } else {
927 if (mirrorCorners) {
928 DrawMirroredMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner,
929 srcOuter, srcInner, skipRect, true);
930 } else {
931 DrawMinBoxShadow(destDrawTarget, boxShadow, dstOuter, dstInner, srcOuter,
932 srcInner, skipRect, true);
936 // A note about anti-aliasing and seems between adjacent parts:
937 // We don't explicitly disable anti-aliasing in the DrawSurface calls above,
938 // so if there's a transform on destDrawTarget that is not pixel-aligned,
939 // there will be seams between adjacent parts of the box-shadow. It's hard to
940 // avoid those without the use of an intermediate surface.
941 // You might think that we could avoid those by just turning off AA, but there
942 // is a problem with that: Box-shadow rendering needs to clip out the
943 // element's border box, and we'd like that clip to have anti-aliasing -
944 // especially if the element has rounded corners! So we can't do that unless
945 // we have a way to say "Please anti-alias the clip, but don't antialias the
946 // destination rect of the DrawSurface call".
948 destDrawTarget->PopClip();
951 static already_AddRefed<Path> GetBoxShadowInsetPath(
952 DrawTarget* aDrawTarget, const Rect aOuterRect, const Rect aInnerRect,
953 const RectCornerRadii* aInnerClipRadii) {
954 /***
955 * We create an inset path by having two rects.
957 * -----------------------
958 * | ________________ |
959 * | | | |
960 * | | | |
961 * | ------------------ |
962 * |_____________________|
964 * The outer rect and the inside rect. The path
965 * creates a frame around the content where we draw the inset shadow.
967 RefPtr<PathBuilder> builder =
968 aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
969 AppendRectToPath(builder, aOuterRect, true);
971 if (aInnerClipRadii) {
972 AppendRoundedRectToPath(builder, aInnerRect, *aInnerClipRadii, false);
973 } else {
974 AppendRectToPath(builder, aInnerRect, false);
976 return builder->Finish();
979 static void FillDestinationPath(
980 gfxContext* aDestinationCtx, const Rect& aDestinationRect,
981 const Rect& aShadowClipRect, const Color& aShadowColor,
982 const RectCornerRadii* aInnerClipRadii = nullptr) {
983 // When there is no blur radius, fill the path onto the destination
984 // surface.
985 aDestinationCtx->SetColor(aShadowColor);
986 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
987 RefPtr<Path> shadowPath = GetBoxShadowInsetPath(
988 destDrawTarget, aDestinationRect, aShadowClipRect, aInnerClipRadii);
990 aDestinationCtx->SetPath(shadowPath);
991 aDestinationCtx->Fill();
994 static void CacheInsetBlur(const IntSize& aMinOuterSize,
995 const IntSize& aMinInnerSize,
996 const IntSize& aBlurRadius,
997 const RectCornerRadii* aCornerRadii,
998 const Color& aShadowColor, 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;
1011 already_AddRefed<SourceSurface> gfxAlphaBoxBlur::GetInsetBlur(
1012 const Rect& aOuterRect, const Rect& aWhitespaceRect, bool aIsDestRect,
1013 const Color& aShadowColor, const IntSize& aBlurRadius,
1014 const RectCornerRadii* aInnerClipRadii, DrawTarget* aDestDrawTarget,
1015 bool aMirrorCorners) {
1016 if (!gBlurCache) {
1017 gBlurCache = new BlurCache();
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();
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));
1043 IntSize zeroSpread(0, 0);
1044 RefPtr<DrawTarget> minDrawTarget =
1045 InitDrawTarget(aDestDrawTarget, blurRect, zeroSpread, aBlurRadius);
1046 if (!minDrawTarget) {
1047 return nullptr;
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());
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);
1066 ColorPattern black(Color(0.f, 0.f, 0.f, 1.f));
1067 minDrawTarget->Fill(maskPath, black);
1069 // Blur and fill in with the color we actually wanted
1070 RefPtr<SourceSurface> minInsetBlur = DoBlur(&aShadowColor);
1071 if (!minInsetBlur) {
1072 return nullptr;
1075 if (RefPtr<SourceSurface> opt =
1076 aDestDrawTarget->OptimizeSourceSurface(minInsetBlur)) {
1077 minInsetBlur = opt;
1080 if (!aIsDestRect) {
1081 CacheInsetBlur(outerSize, whitespaceSize, aBlurRadius, aInnerClipRadii,
1082 aShadowColor, aDestDrawTarget->GetBackendType(),
1083 minInsetBlur);
1086 return minInsetBlur.forget();
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.
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.
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 * |________________________________|
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 NS_FOR_CSS_FULL_CORNERS(i) {
1119 cornerSize.width = std::max(cornerSize.width, corners[i].width);
1120 cornerSize.height = std::max(cornerSize.height, corners[i].height);
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;
1128 aOutInnerMargin.SizeTo(margin.height, margin.width, margin.height,
1129 margin.width);
1130 aOutBlurMargin.SizeTo(aBlurRadius.height, aBlurRadius.width,
1131 aBlurRadius.height, aBlurRadius.width);
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);
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());
1156 if (useDestRect) {
1157 aOutWhitespaceRect = aShadowClipRect;
1158 aOutOuterRect = aDestinationRect;
1159 } else {
1160 aOutWhitespaceRect = insideWhiteSpace;
1161 aOutOuterRect = aOutWhitespaceRect;
1162 aOutOuterRect.Inflate(aBlurMargin);
1165 return useDestRect;
1168 void gfxAlphaBoxBlur::BlurInsetBox(
1169 gfxContext* aDestinationCtx, const Rect& aDestinationRect,
1170 const Rect& aShadowClipRect, const IntSize& aBlurRadius,
1171 const Color& 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;
1180 DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
1182 Margin innerMargin;
1183 Margin blurMargin;
1184 GetBlurMargins(aInnerClipRadii, aBlurRadius, blurMargin, innerMargin);
1186 Rect whitespaceRect;
1187 Rect outerRect;
1188 bool useDestRect =
1189 GetInsetBoxShadowRects(blurMargin, innerMargin, aShadowClipRect,
1190 aDestinationRect, whitespaceRect, outerRect);
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;
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);
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
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);
1228 // Inflate once for the frame around the whitespace
1229 Rect destRect(aShadowClipRect);
1230 destRect.Inflate(blurMargin);
1232 // Deflate for the blurred in white space
1233 Rect destInnerRect(aShadowClipRect);
1234 destInnerRect.Deflate(innerMargin);
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);