Bug 581222. Extend gfxAlphaBlur to support "spread radii". r=vlad

--HG--
extra : rebase_source : e644ae08007e52c524c3237d336441f1413f846b
This commit is contained in:
Robert O'Callahan 2010-08-23 21:30:07 +12:00
parent 81493ca326
commit 5de92d7b23
9 changed files with 178 additions and 37 deletions

View File

@ -1887,7 +1887,7 @@ nsCanvasRenderingContext2D::ShadowInitialize(const gfxRect& extents, gfxAlphaBox
blurRadius.height, blurRadius.width);
drawExtents = drawExtents.Intersect(clipExtents - CurrentState().shadowOffset);
gfxContext* ctx = blur.Init(drawExtents, blurRadius, nsnull, nsnull);
gfxContext* ctx = blur.Init(drawExtents, gfxIntSize(0,0), blurRadius, nsnull, nsnull);
if (!ctx)
return nsnull;

View File

@ -54,15 +54,16 @@ gfxAlphaBoxBlur::~gfxAlphaBoxBlur()
gfxContext*
gfxAlphaBoxBlur::Init(const gfxRect& aRect,
const gfxIntSize& aSpreadRadius,
const gfxIntSize& aBlurRadius,
const gfxRect* aDirtyRect,
const gfxRect* aSkipRect)
{
mSpreadRadius = aSpreadRadius;
mBlurRadius = aBlurRadius;
gfxRect rect(aRect);
rect.Outset(aBlurRadius.height, aBlurRadius.width,
aBlurRadius.height, aBlurRadius.width);
rect.Outset(aBlurRadius + aSpreadRadius);
rect.RoundOut();
if (rect.IsEmpty())
@ -74,8 +75,7 @@ gfxAlphaBoxBlur::Init(const gfxRect& aRect,
mHasDirtyRect = PR_TRUE;
mDirtyRect = *aDirtyRect;
gfxRect requiredBlurArea = mDirtyRect.Intersect(rect);
requiredBlurArea.Outset(aBlurRadius.height, aBlurRadius.width,
aBlurRadius.height, aBlurRadius.width);
requiredBlurArea.Outset(aBlurRadius + aSpreadRadius);
rect = requiredBlurArea.Intersect(rect);
} else {
mHasDirtyRect = PR_FALSE;
@ -83,14 +83,13 @@ gfxAlphaBoxBlur::Init(const gfxRect& aRect,
if (aSkipRect) {
// If we get passed a skip rect, we can lower the amount of
// blurring we need to do. We convert it to nsIntRect to avoid
// blurring/spreading we need to do. We convert it to nsIntRect to avoid
// expensive int<->float conversions if we were to use gfxRect instead.
gfxRect skipRect = *aSkipRect;
skipRect.RoundIn();
skipRect.Inset(aBlurRadius + aSpreadRadius);
mSkipRect = gfxThebesUtils::GfxRectToIntRect(skipRect);
nsIntRect shadowIntRect = gfxThebesUtils::GfxRectToIntRect(rect);
mSkipRect.Deflate(aBlurRadius.width, aBlurRadius.height);
mSkipRect.IntersectRect(mSkipRect, shadowIntRect);
if (mSkipRect == shadowIntRect)
return nsnull;
@ -140,6 +139,7 @@ gfxAlphaBoxBlur::PremultiplyAlpha(gfxFloat alpha)
* @param aWidth The number of columns in the buffers.
* @param aRows The number of rows in the buffers.
* @param aSkipRect An area to skip blurring in.
* XXX shouldn't we pass stride in separately here?
*/
static void
BoxBlurHorizontal(unsigned char* aInput,
@ -152,7 +152,7 @@ BoxBlurHorizontal(unsigned char* aInput,
{
PRInt32 boxSize = aLeftLobe + aRightLobe + 1;
PRBool skipRectCoversWholeRow = 0 >= aSkipRect.x &&
aWidth - 1 <= aSkipRect.XMost();
aWidth <= aSkipRect.XMost();
for (PRInt32 y = 0; y < aRows; y++) {
// Check whether the skip rect intersects this row. If the skip
@ -206,6 +206,7 @@ BoxBlurHorizontal(unsigned char* aInput,
/**
* Identical to BoxBlurHorizontal, except it blurs top and bottom instead of
* left and right.
* XXX shouldn't we pass stride in separately here?
*/
static void
BoxBlurVertical(unsigned char* aInput,
@ -218,7 +219,7 @@ BoxBlurVertical(unsigned char* aInput,
{
PRInt32 boxSize = aTopLobe + aBottomLobe + 1;
PRBool skipRectCoversWholeColumn = 0 >= aSkipRect.y &&
aRows - 1 <= aSkipRect.YMost();
aRows <= aSkipRect.YMost();
for (PRInt32 x = 0; x < aWidth; x++) {
PRBool inSkipRectX = x >= aSkipRect.x &&
@ -304,6 +305,99 @@ static void ComputeLobes(PRInt32 aRadius, PRInt32 aLobes[3][2])
aLobes[2][1] = final;
}
static void
SpreadHorizontal(unsigned char* aInput,
unsigned char* aOutput,
PRInt32 aRadius,
PRInt32 aWidth,
PRInt32 aRows,
PRInt32 aStride,
const nsIntRect& aSkipRect)
{
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride*aRows);
return;
}
PRBool skipRectCoversWholeRow = 0 >= aSkipRect.x &&
aWidth <= aSkipRect.XMost();
for (PRInt32 y = 0; y < aRows; y++) {
// Check whether the skip rect intersects this row. If the skip
// rect covers the whole surface in this row, we can avoid
// this row entirely (and any others along the skip rect).
PRBool inSkipRectY = y >= aSkipRect.y &&
y < aSkipRect.YMost();
if (inSkipRectY && skipRectCoversWholeRow) {
y = aSkipRect.YMost() - 1;
continue;
}
for (PRInt32 x = 0; x < aWidth; x++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectY && x >= aSkipRect.x &&
x < aSkipRect.XMost()) {
x = aSkipRect.XMost();
if (x >= aWidth)
break;
}
PRInt32 sMin = PR_MAX(x - aRadius, 0);
PRInt32 sMax = PR_MIN(x + aRadius, aWidth - 1);
PRInt32 v = 0;
for (PRInt32 s = sMin; s <= sMax; ++s) {
v = PR_MAX(v, aInput[aStride * y + s]);
}
aOutput[aStride * y + x] = v;
}
}
}
static void
SpreadVertical(unsigned char* aInput,
unsigned char* aOutput,
PRInt32 aRadius,
PRInt32 aWidth,
PRInt32 aRows,
PRInt32 aStride,
const nsIntRect& aSkipRect)
{
if (aRadius == 0) {
memcpy(aOutput, aInput, aStride*aRows);
return;
}
PRBool skipRectCoversWholeColumn = 0 >= aSkipRect.y &&
aRows <= aSkipRect.YMost();
for (PRInt32 x = 0; x < aWidth; x++) {
PRBool inSkipRectX = x >= aSkipRect.x &&
x < aSkipRect.XMost();
if (inSkipRectX && skipRectCoversWholeColumn) {
x = aSkipRect.XMost() - 1;
continue;
}
for (PRInt32 y = 0; y < aRows; y++) {
// Check whether we are within the skip rect. If so, go
// to the next point outside the skip rect.
if (inSkipRectX && y >= aSkipRect.y &&
y < aSkipRect.YMost()) {
y = aSkipRect.YMost();
if (y >= aRows)
break;
}
PRInt32 sMin = PR_MAX(y - aRadius, 0);
PRInt32 sMax = PR_MIN(y + aRadius, aRows - 1);
PRInt32 v = 0;
for (PRInt32 s = sMin; s <= sMax; ++s) {
v = PR_MAX(v, aInput[aStride * s + x]);
}
aOutput[aStride * y + x] = v;
}
}
}
void
gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
{
@ -312,8 +406,8 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
unsigned char* boxData = mImageSurface->Data();
// no need to do all this if not blurring
if (mBlurRadius.width != 0 || mBlurRadius.height != 0) {
// no need to do all this if not blurring or spreading
if (mBlurRadius != gfxIntSize(0,0) || mSpreadRadius != gfxIntSize(0,0)) {
nsTArray<unsigned char> tempAlphaDataBuf;
PRSize szB = mImageSurface->GetDataSize();
if (!tempAlphaDataBuf.SetLength(szB))
@ -323,11 +417,16 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
// .SetLength above doesn't initialise the new elements since
// they are unsigned chars and so have no default constructor.
// So we have to initialise them by hand.
// https://bugzilla.mozilla.org/show_bug.cgi?id=582668#c10
memset(tmpData, 0, szB);
PRInt32 stride = mImageSurface->Stride();
PRInt32 rows = mImageSurface->Height();
PRInt32 width = mImageSurface->Width();
if (mSpreadRadius.width > 0 || mSpreadRadius.height > 0) {
SpreadHorizontal(boxData, tmpData, mSpreadRadius.width, width, rows, stride, mSkipRect);
SpreadVertical(tmpData, boxData, mSpreadRadius.height, width, rows, stride, mSkipRect);
}
if (mBlurRadius.width > 0) {
PRInt32 lobes[3][2];
@ -335,6 +434,8 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
BoxBlurHorizontal(boxData, tmpData, lobes[0][0], lobes[0][1], stride, rows, mSkipRect);
BoxBlurHorizontal(tmpData, boxData, lobes[1][0], lobes[1][1], stride, rows, mSkipRect);
BoxBlurHorizontal(boxData, tmpData, lobes[2][0], lobes[2][1], stride, rows, mSkipRect);
} else {
memcpy(tmpData, boxData, stride*rows);
}
if (mBlurRadius.height > 0) {
@ -343,20 +444,22 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
BoxBlurVertical(tmpData, boxData, lobes[0][0], lobes[0][1], stride, rows, mSkipRect);
BoxBlurVertical(boxData, tmpData, lobes[1][0], lobes[1][1], stride, rows, mSkipRect);
BoxBlurVertical(tmpData, boxData, lobes[2][0], lobes[2][1], stride, rows, mSkipRect);
} else {
memcpy(boxData, tmpData, stride*rows);
}
}
// Avoid a semi-expensive clip operation if we can, otherwise
// clip to the dirty rect
if (mHasDirtyRect) {
aDestinationCtx->Save();
aDestinationCtx->NewPath();
aDestinationCtx->Rectangle(mDirtyRect);
aDestinationCtx->Clip();
aDestinationCtx->Mask(mImageSurface, offset);
aDestinationCtx->Restore();
aDestinationCtx->Save();
aDestinationCtx->NewPath();
aDestinationCtx->Rectangle(mDirtyRect);
aDestinationCtx->Clip();
aDestinationCtx->Mask(mImageSurface, offset);
aDestinationCtx->Restore();
} else {
aDestinationCtx->Mask(mImageSurface, offset);
aDestinationCtx->Mask(mImageSurface, offset);
}
}

View File

@ -46,10 +46,13 @@
/**
* Implementation of a box blur approximation of a Gaussian blur.
*
* Creates an 8-bit alpha channel context for callers to draw in, blurs the
* contents of that context and applies it as an alpha mask on a
* different existing context.
*
* Creates an 8-bit alpha channel context for callers to draw in,
* spreads the contents of that context, blurs the contents, and applies
* it as an alpha mask on a different existing context.
*
* A spread N makes each output pixel the maximum value of all source
* pixels within a square of side length 2N+1 centered on the output pixel.
*
* A temporary surface is created in the Init function. The caller then draws
* any desired content onto the context acquired through GetContext, and lastly
* calls Paint to apply the blurred content as an alpha mask.
@ -75,6 +78,7 @@ public:
* pass NULL here.
*/
gfxContext* Init(const gfxRect& aRect,
const gfxIntSize& aSpreadRadius,
const gfxIntSize& aBlurRadius,
const gfxRect* aDirtyRect,
const gfxRect* aSkipRect);
@ -95,8 +99,8 @@ public:
void PremultiplyAlpha(gfxFloat alpha);
/**
* Does the actual blurring and mask applying. Users of this object
* must have drawn whatever they want to be blurred onto the internal
* Does the actual blurring/spreading and mask applying. Users of this
* object must have drawn whatever they want to be blurred onto the internal
* gfxContext returned by GetContext before calling this.
*
* @param aDestinationCtx The graphics context on which to apply the
@ -111,6 +115,10 @@ public:
static gfxIntSize CalculateBlurRadius(const gfxPoint& aStandardDeviation);
protected:
/**
* The spread radius, in pixels.
*/
gfxIntSize mSpreadRadius;
/**
* The blur radius, in pixels.
*/

View File

@ -137,6 +137,10 @@ struct THEBES_API gfxRect {
Inset(sides[0], sides[1], sides[2], sides[3]);
}
void Inset(const gfxIntSize& size) {
Inset(size.height, size.width, size.height, size.width);
}
void Outset(gfxFloat k) {
pos.x -= k;
pos.y -= k;
@ -155,6 +159,10 @@ struct THEBES_API gfxRect {
Outset(sides[0], sides[1], sides[2], sides[3]);
}
void Outset(const gfxIntSize& size) {
Outset(size.height, size.width, size.height, size.width);
}
// Round the rectangle edges to integer coordinates, such that the rounded
// rectangle has the same set of pixel centers as the original rectangle.
// Edges at offset 0.5 round up.

View File

@ -1227,7 +1227,7 @@ nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
nsRefPtr<gfxContext> shadowContext;
nsContextBoxBlur blurringArea;
shadowContext = blurringArea.Init(shadowRect, blurRadius, twipsPerPixel, renderContext,
shadowContext = blurringArea.Init(shadowRect, 0, blurRadius, twipsPerPixel, renderContext,
aDirtyRect, &skipGfxRect);
if (!shadowContext)
continue;
@ -1398,7 +1398,7 @@ nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
nsRefPtr<gfxContext> shadowContext;
nsContextBoxBlur blurringArea;
shadowContext = blurringArea.Init(shadowPaintRect, blurRadius, twipsPerPixel, renderContext,
shadowContext = blurringArea.Init(shadowPaintRect, 0, blurRadius, twipsPerPixel, renderContext,
aDirtyRect, &skipGfxRect);
if (!shadowContext)
continue;
@ -3827,16 +3827,19 @@ ImageRenderer::Draw(nsPresContext* aPresContext,
}
#define MAX_BLUR_RADIUS 300
#define MAX_SPREAD_RADIUS 50
// -----
// nsContextBoxBlur
// -----
gfxContext*
nsContextBoxBlur::Init(const nsRect& aRect, nscoord aBlurRadius,
nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
nscoord aBlurRadius,
PRInt32 aAppUnitsPerDevPixel,
gfxContext* aDestinationCtx,
const nsRect& aDirtyRect,
const gfxRect* aSkipRect)
const gfxRect* aSkipRect,
PRUint32 aFlags)
{
if (aRect.IsEmpty()) {
mContext = nsnull;
@ -3845,10 +3848,12 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aBlurRadius,
PRInt32 blurRadius = static_cast<PRInt32>(aBlurRadius / aAppUnitsPerDevPixel);
blurRadius = PR_MIN(blurRadius, MAX_BLUR_RADIUS);
PRInt32 spreadRadius = static_cast<PRInt32>(aSpreadRadius / aAppUnitsPerDevPixel);
spreadRadius = PR_MIN(spreadRadius, MAX_BLUR_RADIUS);
mDestinationCtx = aDestinationCtx;
// If not blurring, draw directly onto the destination device
if (blurRadius <= 0) {
if (blurRadius <= 0 && spreadRadius <= 0 && !(aFlags & FORCE_MASK)) {
mContext = aDestinationCtx;
return mContext;
}
@ -3860,7 +3865,8 @@ nsContextBoxBlur::Init(const nsRect& aRect, nscoord aBlurRadius,
dirtyRect.RoundOut();
// Create the temporary surface for blurring
mContext = blur.Init(rect, gfxIntSize(blurRadius, blurRadius),
mContext = blur.Init(rect, gfxIntSize(spreadRadius, spreadRadius),
gfxIntSize(blurRadius, blurRadius),
&dirtyRect, aSkipRect);
return mContext;
}

View File

@ -391,6 +391,9 @@ protected:
*/
class nsContextBoxBlur {
public:
enum {
FORCE_MASK = 0x01
};
/**
* Prepares a gfxContext to draw on. Do not call this twice; if you want
* to get the gfxContext again use GetContext().
@ -421,6 +424,10 @@ public:
*
* @param aSkipRect An area in device pixels (NOT app units!) to avoid
* blurring over, to prevent unnecessary work.
*
* @param aFlags FORCE_MASK to ensure that the content drawn to the
* returned gfxContext is used as a mask, and not
* drawn directly to aDestinationCtx.
*
* @return A blank 8-bit alpha-channel-only graphics context to
* draw on, or null on error. Must not be freed. The
@ -435,10 +442,19 @@ public:
* should prepare the destination context as if you were going to draw
* directly on it instead of any temporary surface created in this class.
*/
gfxContext* Init(const nsRect& aRect, nscoord aBlurRadius,
gfxContext* Init(const nsRect& aRect, nscoord aSpreadRadius,
nscoord aBlurRadius,
PRInt32 aAppUnitsPerDevPixel, gfxContext* aDestinationCtx,
const nsRect& aDirtyRect, const gfxRect* aSkipRect);
const nsRect& aDirtyRect, const gfxRect* aSkipRect,
PRUint32 aFlags = 0);
/**
* Does the actual blurring/spreading. Users of this object *must*
* have called Init() first, then have drawn whatever they want to be
* blurred onto the internal gfxContext before calling this.
*/
void DoEffects();
/**
* Does the actual blurring and mask applying. Users of this object *must*
* have called Init() first, then have drawn whatever they want to be

View File

@ -293,7 +293,7 @@ nsDisplayTextShadow::Paint(nsDisplayListBuilder* aBuilder,
// Create our shadow surface, then paint the text decorations onto it
nsContextBoxBlur contextBoxBlur;
gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, shadow->mRadius,
gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, 0, shadow->mRadius,
presContext->AppUnitsPerDevPixel(),
thebesCtx, mVisibleRect, nsnull);
if (!shadowCtx) {

View File

@ -4548,7 +4548,7 @@ nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength,
shadowGfxRect.Width(), shadowGfxRect.Height());
nsContextBoxBlur contextBoxBlur;
gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius,
gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
PresContext()->AppUnitsPerDevPixel(),
aCtx, aDirtyRect, nsnull);
if (!shadowContext)

View File

@ -582,7 +582,7 @@ void nsTextBoxFrame::PaintOneShadow(gfxContext* aCtx,
shadowRect.MoveBy(shadowOffset);
nsContextBoxBlur contextBoxBlur;
gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius,
gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
PresContext()->AppUnitsPerDevPixel(),
aCtx, aDirtyRect, nsnull);