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