Bug 1188075 - Speed up inner box-shadow drawing by using a border-image style approach. r=mstange

This commit is contained in:
Mason Chang 2015-09-24 09:50:29 -07:00
parent 401677e76c
commit 9e89498adf
8 changed files with 625 additions and 125 deletions

View File

@ -40,7 +40,7 @@ inline Color ToColor(const gfxRGBA &aRGBA)
Float(aRGBA.b), Float(aRGBA.a));
}
inline gfxRGBA ThebesColor(Color &aColor)
inline gfxRGBA ThebesColor(const Color &aColor)
{
return gfxRGBA(aColor.r, aColor.g, aColor.b, aColor.a);
}

View File

@ -170,16 +170,21 @@ struct BlurCacheKey : public PLDHashEntryHdr {
gfxRGBA mShadowColor;
BackendType mBackend;
RectCornerRadii mCornerRadii;
bool mIsInset;
BlurCacheKey(IntSize aMinimumSize, gfxIntSize aBlurRadius,
// Only used for inset blurs
bool mHasBorderRadius;
gfxIntSize mSpreadRadius;
IntSize mInnerMinSize;
BlurCacheKey(IntSize aMinSize, gfxIntSize aBlurRadius,
RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
BackendType aBackend)
: mMinSize(aMinimumSize)
, mBlurRadius(aBlurRadius)
, mShadowColor(aShadowColor)
, mBackend(aBackend)
, mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
{ }
BackendType aBackendType)
: BlurCacheKey(aMinSize, IntSize(0, 0),
aBlurRadius, IntSize(0, 0),
aCornerRadii, aShadowColor,
false, false, aBackendType)
{}
explicit BlurCacheKey(const BlurCacheKey* aOther)
: mMinSize(aOther->mMinSize)
@ -187,6 +192,26 @@ struct BlurCacheKey : public PLDHashEntryHdr {
, mShadowColor(aOther->mShadowColor)
, mBackend(aOther->mBackend)
, mCornerRadii(aOther->mCornerRadii)
, mIsInset(aOther->mIsInset)
, mHasBorderRadius(aOther->mHasBorderRadius)
, mSpreadRadius(aOther->mSpreadRadius)
, mInnerMinSize(aOther->mInnerMinSize)
{ }
explicit BlurCacheKey(IntSize aOuterMinSize, IntSize aInnerMinSize,
gfxIntSize aBlurRadius, gfxIntSize aSpreadRadius,
const RectCornerRadii* aCornerRadii, gfxRGBA aShadowColor,
bool aIsInset,
bool aHasBorderRadius, BackendType aBackendType)
: mMinSize(aOuterMinSize)
, mBlurRadius(aBlurRadius)
, mShadowColor(aShadowColor)
, mBackend(aBackendType)
, mCornerRadii(aCornerRadii ? *aCornerRadii : RectCornerRadii())
, mIsInset(aIsInset)
, mHasBorderRadius(aHasBorderRadius)
, mSpreadRadius(aSpreadRadius)
, mInnerMinSize(aInnerMinSize)
{ }
static PLDHashNumber
@ -202,26 +227,42 @@ struct BlurCacheKey : public PLDHashEntryHdr {
hash = AddToHash(hash, HashBytes(&aKey->mShadowColor.a, sizeof(gfxFloat)));
for (int i = 0; i < 4; i++) {
hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
hash = AddToHash(hash, aKey->mCornerRadii[i].width, aKey->mCornerRadii[i].height);
}
hash = AddToHash(hash, (uint32_t)aKey->mBackend);
if (aKey->mIsInset) {
hash = AddToHash(hash, aKey->mSpreadRadius.width, aKey->mSpreadRadius.height);
hash = AddToHash(hash, aKey->mInnerMinSize.width, aKey->mInnerMinSize.height);
hash = AddToHash(hash, HashBytes(&aKey->mHasBorderRadius, sizeof(bool)));
}
return hash;
}
bool KeyEquals(KeyTypePointer aKey) const
bool
KeyEquals(KeyTypePointer aKey) const
{
if (aKey->mMinSize == mMinSize &&
aKey->mBlurRadius == mBlurRadius &&
aKey->mCornerRadii == mCornerRadii &&
aKey->mShadowColor == mShadowColor &&
aKey->mBackend == mBackend) {
if (mIsInset) {
return (mHasBorderRadius == aKey->mHasBorderRadius) &&
(mInnerMinSize == aKey->mInnerMinSize) &&
(mSpreadRadius == aKey->mSpreadRadius);
}
return true;
}
return false;
}
static KeyTypePointer KeyToPointer(KeyType aKey)
static KeyTypePointer
KeyToPointer(KeyType aKey)
{
return &aKey;
}
@ -291,6 +332,27 @@ class BlurCache final : public nsExpirationTracker<BlurCacheData,4>
return blur;
}
BlurCacheData* LookupInsetBoxShadow(const IntSize aOuterMinSize,
const IntSize aInnerMinSize,
const gfxIntSize& aBlurRadius,
const gfxIntSize& aSpreadRadius,
const RectCornerRadii* aCornerRadii,
const gfxRGBA& aShadowColor,
const bool& aHasBorderRadius,
BackendType aBackendType)
{
BlurCacheKey key(aOuterMinSize, aInnerMinSize,
aBlurRadius, aSpreadRadius,
aCornerRadii, aShadowColor,
true, aHasBorderRadius, aBackendType);
BlurCacheData* blur = mHashEntries.Get(key);
if (blur) {
MarkUsed(blur);
}
return blur;
}
// Returns true if we successfully register the blur in the cache, false
// otherwise.
bool RegisterEntry(BlurCacheData* aValue)
@ -431,7 +493,7 @@ CreateBlurMask(const IntSize& aRectSize,
}
static already_AddRefed<SourceSurface>
CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
CreateBoxShadow(SourceSurface* aBlurMask, const gfxRGBA& aShadowColor)
{
IntSize blurredSize = aBlurMask->GetSize();
gfxPlatform* platform = gfxPlatform::GetPlatform();
@ -447,7 +509,7 @@ CreateBoxShadow(DrawTarget& aDT, SourceSurface* aBlurMask, const gfxRGBA& aShado
return boxShadowDT->Snapshot();
}
SourceSurface*
static SourceSurface*
GetBlur(DrawTarget& aDT,
const IntSize& aRectSize,
const gfxIntSize& aBlurRadius,
@ -480,7 +542,7 @@ GetBlur(DrawTarget& aDT,
return nullptr;
}
RefPtr<SourceSurface> boxShadow = CreateBoxShadow(aDT, blurMask, aShadowColor);
RefPtr<SourceSurface> boxShadow = CreateBoxShadow(blurMask, aShadowColor);
if (!boxShadow) {
return nullptr;
}
@ -541,6 +603,68 @@ DrawCorner(DrawTarget& aDT, SourceSurface* aSurface,
aDT.DrawSurface(aSurface, aDest, aSrc);
}
static void
DrawBoxShadows(DrawTarget& aDestDrawTarget, SourceSurface* aSourceBlur,
Rect aDstOuter, Rect aDstInner, Rect aSrcOuter, Rect aSrcInner,
Rect aSkipRect)
{
// Corners: top left, top right, bottom left, bottom right
DrawCorner(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.X(),
aDstInner.Y(), aDstOuter.X()),
RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.X(),
aSrcInner.Y(), aSrcOuter.X()),
aSkipRect);
DrawCorner(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstOuter.Y(), aDstOuter.XMost(),
aDstInner.Y(), aDstInner.XMost()),
RectWithEdgesTRBL(aSrcOuter.Y(), aSrcOuter.XMost(),
aSrcInner.Y(), aSrcInner.XMost()),
aSkipRect);
DrawCorner(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.X(),
aDstOuter.YMost(), aDstOuter.X()),
RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.X(),
aSrcOuter.YMost(), aSrcOuter.X()),
aSkipRect);
DrawCorner(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstInner.YMost(), aDstOuter.XMost(),
aDstOuter.YMost(), aDstInner.XMost()),
RectWithEdgesTRBL(aSrcInner.YMost(), aSrcOuter.XMost(),
aSrcOuter.YMost(), aSrcInner.XMost()),
aSkipRect);
// Edges: top, left, right, bottom
RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstOuter.Y(), aDstInner.XMost(),
aDstInner.Y(), aDstInner.X()),
RectWithEdgesTRBL(aSrcOuter.Y(), aSrcInner.XMost(),
aSrcInner.Y(), aSrcInner.X()),
aSkipRect);
RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstInner.Y(), aDstInner.X(),
aDstInner.YMost(), aDstOuter.X()),
RectWithEdgesTRBL(aSrcInner.Y(), aSrcInner.X(),
aSrcInner.YMost(), aSrcOuter.X()),
aSkipRect);
RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstInner.Y(), aDstOuter.XMost(),
aDstInner.YMost(), aDstInner.XMost()),
RectWithEdgesTRBL(aSrcInner.Y(), aSrcOuter.XMost(),
aSrcInner.YMost(), aSrcInner.XMost()),
aSkipRect);
RepeatOrStretchSurface(aDestDrawTarget, aSourceBlur,
RectWithEdgesTRBL(aDstInner.YMost(), aDstInner.XMost(),
aDstOuter.YMost(), aDstInner.X()),
RectWithEdgesTRBL(aSrcInner.YMost(), aSrcInner.XMost(),
aSrcOuter.YMost(), aSrcInner.X()),
aSkipRect);
}
/***
* We draw a blurred a rectangle by only blurring a smaller rectangle and
* splitting the rectangle into 9 parts.
@ -598,60 +722,8 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
// The target rect is smaller than the minimal size so just draw the surface
destDrawTarget.DrawSurface(boxShadow, dstInner, srcInner);
} else {
// Corners: top left, top right, bottom left, bottom right
DrawCorner(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstOuter.Y(), dstInner.X(),
dstInner.Y(), dstOuter.X()),
RectWithEdgesTRBL(srcOuter.Y(), srcInner.X(),
srcInner.Y(), srcOuter.X()),
skipRect);
DrawCorner(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstOuter.Y(), dstOuter.XMost(),
dstInner.Y(), dstInner.XMost()),
RectWithEdgesTRBL(srcOuter.Y(), srcOuter.XMost(),
srcInner.Y(), srcInner.XMost()),
skipRect);
DrawCorner(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstInner.YMost(), dstInner.X(),
dstOuter.YMost(), dstOuter.X()),
RectWithEdgesTRBL(srcInner.YMost(), srcInner.X(),
srcOuter.YMost(), srcOuter.X()),
skipRect);
DrawCorner(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstInner.YMost(), dstOuter.XMost(),
dstOuter.YMost(), dstInner.XMost()),
RectWithEdgesTRBL(srcInner.YMost(), srcOuter.XMost(),
srcOuter.YMost(), srcInner.XMost()),
skipRect);
// Edges: top, left, right, bottom
RepeatOrStretchSurface(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstOuter.Y(), dstInner.XMost(),
dstInner.Y(), dstInner.X()),
RectWithEdgesTRBL(srcOuter.Y(), srcInner.XMost(),
srcInner.Y(), srcInner.X()),
skipRect);
RepeatOrStretchSurface(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstInner.Y(), dstInner.X(),
dstInner.YMost(), dstOuter.X()),
RectWithEdgesTRBL(srcInner.Y(), srcInner.X(),
srcInner.YMost(), srcOuter.X()),
skipRect);
RepeatOrStretchSurface(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstInner.Y(), dstOuter.XMost(),
dstInner.YMost(), dstInner.XMost()),
RectWithEdgesTRBL(srcInner.Y(), srcOuter.XMost(),
srcInner.YMost(), srcInner.XMost()),
skipRect);
RepeatOrStretchSurface(destDrawTarget, boxShadow,
RectWithEdgesTRBL(dstInner.YMost(), dstInner.XMost(),
dstOuter.YMost(), dstInner.X()),
RectWithEdgesTRBL(srcInner.YMost(), srcInner.XMost(),
srcOuter.YMost(), srcInner.X()),
skipRect);
DrawBoxShadows(destDrawTarget, boxShadow, dstOuter, dstInner,
srcOuter, srcInner, skipRect);
// Middle part
RepeatOrStretchSurface(destDrawTarget, boxShadow,
@ -682,3 +754,252 @@ gfxAlphaBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
destDrawTarget.PopClip();
}
static already_AddRefed<Path>
GetBoxShadowInsetPath(DrawTarget* aDrawTarget,
const Rect aOuterRect, const Rect aInnerRect,
const bool aHasBorderRadius, const RectCornerRadii& aInnerClipRadii)
{
/***
* We create an inset path by having two rects.
*
* -----------------------
* | ________________ |
* | | | |
* | | | |
* | ------------------ |
* |_____________________|
*
* The outer rect and the inside rect. The path
* creates a frame around the content where we draw the inset shadow.
*/
RefPtr<PathBuilder> builder =
aDrawTarget->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
AppendRectToPath(builder, aOuterRect, true);
if (aHasBorderRadius) {
AppendRoundedRectToPath(builder, aInnerRect, aInnerClipRadii, false);
} else {
AppendRectToPath(builder, aInnerRect, false);
}
return builder->Finish();
}
static void
ComputeRectsForInsetBoxShadow(gfxIntSize aBlurRadius,
gfxIntSize aSpreadRadius,
Rect& aOutOuterRect,
Rect& aOutInnerRect,
Margin& aOutPathMargins,
const Rect& aDestRect,
const Rect& aShadowClipRect)
{
gfxIntSize marginSize = aBlurRadius + aSpreadRadius;
aOutPathMargins.SizeTo(marginSize.height, marginSize.width, marginSize.height, marginSize.width);
aOutPathMargins += aOutPathMargins;
aOutOuterRect.x = 0;
aOutInnerRect.x = marginSize.width;
aOutOuterRect.y = 0;
aOutInnerRect.y = marginSize.height;
// + 1 for the middle edges so we can sample them
aOutInnerRect.width = aOutPathMargins.LeftRight() + 1;
aOutInnerRect.height = aOutPathMargins.TopBottom() + 1;
// The outer path rect needs to be 1 blur radius past the inner edges
aOutOuterRect.width = aOutInnerRect.XMost() + marginSize.width;
aOutOuterRect.height = aOutInnerRect.YMost() + marginSize.height;
if ((aOutOuterRect.width >= aDestRect.width) ||
(aOutOuterRect.height >= aDestRect.height) ||
(aOutInnerRect.width >= aShadowClipRect.width) ||
(aOutInnerRect.height >= aShadowClipRect.height))
{
aOutOuterRect.width = aDestRect.width;
aOutOuterRect.height = aDestRect.height;
aOutInnerRect.width = aShadowClipRect.width;
aOutInnerRect.height = aShadowClipRect.height;
aOutPathMargins.SizeTo(0, 0, 0, 0);
}
}
static void
FillDestinationPath(gfxContext* aDestinationCtx,
const Rect aDestinationRect,
const Rect aShadowClipRect,
const Color& aShadowColor,
const bool aHasBorderRadius,
const RectCornerRadii& aInnerClipRadii)
{
// When there is no blur radius, fill the path onto the destination
// surface.
aDestinationCtx->SetColor(ThebesColor(aShadowColor));
DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
RefPtr<Path> shadowPath = GetBoxShadowInsetPath(destDrawTarget, aDestinationRect,
aShadowClipRect, aHasBorderRadius,
aInnerClipRadii);
aDestinationCtx->SetPath(shadowPath);
aDestinationCtx->Fill();
}
void
CacheInsetBlur(const IntSize aMinOuterSize,
const IntSize aMinInnerSize,
const gfxIntSize& aBlurRadius,
const gfxIntSize& aSpreadRadius,
const RectCornerRadii* aCornerRadii,
const gfxRGBA& aShadowColor,
const bool& aHasBorderRadius,
BackendType aBackendType,
IntMargin aExtendBy,
SourceSurface* aBoxShadow)
{
BlurCacheKey key(aMinOuterSize, aMinInnerSize,
aBlurRadius, aSpreadRadius,
aCornerRadii, aShadowColor,
true, aHasBorderRadius, aBackendType);
BlurCacheData* data = new BlurCacheData(aBoxShadow, aExtendBy, key);
if (!gBlurCache->RegisterEntry(data)) {
delete data;
}
}
already_AddRefed<mozilla::gfx::SourceSurface>
gfxAlphaBoxBlur::GetInsetBlur(Rect& aOuterRect,
Rect& aInnerRect,
const gfxIntSize& aBlurRadius,
const gfxIntSize& aSpreadRadius,
const RectCornerRadii& aInnerClipRadii,
const Color& aShadowColor,
const bool& aHasBorderRadius,
IntPoint& aOutTopLeft,
gfxContext* aDestinationCtx)
{
if (!gBlurCache) {
gBlurCache = new BlurCache();
}
gfxIntSize outerRectSize = RoundedToInt(aOuterRect).Size();
gfxIntSize innerRectSize = RoundedToInt(aInnerRect).Size();
DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
BlurCacheData* cached =
gBlurCache->LookupInsetBoxShadow(outerRectSize, innerRectSize, aBlurRadius, aSpreadRadius,
&aInnerClipRadii, ThebesColor(aShadowColor),
aHasBorderRadius, destDrawTarget->GetBackendType());
if (cached) {
IntMargin extends = cached->mExtendDest;
aOutTopLeft.x = extends.left;
aOutTopLeft.y = extends.top;
// 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.
gfxIntSize zeroSpread(0, 0);
gfxContext* minGfxContext = Init(ThebesRect(aOuterRect), zeroSpread, aBlurRadius, nullptr, nullptr);
if (!minGfxContext) {
return nullptr;
}
DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, aOuterRect,
aInnerRect, aHasBorderRadius,
aInnerClipRadii);
minGfxContext->SetColor(ThebesColor(aShadowColor));
minGfxContext->SetPath(maskPath);
minGfxContext->Fill();
RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &aOutTopLeft);
if (!minMask) {
return nullptr;
}
RefPtr<SourceSurface> minInsetBlur = CreateBoxShadow(minMask, ThebesColor(aShadowColor));
if (!minInsetBlur) {
return nullptr;
}
IntMargin extendBy(aOutTopLeft.y, 0, 0, aOutTopLeft.x);
CacheInsetBlur(outerRectSize, innerRectSize,
aBlurRadius, aSpreadRadius,
&aInnerClipRadii, ThebesColor(aShadowColor),
aHasBorderRadius, destDrawTarget->GetBackendType(),
extendBy, minInsetBlur);
return minInsetBlur.forget();
}
/***
* Blur an inset box shadow by doing:
* 1) Create a minimal box shadow path that creates a frame.
* 2) Draw the box shadow portion over the destination surface.
* 3) The "inset" part is created by a clip rect that properly clips
* the alpha mask so that it has clean edges. We still create the full
* proper alpha mask, but let the clip deal with the clean edges.
*
* All parameters should already be in device pixels.
*/
void
gfxAlphaBoxBlur::BlurInsetBox(gfxContext* aDestinationCtx,
const Rect aDestinationRect,
const Rect aShadowClipRect,
const gfxIntSize aBlurRadius,
const gfxIntSize aSpreadRadius,
const Color& aShadowColor,
bool aHasBorderRadius,
const RectCornerRadii& aInnerClipRadii,
const Rect aSkipRect)
{
// Blur inset shadows ALWAYS have a 0 spread radius.
if ((aBlurRadius.width <= 0 && aBlurRadius.height <= 0)) {
FillDestinationPath(aDestinationCtx, aDestinationRect, aShadowClipRect,
aShadowColor, aHasBorderRadius, aInnerClipRadii);
return;
}
DrawTarget* destDrawTarget = aDestinationCtx->GetDrawTarget();
Rect outerRect;
Rect innerRect;
Margin pathMargins;
ComputeRectsForInsetBoxShadow(aBlurRadius, aSpreadRadius,
outerRect, innerRect,
pathMargins,
aDestinationRect,
aShadowClipRect);
IntPoint topLeft;
RefPtr<SourceSurface> minInsetBlur = GetInsetBlur(outerRect, innerRect,
aBlurRadius, aSpreadRadius,
aInnerClipRadii, aShadowColor,
aHasBorderRadius,
topLeft, aDestinationCtx);
if (!minInsetBlur) {
return;
}
Rect destRectOuter(aDestinationRect);
Rect destRectInner(destRectOuter);
destRectInner.Deflate(pathMargins);
Rect srcRectOuter(outerRect);
srcRectOuter.MoveBy(abs(topLeft.x), abs(topLeft.y));
Rect srcRectInner(srcRectOuter);
srcRectInner.Deflate(pathMargins);
if (srcRectOuter.IsEqualInterior(srcRectInner)) {
destDrawTarget->DrawSurface(minInsetBlur, destRectOuter, srcRectOuter);
} else {
DrawBoxShadows(*destDrawTarget, minInsetBlur,
destRectOuter, destRectInner,
srcRectOuter, srcRectInner,
aSkipRect);
}
}

