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