static already_AddRefed<SourceSurface> GetBlur(gfxContext* aDestinationCtx, const IntSize& aRectSize, const IntSize& aBlurRadius, RectCornerRadii* aCornerRadii, const Color& aShadowColor, IntMargin& aExtendDestBy, IntMargin& aSlice) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, aSlice, aRectSize); // We can get seams using the min size rect when drawing to the destination rect // if we have a non-pixel aligned destination transformation. In those cases, // fallback to just rendering the destination rect. Matrix destMatrix = ToMatrix(aDestinationCtx->CurrentMatrix()); bool useDestRect = !destMatrix.IsRectilinear() || destMatrix.HasNonIntegerTranslation(); if (useDestRect) { minSize = aRectSize; } DrawTarget& destDT = *aDestinationCtx->GetDrawTarget(); BlurCacheData* cached = gBlurCache->Lookup(minSize, aBlurRadius, aCornerRadii, aShadowColor, destDT.GetBackendType()); if (cached && !useDestRect) { // See CreateBlurMask() for these values aExtendDestBy = cached->mExtendDest; aSlice = aSlice + aExtendDestBy; RefPtr<SourceSurface> blur = cached->mBlur; return blur.forget(); } RefPtr<SourceSurface> blurMask = CreateBlurMask(minSize, aCornerRadii, aBlurRadius, aExtendDestBy, aSlice, destDT); if (!blurMask) { return nullptr; } RefPtr<SourceSurface> boxShadow = CreateBoxShadow(blurMask, aShadowColor); if (!boxShadow) { return nullptr; } if (useDestRect) { // Since we're just going to paint the actual rect to the destination aSlice.SizeTo(0, 0, 0, 0); } else { CacheBlur(destDT, minSize, aBlurRadius, aCornerRadii, aShadowColor, aExtendDestBy, boxShadow); } return boxShadow.forget(); }
// Blurs a small surface and creates the mask. static already_AddRefed<SourceSurface> CreateBlurMask(const IntSize& aRectSize, RectCornerRadii* aCornerRadii, gfxIntSize aBlurRadius, IntMargin& aExtendDestBy, IntMargin& aSliceBorder, DrawTarget& aDestDrawTarget) { IntMargin slice; gfxAlphaBoxBlur blur; IntSize minSize = ComputeMinSizeForShadowShape(aCornerRadii, aBlurRadius, slice, aRectSize); IntRect minRect(IntPoint(), minSize); gfxContext* blurCtx = blur.Init(ThebesRect(Rect(minRect)), gfxIntSize(), aBlurRadius, nullptr, nullptr); if (!blurCtx) { return nullptr; } DrawTarget* blurDT = blurCtx->GetDrawTarget(); ColorPattern black(Color(0.f, 0.f, 0.f, 1.f)); if (aCornerRadii) { RefPtr<Path> roundedRect = MakePathForRoundedRect(*blurDT, Rect(minRect), *aCornerRadii); blurDT->Fill(roundedRect, black); } else { blurDT->FillRect(Rect(minRect), black); } IntPoint topLeft; RefPtr<SourceSurface> result = blur.DoBlur(&aDestDrawTarget, &topLeft); if (!result) { return nullptr; } IntRect expandedMinRect(topLeft, result->GetSize()); aExtendDestBy = expandedMinRect - minRect; aSliceBorder = slice + aExtendDestBy; MOZ_ASSERT(aSliceBorder.LeftRight() <= expandedMinRect.width); MOZ_ASSERT(aSliceBorder.TopBottom() <= expandedMinRect.height); return result.forget(); }
static IntSize ComputeMinSizeForShadowShape(RectCornerRadii* aCornerRadii, gfxIntSize aBlurRadius, IntMargin& aSlice, const IntSize& aRectSize) { float cornerWidth = 0; float cornerHeight = 0; if (aCornerRadii) { RectCornerRadii corners = *aCornerRadii; for (size_t i = 0; i < 4; i++) { cornerWidth = std::max(cornerWidth, corners[i].width); cornerHeight = std::max(cornerHeight, corners[i].height); } } aSlice = IntMargin(ceil(cornerHeight) + aBlurRadius.height, ceil(cornerWidth) + aBlurRadius.width, ceil(cornerHeight) + aBlurRadius.height, ceil(cornerWidth) + aBlurRadius.width); IntSize minSize(aSlice.LeftRight() + 1, aSlice.TopBottom() + 1); // If aRectSize is smaller than minSize, the border-image approach won't // work; there's no way to squeeze parts of the min box-shadow source // image such that the result looks correct. So we need to adjust minSize // in such a way that we can later draw it without stretching in the affected // dimension. We also need to adjust "slice" to ensure that we're not trying // to slice away more than we have. if (aRectSize.width < minSize.width) { minSize.width = aRectSize.width; aSlice.left = 0; aSlice.right = 0; } if (aRectSize.height < minSize.height) { minSize.height = aRectSize.height; aSlice.top = 0; aSlice.bottom = 0; } MOZ_ASSERT(aSlice.LeftRight() <= minSize.width); MOZ_ASSERT(aSlice.TopBottom() <= minSize.height); return minSize; }
static IntSize ComputeMinSizeForShadowShape(const RectCornerRadii* aCornerRadii, const IntSize& aBlurRadius, IntMargin& aOutSlice, const IntSize& aRectSize) { Size cornerSize(0, 0); if (aCornerRadii) { const RectCornerRadii& corners = *aCornerRadii; NS_FOR_CSS_FULL_CORNERS(i) { cornerSize.width = std::max(cornerSize.width, corners[i].width); cornerSize.height = std::max(cornerSize.height, corners[i].height); } } IntSize margin = IntSize::Ceil(cornerSize) + aBlurRadius; aOutSlice = IntMargin(margin.height, margin.width, margin.height, margin.width); IntSize minSize(aOutSlice.LeftRight() + 1, aOutSlice.TopBottom() + 1); // If aRectSize is smaller than minSize, the border-image approach won't // work; there's no way to squeeze parts of the min box-shadow source // image such that the result looks correct. So we need to adjust minSize // in such a way that we can later draw it without stretching in the affected // dimension. We also need to adjust "slice" to ensure that we're not trying // to slice away more than we have. if (aRectSize.width < minSize.width) { minSize.width = aRectSize.width; aOutSlice.left = 0; aOutSlice.right = 0; } if (aRectSize.height < minSize.height) { minSize.height = aRectSize.height; aOutSlice.top = 0; aOutSlice.bottom = 0; } MOZ_ASSERT(aOutSlice.LeftRight() <= minSize.width); MOZ_ASSERT(aOutSlice.TopBottom() <= minSize.height); return minSize; }
already_AddRefed<mozilla::gfx::SourceSurface> gfxAlphaBoxBlur::GetInsetBlur(IntMargin& aExtendDestBy, IntMargin& aSlice, const Rect aDestinationRect, const Rect aShadowClipRect, const IntSize& aBlurRadius, const IntSize& aSpreadRadius, const RectCornerRadii& aInnerClipRadii, const Color& aShadowColor, const bool& aHasBorderRadius, const Point aShadowOffset, bool& aMovedOffset, DrawTarget* aDestDrawTarget) { if (!gBlurCache) { gBlurCache = new BlurCache(); } IntRect outerRect; IntRect innerRect; ComputeRectsForInsetBoxShadow(aBlurRadius, aSpreadRadius, outerRect, innerRect, aSlice, aDestinationRect, aShadowClipRect, aHasBorderRadius, aInnerClipRadii); // If we have a shadow offset larger than the min rect, // there's no clean way we can properly create a min rect with the offset // in the correct place and still render correctly. In those cases, // fallback to just rendering the dest rect as is. bool useDestRect = (std::abs(aShadowOffset.x) > aSlice.left) || (std::abs(aShadowOffset.y) > aSlice.top); aMovedOffset = false; if (useDestRect) { aDestinationRect.ToIntRect(&outerRect); aShadowClipRect.ToIntRect(&innerRect); aMovedOffset = true; } BlurCacheData* cached = gBlurCache->LookupInsetBoxShadow(outerRect.Size(), innerRect.Size(), aBlurRadius, aSpreadRadius, &aInnerClipRadii, aShadowColor, aHasBorderRadius, aDestDrawTarget->GetBackendType()); if (cached && !useDestRect) { aExtendDestBy = cached->mExtendDest; // Need to extend it twice: once for the outer rect and once for the inner rect. aSlice += aExtendDestBy; aSlice += aExtendDestBy; // So we don't forget the actual cached blur RefPtr<SourceSurface> cachedBlur = cached->mBlur; return cachedBlur.forget(); } // Dirty rect and skip rect are null for the min inset shadow. // When rendering inset box shadows, we respect the spread radius by changing // the shape of the unblurred shadow, and can pass a spread radius of zero here. IntSize zeroSpread(0, 0); gfxContext* minGfxContext = Init(ThebesRect(outerRect), zeroSpread, aBlurRadius, nullptr, nullptr); if (!minGfxContext) { return nullptr; } DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget(); RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, IntRectToRect(outerRect), IntRectToRect(innerRect), aHasBorderRadius, aInnerClipRadii); Color black(0.f, 0.f, 0.f, 1.f); minGfxContext->SetColor(black); minGfxContext->SetPath(maskPath); minGfxContext->Fill(); IntPoint topLeft; RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft); if (!minMask) { return nullptr; } RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, aShadowColor); if (!minInsetBlur) { return nullptr; } IntRect blurRect(topLeft, minInsetBlur->GetSize()); aExtendDestBy = blurRect - outerRect; if (useDestRect) { // Since we're just going to paint the actual rect to the destination aSlice.SizeTo(0, 0, 0, 0); } else { aSlice += aExtendDestBy; aSlice += aExtendDestBy; CacheInsetBlur(outerRect.Size(), innerRect.Size(), aBlurRadius, aSpreadRadius, &aInnerClipRadii, aShadowColor, aHasBorderRadius, aDestDrawTarget->GetBackendType(), aExtendDestBy, minInsetBlur); } return minInsetBlur.forget(); }
static void ComputeRectsForInsetBoxShadow(IntSize aBlurRadius, IntSize aSpreadRadius, IntRect& aOutOuterRect, IntRect& aOutInnerRect, IntMargin& aOutPathMargins, const Rect& aDestRect, const Rect& aShadowClipRect, bool aHasBorderRadius, const RectCornerRadii& aInnerClipRectRadii) { IntSize rectBufferSize = aBlurRadius + aSpreadRadius; float cornerWidth = 0; float cornerHeight = 0; if (aHasBorderRadius) { for (size_t i = 0; i < 4; i++) { cornerWidth = std::max(cornerWidth, aInnerClipRectRadii[i].width); cornerHeight = std::max(cornerHeight, aInnerClipRectRadii[i].height); } } // Create the inner rect to be the smallest possible size based on // blur / spread / corner radii IntMargin innerMargin = IntMargin(ceil(cornerHeight) + rectBufferSize.height, ceil(cornerWidth) + rectBufferSize.width, ceil(cornerHeight) + rectBufferSize.height, ceil(cornerWidth) + rectBufferSize.width); aOutPathMargins = innerMargin; // If we have a negative spread radius, we would not have enough // size to actually do the blur. So the min size must be the abs() of the blur // and spread radius. IntSize minBlurSize(std::abs(aSpreadRadius.width) + std::abs(aBlurRadius.width), std::abs(aSpreadRadius.height) + std::abs(aBlurRadius.height)); IntMargin minInnerMargins = IntMargin(ceil(cornerHeight) + minBlurSize.height, ceil(cornerWidth) + minBlurSize.width, ceil(cornerHeight) + minBlurSize.height, ceil(cornerWidth) + minBlurSize.width); IntSize minInnerSize(minInnerMargins.LeftRight() + 1, minInnerMargins.TopBottom() + 1); if (aShadowClipRect.height < minInnerSize.height) { minInnerSize.height = aShadowClipRect.height; } if (aShadowClipRect.width < minInnerSize.width) { minInnerSize.width = aShadowClipRect.width; } // Then expand the outer rect based on the size between the inner/outer rects IntSize minOuterSize(minInnerSize); IntMargin outerRectMargin(rectBufferSize.height, rectBufferSize.width, rectBufferSize.height, rectBufferSize.width); minOuterSize.width += outerRectMargin.LeftRight(); minOuterSize.height += outerRectMargin.TopBottom(); aOutOuterRect = IntRect(IntPoint(), minOuterSize); aOutInnerRect = IntRect(IntPoint(rectBufferSize.width, rectBufferSize.height), minInnerSize); if (aShadowClipRect.IsEmpty()) { aOutInnerRect.width = 0; aOutInnerRect.height = 0; } }