View File

@ -20,6 +20,7 @@ struct gfxRGBA;
namespace mozilla {
namespace gfx {
class AlphaBoxBlur;
struct Color;
struct RectCornerRadii;
class SourceSurface;
class DrawTarget;
@ -135,9 +136,44 @@ public:
static void ShutdownBlurCache();
/***
* Blurs an inset box shadow according to a given path.
* This is equivalent to calling Init(), drawing the inset path,
* and calling paint. Do not call Init() if using this method.
*
* @param aDestinationCtx The destination to blur to.
* @param aDestinationRect The destination rect in device pixels
* @param aShadowClipRect The destiniation inner rect of the
* inset path in device pixels.
* @param aBlurRadius The standard deviation of the blur.
* @param aSpreadRadius The spread radius in device pixels.
* @param aShadowColor The color of the blur.
* @param aHasBorderRadius If this element also has a border radius
* @param aInnerClipRadii Corner radii for the inside rect if it is a rounded rect.
* @param aSKipRect An area in device pixels we don't have to paint in.
*/
void BlurInsetBox(gfxContext* aDestinationCtx,
const mozilla::gfx::Rect aDestinationRect,
const mozilla::gfx::Rect aShadowClipRect,
const gfxIntSize aBlurRadius,
const gfxIntSize aSpreadRadius,
const mozilla::gfx::Color& aShadowColor,
const bool aHasBorderRadius,
const RectCornerRadii& aInnerClipRadii,
const mozilla::gfx::Rect aSkipRect);
protected:
already_AddRefed<mozilla::gfx::SourceSurface>
GetInsetBlur(mozilla::gfx::Rect& aOuterRect,
mozilla::gfx::Rect& aInnerRect,
const gfxIntSize& aBlurRadius,
const gfxIntSize& aSpreadRadius,
const RectCornerRadii& aInnerClipRadii,
const mozilla::gfx::Color& aShadowColor,
const bool& aHasBorderRadius,
mozilla::gfx::IntPoint& aOutTopLeft,
gfxContext* aDestinationCtx);
/**
* The context of the temporary alpha surface.
*/

View File

@ -1581,29 +1581,22 @@ nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
gfxContext* renderContext = aRenderingContext.ThebesContext();
DrawTarget* drawTarget = renderContext->GetDrawTarget();
nsContextBoxBlur blurringArea;
gfxContext* shadowContext =
blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel,
renderContext, aDirtyRect, &skipGfxRect);
if (!shadowContext)
continue;
DrawTarget* shadowDT = shadowContext->GetDrawTarget();
// shadowContext is owned by either blurringArea or aRenderingContext.
MOZ_ASSERT(shadowContext == renderContext ||
shadowContext == blurringArea.GetContext());
// Set the shadow color; if not specified, use the foreground color
Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
shadowItem->mColor :
aForFrame->StyleColor()->mColor);
renderContext->Save();
renderContext->SetColor(ThebesColor(shadowColor));
// Clip the context to the area of the frame's padding rect, so no part of the
// shadow is painted outside. Also cut out anything beyond where the inset shadow
// will be.
Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
shadowGfxRect.Round();
// Set the shadow color; if not specified, use the foreground color
Color shadowColor = Color::FromABGR(shadowItem->mHasColor ?
shadowItem->mColor :
aForFrame->StyleColor()->mColor);
renderContext->Save();
// This clips the outside border radius.
// clipRectRadii is the border radius inside the inset shadow.
if (hasBorderRadius) {
RefPtr<Path> roundedRect =
MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
@ -1612,22 +1605,13 @@ nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
renderContext->Clip(shadowGfxRect);
}
// Fill the surface minus the area within the frame that we should
// not paint in, and blur and apply it.
RefPtr<PathBuilder> builder =
shadowDT->CreatePathBuilder(FillRule::FILL_EVEN_ODD);
AppendRectToPath(builder, shadowPaintGfxRect, true);
if (hasBorderRadius) {
AppendRoundedRectToPath(builder, shadowClipGfxRect, clipRectRadii, false);
} else {
AppendRectToPath(builder, shadowClipGfxRect, false);
}
RefPtr<Path> path = builder->Finish();
shadowContext->SetPath(path);
shadowContext->Fill();
shadowContext->NewPath();
blurringArea.DoPaint();
nsContextBoxBlur insetBoxBlur;
gfxRect destRect = nsLayoutUtils::RectToGfxRect(shadowPaintRect, twipsPerPixel);
insetBoxBlur.InsetBoxBlur(renderContext, ToRect(destRect),
shadowClipGfxRect, shadowColor,
blurRadius, spreadDistanceAppUnits,
twipsPerPixel, hasBorderRadius,
clipRectRadii, ToRect(skipGfxRect));
renderContext->Restore();
}
}
@ -5322,27 +5306,12 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
return nullptr;
}
gfxFloat scaleX = 1;
gfxFloat scaleY = 1;
gfxIntSize blurRadius;
gfxIntSize spreadRadius;
GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
aBlurRadius, aSpreadRadius,
blurRadius, spreadRadius);
// Do blurs in device space when possible.
// Chrome/Skia always does the blurs in device space
// and will sometimes get incorrect results (e.g. rotated blurs)
gfxMatrix transform = aDestinationCtx->CurrentMatrix();
// XXX: we could probably handle negative scales but for now it's easier just to fallback
if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
transform = gfxMatrix();
} else {
scaleX = transform._11;
scaleY = transform._22;
}
// compute a large or smaller blur radius
gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
gfxIntSize spreadRadius = gfxIntSize(std::min(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
int32_t(MAX_SPREAD_RADIUS)),
std::min(int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel),
int32_t(MAX_SPREAD_RADIUS)));
mDestinationCtx = aDestinationCtx;
// If not blurring, draw directly onto the destination device
@ -5360,6 +5329,7 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
dirtyRect.RoundOut();
gfxMatrix transform = aDestinationCtx->CurrentMatrix();
rect = transform.TransformBounds(rect);
mPreTransformed = !transform.IsIdentity();
@ -5386,8 +5356,9 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
void
nsContextBoxBlur::DoPaint()
{
if (mContext == mDestinationCtx)
if (mContext == mDestinationCtx) {
return;
}
gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
@ -5485,3 +5456,99 @@ nsContextBoxBlur::BlurRectangle(gfxContext* aDestinationCtx,
dirtyRect,
skipRect);
}
/* static */ void
nsContextBoxBlur::GetBlurAndSpreadRadius(gfxContext* aDestinationCtx,
int32_t aAppUnitsPerDevPixel,
nscoord aBlurRadius,
nscoord aSpreadRadius,
gfxIntSize& aOutBlurRadius,
gfxIntSize& aOutSpreadRadius,
bool aConstrainSpreadRadius)
{
gfxFloat scaleX = 1;
gfxFloat scaleY = 1;
// Do blurs in device space when possible.
// Chrome/Skia always does the blurs in device space
// and will sometimes get incorrect results (e.g. rotated blurs)
gfxMatrix transform = aDestinationCtx->CurrentMatrix();
// XXX: we could probably handle negative scales but for now it's easier just to fallback
if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 || transform._22 <= 0.0) {
transform = gfxMatrix();
} else {
scaleX = transform._11;
scaleY = transform._22;
}
// compute a large or smaller blur radius
aOutBlurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
aOutSpreadRadius =
gfxIntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
if (aConstrainSpreadRadius) {
aOutSpreadRadius.width = std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
aOutSpreadRadius.height = std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
}
}
/* static */ bool
nsContextBoxBlur::InsetBoxBlur(gfxContext* aDestinationCtx,
Rect aDestinationRect,
Rect aShadowClipRect,
Color& aShadowColor,
nscoord aBlurRadiusAppUnits,
nscoord aSpreadDistanceAppUnits,
int32_t aAppUnitsPerDevPixel,
bool aHasBorderRadius,
RectCornerRadii& aInnerClipRectRadii,
Rect aSkipRect)
{
if (aDestinationRect.IsEmpty()) {
mContext = nullptr;
return false;
}
gfxIntSize blurRadius;
gfxIntSize spreadRadius;
// Convert the blur and spread radius to device pixels
bool constrainSpreadRadius = false;
GetBlurAndSpreadRadius(aDestinationCtx, aAppUnitsPerDevPixel,
aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
blurRadius, spreadRadius, constrainSpreadRadius);
// The blur and spread radius are scaled already, so scale all
// input data to the blur. This way, we don't have to scale the min
// inset blur to the invert of the dest context, then rescale it back
// when we draw to the destination surface.
gfxSize scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
Matrix currentMatrix = ToMatrix(aDestinationCtx->CurrentMatrix());
Rect transformedDestRect = currentMatrix.TransformBounds(aDestinationRect);
Rect transformedShadowClipRect = currentMatrix.TransformBounds(aShadowClipRect);
Rect transformedSkipRect = currentMatrix.TransformBounds(aSkipRect);
transformedDestRect.Round();
transformedShadowClipRect.Round();
transformedSkipRect.RoundIn();
for (size_t i = 0; i < 4; i++) {
aInnerClipRectRadii[i].width = std::floor(scale.width * aInnerClipRectRadii[i].width);
aInnerClipRectRadii[i].height = std::floor(scale.height * aInnerClipRectRadii[i].height);
}
{
gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
aDestinationCtx->SetMatrix(gfxMatrix());
mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
transformedShadowClipRect,
blurRadius, spreadRadius,
aShadowColor,
aHasBorderRadius,
aInnerClipRectRadii, transformedSkipRect);
}
return true;
}

