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