View File

@ -25,6 +25,7 @@ class nsRenderingContext;
namespace mozilla {
namespace gfx {
struct Color;
class DrawTarget;
} // namespace gfx
@ -944,7 +945,48 @@ public:
const nsRect& aDirtyRect,
const gfxRect& aSkipRect);
/**
* Draws a blurred inset box shadow shape onto the destination surface.
* Like BlurRectangle, this is equivalent to calling Init(),
* drawing a rectangle onto the returned surface
* and then calling DoPaint, but may let us optimize better in the
* backend.
*
* @param aDestinationCtx The destination to blur to.
* @param aDestinationRect The rectangle to blur in app units.
* @param aShadowClipRect The inside clip rect that creates the path.
* @param aShadowColor The color of the blur
* @param aBlurRadiusAppUnits The blur radius in app units
* @param aSpreadRadiusAppUnits The spread radius in app units.
* @param aAppUnitsPerDevPixel The number of app units in a device pixel,
* for conversion. Most of the time you'll
* pass this from the current PresContext if
* available.
* @param aHasBorderRadius If this inset box blur has a border radius
* @param aInnerClipRectRadii The clip rect radii used for the inside rect's path.
* @param aSkipRect An area in device pixels (NOT app units!) to avoid
* blurring over, to prevent unnecessary work.
*/
bool InsetBoxBlur(gfxContext* aDestinationCtx,
mozilla::gfx::Rect aDestinationRect,
mozilla::gfx::Rect aShadowClipRect,
mozilla::gfx::Color& aShadowColor,
nscoord aBlurRadiusAppUnits,
nscoord aSpreadRadiusAppUnits,
int32_t aAppUnitsPerDevPixel,
bool aHasBorderRadius,
RectCornerRadii& aInnerClipRectRadii,
mozilla::gfx::Rect aSkipRect);
protected:
static void GetBlurAndSpreadRadius(gfxContext* aContext,
int32_t aAppUnitsPerDevPixel,
nscoord aBlurRadius,
nscoord aSpreadRadius,
gfxIntSize& aOutBlurRadius,
gfxIntSize& aOutSpreadRadius,
bool aConstrainSpreadRadius = true);
gfxAlphaBoxBlur mAlphaBoxBlur;
nsRefPtr<gfxContext> mContext;
gfxContext* mDestinationCtx;
@ -952,7 +994,6 @@ protected:
/* This is true if the blur already has it's content transformed
* by mDestinationCtx's transform */
bool mPreTransformed;
};
#endif /* nsCSSRendering_h___ */

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<style>
#outerDiv {
width: 500px;
height: 500px;
background: lime;
position: absolute;
}
#middleBlur {
width: 300px;
height: 300px;
margin-left: 100px;
margin-top: 100px;
background: black;
box-shadow: inset 0 0 20px 100px lime;
}
</style>
<div id="outerDiv">
<div id="middleBlur">
</div>
</div>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<style>
#thediv {
width: 500px;
height: 500px;
background: black;
box-shadow: inset 0 0 20px 200px lime;
}
</style>
<div id="thediv"></div>

View File

@ -21,6 +21,7 @@ random-if(d2d) == boxshadow-threecorners.html boxshadow-threecorners-ref.html
== boxshadow-skiprect.html boxshadow-skiprect-ref.html
== boxshadow-opacity.html boxshadow-opacity-ref.html
== boxshadow-color-rounding.html boxshadow-color-rounding-ref.html
== boxshadow-color-rounding-middle.html boxshadow-color-rounding-middle-ref.html
== overflow-not-scrollable-1.html overflow-not-scrollable-1-ref.html
== overflow-not-scrollable-1.html overflow-not-scrollable-1-ref2.html