Change the blur radius for -moz-box-shadow and text-shadow to match what is specified in css3-background, and the blur radius for canvas to follow what is specified in HTML5. (Bug 590039) r=roc a2.0=blocking2.0:beta6
authorL. David Baron <dbaron@dbaron.org>
Sat, 11 Sep 2010 09:27:12 -0700
changeset 52476 830111e10951
parent 52475 aefb8c332502
child 52477 7e330021ce68
push id15656
push userdbaron@mozilla.com
push date2010-09-11 16:27 +0000
treeherdermozilla-central@26bfc0860822 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs590039, 467518
milestone2.0b6pre
Change the blur radius for -moz-box-shadow and text-shadow to match what is specified in css3-background, and the blur radius for canvas to follow what is specified in HTML5. (Bug 590039) r=roc a2.0=blocking2.0:beta6

This fixes the multiplication by 1.5 in
gfxAlphaBoxBlur::CalculateBlurRadius (originally added in changeset
ce9f05b57b95 for bug 467518) to work correctly. It was previously a
multiplication by 1 due to integer division. CalculateBlurRadius
previously multiplied by 1.880; it now multiplies by 2.820.

This changes canvas shadow handling to multiply shadowBlur by 2 before
taking its square root, as described in the spec. This means that
canvas shadow blurs 8px or smaller are 1.5 times larger than they were
previously (due to the CalculateBlurRadius change), and canvas shadow
blurs larger than 8px are 2.121 times larger than they were previously
(due to the CalculateBlurRadius change *and* the additional factor of
sqrt(2)).

This changes text-shadow and -moz-box-shadow handling to use
CalculateBlurRadius on half of the value given instead of passing the
value through directly. This means that text-shadow and box-shadow
blurs are multiplied by 1.410 relative to their old sizes. It also
means that we round rather than floor, so that the effect that used to
be drawn by a blur in the range 1px to 1.99px is now drawn by a blur
anywhere in the range 0.36px to 1.05px, the effect that used to be drawn
by a blur in the range 2px to 2.99px is now drawn by a blur anywhere in
the range 1.06px to 1.77px, what used to be a drawn by a blur in the
range 3px to 3.99px is now drawn by a blur anywhere in the range 1.78px
to 2.47px, etc.
content/canvas/src/nsCanvasRenderingContext2D.cpp
gfx/thebes/gfxBlur.cpp
gfx/thebes/gfxBlur.h
layout/base/nsCSSRendering.cpp
layout/base/nsCSSRendering.h
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.cpp
layout/reftests/box-shadow/boxshadow-blur-2-notref.html
layout/reftests/box-shadow/boxshadow-blur-2-ref.html
layout/reftests/box-shadow/boxshadow-blur-2.html
layout/reftests/box-shadow/reftest.list
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -1876,17 +1876,18 @@ nsCanvasRenderingContext2D::GetShadowCol
 
 static const gfxFloat SIGMA_MAX = 25;
 
 gfxContext*
 nsCanvasRenderingContext2D::ShadowInitialize(const gfxRect& extents, gfxAlphaBoxBlur& blur)
 {
     gfxIntSize blurRadius;
 
-    gfxFloat sigma = CurrentState().shadowBlur > 8 ? sqrt(CurrentState().shadowBlur) : CurrentState().shadowBlur / 2;
+    float shadowBlur = CurrentState().shadowBlur;
+    gfxFloat sigma = shadowBlur > 8 ? sqrt(shadowBlur * 2) : (shadowBlur / 2);
     // limit to avoid overly huge temp images
     if (sigma > SIGMA_MAX)
         sigma = SIGMA_MAX;
     blurRadius = gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(sigma, sigma));
 
     // calculate extents
     gfxRect drawExtents = extents;
 
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -458,17 +458,28 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDest
         aDestinationCtx->Clip();
         aDestinationCtx->Mask(mImageSurface, offset);
         aDestinationCtx->Restore();
     } else {
         aDestinationCtx->Mask(mImageSurface, offset);
     }
 }
 
-// Blur radius is approximately 3/2 times the box-blur size
-static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * (3/2);
+/**
+ * Compute the box blur size (which we're calling the blur radius) from
+ * the standard deviation.
+ *
+ * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
+ * approximating a Gaussian using box blurs.  This yields quite a good
+ * approximation for a Gaussian.  Then we multiply this by 1.5 since our
+ * code wants the radius of the entire triple-box-blur kernel instead of
+ * the diameter of an individual box blur.  For more details, see:
+ *   http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
+ *   https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
+ */
+static const gfxFloat GAUSSIAN_SCALE_FACTOR = (3 * sqrt(2 * M_PI) / 4) * 1.5;
 
 gfxIntSize gfxAlphaBoxBlur::CalculateBlurRadius(const gfxPoint& aStd)
 {
     return gfxIntSize(
         static_cast<PRInt32>(floor(aStd.x * GAUSSIAN_SCALE_FACTOR + 0.5)),
         static_cast<PRInt32>(floor(aStd.y * GAUSSIAN_SCALE_FACTOR + 0.5)));
 }
--- a/gfx/thebes/gfxBlur.h
+++ b/gfx/thebes/gfxBlur.h
@@ -39,17 +39,22 @@
 #define GFX_BLUR_H
 
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "gfxTypes.h"
 #include "gfxThebesUtils.h"
 
 /**
- * Implementation of a box blur approximation of a Gaussian blur.
+ * Implementation of a triple box blur approximation of a Gaussian blur.
+ *
+ * A Gaussian blur is good for blurring because, when done independently
+ * in the horizontal and vertical directions, it matches the result that
+ * would be obtained using a different (rotated) set of axes.  A triple
+ * box blur is a very close approximation of a Gaussian.
  *
  * 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.
  * 
@@ -63,17 +68,21 @@ public:
     gfxAlphaBoxBlur();
 
     ~gfxAlphaBoxBlur();
 
     /**
      * Constructs a box blur and initializes the temporary surface.
      * @param aRect The coordinates of the surface to create in device units.
      *
-     * @param aBlurRadius The blur radius in pixels
+     * @param aBlurRadius The blur radius in pixels.  This is the radius of
+     *   the entire (triple) kernel function.  Each individual box blur has
+     *   radius approximately 1/3 this value, or diameter approximately 2/3
+     *   this value.  This parameter should nearly always be computed using
+     *   CalculateBlurRadius, below.
      *
      * @param aDirtyRect A pointer to a dirty rect, measured in device units, if available.
      *  This will be used for optimizing the blur operation. It is safe to pass NULL here.
      *
      * @param aSkipRect A pointer to a rect, measured in device units, that represents an area
      *  where blurring is unnecessary and shouldn't be done for speed reasons. It is safe to
      *  pass NULL here.
      */
@@ -105,17 +114,19 @@ public:
      *
      * @param aDestinationCtx The graphics context on which to apply the
      *  blurred mask.
      */
     void Paint(gfxContext* aDestinationCtx, const gfxPoint& offset = gfxPoint(0.0, 0.0));
 
     /**
      * Calculates a blur radius that, when used with box blur, approximates
-     * a Gaussian blur with the given standard deviation.
+     * a Gaussian blur with the given standard deviation.  The result of
+     * this function should be used as the aBlurRadius parameter to Init,
+     * above.
      */
     static gfxIntSize CalculateBlurRadius(const gfxPoint& aStandardDeviation);
 
 protected:
     /**
      * The spread radius, in pixels.
      */
     gfxIntSize mSpreadRadius;
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -1159,17 +1159,18 @@ nsCSSRendering::PaintBoxShadowOuter(nsPr
       shadowRect.Inflate(shadowItem->mSpread, shadowItem->mSpread);
       pixelSpreadRadius = 0;
     }
 
     // shadowRect won't include the blur, so make an extra rect here that includes the blur
     // for use in the even-odd rule below.
     nsRect shadowRectPlusBlur = shadowRect;
     nscoord blurRadius = shadowItem->mRadius;
-    shadowRectPlusBlur.Inflate(blurRadius, blurRadius);
+    shadowRectPlusBlur.Inflate(
+      nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel));
 
     gfxRect shadowGfxRect =
       nsLayoutUtils::RectToGfxRect(shadowRect, twipsPerPixel);
     gfxRect shadowGfxRectPlusBlur =
       nsLayoutUtils::RectToGfxRect(shadowRectPlusBlur, twipsPerPixel);
     shadowGfxRect.Round();
     shadowGfxRectPlusBlur.RoundOut();
 
@@ -1325,18 +1326,20 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
     /*
      * shadowRect: the frame's padding rect
      * shadowPaintRect: the area to paint on the temp surface, larger than shadowRect
      *                  so that blurs still happen properly near the edges
      * shadowClipRect: the area on the temporary surface within shadowPaintRect
      *                 that we will NOT paint in
      */
     nscoord blurRadius = shadowItem->mRadius;
+    nsMargin blurMargin =
+      nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, twipsPerPixel);
     nsRect shadowPaintRect = paddingRect;
-    shadowPaintRect.Inflate(blurRadius, blurRadius);
+    shadowPaintRect.Inflate(blurMargin);
 
     nsRect shadowClipRect = paddingRect;
     shadowClipRect.MoveBy(shadowItem->mXOffset, shadowItem->mYOffset);
     shadowClipRect.Deflate(shadowItem->mSpread, shadowItem->mSpread);
 
     gfxCornerSizes clipRectRadii;
     if (hasBorderRadius) {
       // Calculate the radii the inner clipping rect will have
@@ -1362,17 +1365,17 @@ nsCSSRendering::PaintBoxShadowInner(nsPr
 
       nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
                                              &clipRectRadii);
     }
 
     // Set the "skip rect" to the area within the frame that we don't paint in,
     // including after blurring. We also use this for clipping later on.
     nsRect skipRect = shadowClipRect;
-    skipRect.Deflate(blurRadius, blurRadius);
+    skipRect.Deflate(blurMargin);
     gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, twipsPerPixel);
     if (hasBorderRadius) {
       skipGfxRect.Inset(PR_MAX(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
                         PR_MAX(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0);
     }
 
     gfxContext* renderContext = aRenderingContext.ThebesContext();
     nsRefPtr<gfxContext> shadowContext;
@@ -3828,16 +3831,29 @@ ImageRenderer::Draw(nsPresContext*      
     default:
       break;
   }
 }
 
 #define MAX_BLUR_RADIUS 300
 #define MAX_SPREAD_RADIUS 50
 
+static inline gfxIntSize
+ComputeBlurRadius(nscoord aBlurRadius, PRInt32 aAppUnitsPerDevPixel)
+{
+  // http://dev.w3.org/csswg/css3-background/#box-shadow says that the
+  // standard deviation of the blur should be half the given blur value.
+  gfxFloat blurStdDev =
+    NS_MIN(gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel),
+           gfxFloat(MAX_BLUR_RADIUS))
+    / 2.0;
+  return
+    gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(blurStdDev, blurStdDev));
+}
+
 // -----
 // nsContextBoxBlur
 // -----
 gfxContext*
 nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
                        nscoord aBlurRadius,
                        PRInt32 aAppUnitsPerDevPixel,
                        gfxContext* aDestinationCtx,
@@ -3845,39 +3861,38 @@ nsContextBoxBlur::Init(const nsRect& aRe
                        const gfxRect* aSkipRect,
                        PRUint32 aFlags)
 {
   if (aRect.IsEmpty()) {
     mContext = nsnull;
     return nsnull;
   }
 
-  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);
+  gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
+  PRInt32 spreadRadius = NS_MIN(PRInt32(aSpreadRadius / aAppUnitsPerDevPixel),
+                                PRInt32(MAX_SPREAD_RADIUS));
   mDestinationCtx = aDestinationCtx;
 
   // If not blurring, draw directly onto the destination device
-  if (blurRadius <= 0 && spreadRadius <= 0 && !(aFlags & FORCE_MASK)) {
+  if (blurRadius.width <= 0 && blurRadius.height <= 0 && spreadRadius <= 0 &&
+      !(aFlags & FORCE_MASK)) {
     mContext = aDestinationCtx;
     return mContext;
   }
 
   // Convert from app units to device pixels
   gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
 
   gfxRect dirtyRect =
     nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
   dirtyRect.RoundOut();
 
   // Create the temporary surface for blurring
   mContext = blur.Init(rect, gfxIntSize(spreadRadius, spreadRadius),
-                       gfxIntSize(blurRadius, blurRadius),
-                       &dirtyRect, aSkipRect);
+                       blurRadius, &dirtyRect, aSkipRect);
   return mContext;
 }
 
 void
 nsContextBoxBlur::DoPaint()
 {
   if (mContext == mDestinationCtx)
     return;
@@ -3886,8 +3901,21 @@ nsContextBoxBlur::DoPaint()
 }
 
 gfxContext*
 nsContextBoxBlur::GetContext()
 {
   return mContext;
 }
 
+/* static */ nsMargin
+nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
+                                      PRInt32 aAppUnitsPerDevPixel)
+{
+  gfxIntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
+
+  nsMargin result;
+  result.top    = blurRadius.height * aAppUnitsPerDevPixel;
+  result.right  = blurRadius.width  * aAppUnitsPerDevPixel;
+  result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
+  result.left   = blurRadius.width  * aAppUnitsPerDevPixel;
+  return result;
+}
--- a/layout/base/nsCSSRendering.h
+++ b/layout/base/nsCSSRendering.h
@@ -459,16 +459,25 @@ public:
 
   /**
    * Gets the internal gfxContext at any time. Must not be freed. Avoid
    * calling this before calling Init() since the context would not be
    * constructed at that point.
    */
   gfxContext* GetContext();
 
+
+  /**
+   * Get the margin associated with the given blur radius, i.e., the
+   * additional area that might be painted as a result of it.  (The
+   * margin for a spread radius is itself, on all sides.)
+   */
+  static nsMargin GetBlurRadiusMargin(nscoord aBlurRadius,
+                                      PRInt32 aAppUnitsPerDevPixel);
+
 protected:
   gfxAlphaBoxBlur blur;
   nsRefPtr<gfxContext> mContext;
   gfxContext* mDestinationCtx;
   
 };
 
 #endif /* nsCSSRendering_h___ */
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1604,22 +1604,24 @@ nsRect
 nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
                                        nsIFrame* aFrame)
 {
   const nsStyleText* textStyle = aFrame->GetStyleText();
   if (!textStyle->mTextShadow)
     return aTextAndDecorationsRect;
 
   nsRect resultRect = aTextAndDecorationsRect;
+  PRInt32 A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
   for (PRUint32 i = 0; i < textStyle->mTextShadow->Length(); ++i) {
     nsRect tmpRect(aTextAndDecorationsRect);
     nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i);
 
     tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
-    tmpRect.Inflate(shadow->mRadius, shadow->mRadius);
+    tmpRect.Inflate(
+      nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D));
 
     resultRect.UnionRect(resultRect, tmpRect);
   }
   return resultRect;
 }
 
 nsresult
 nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame,
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4320,27 +4320,29 @@ ComputeOutlineAndEffectsRect(nsIFrame* a
                              PRBool aStoreRectProperties) {
   nsRect r = aOverflowRect;
   *aAnyOutlineOrEffects = PR_FALSE;
 
   // box-shadow
   nsCSSShadowArray* boxShadows = aFrame->GetStyleBorder()->mBoxShadow;
   if (boxShadows) {
     nsRect shadows;
+    PRInt32 A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
     for (PRUint32 i = 0; i < boxShadows->Length(); ++i) {
       nsRect tmpRect(nsPoint(0, 0), aNewSize);
       nsCSSShadowItem* shadow = boxShadows->ShadowAt(i);
 
       // inset shadows are never painted outside the frame
       if (shadow->mInset)
         continue;
-      nscoord outsetRadius = shadow->mRadius + shadow->mSpread;
 
       tmpRect.MoveBy(nsPoint(shadow->mXOffset, shadow->mYOffset));
-      tmpRect.Inflate(outsetRadius, outsetRadius);
+      tmpRect.Inflate(shadow->mSpread, shadow->mSpread);
+      tmpRect.Inflate(
+        nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D));
 
       shadows.UnionRect(shadows, tmpRect);
     }
     r.UnionRect(r, shadows);
     *aAnyOutlineOrEffects = PR_TRUE;
   }
 
   const nsStyleOutline* outline = aFrame->GetStyleOutline();
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-blur-2-notref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+body { margin: 0 }
+p {
+height: 1200px;
+width: 100px;
+-moz-box-shadow: 0px -100px 100px black;
+}
+</style>
+<p>
+<div style="position:absolute; background: white; top: 100px; left: 212px; height: 2px; width: 2px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-blur-2-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+body { margin: 0 }
+p {
+height: 1200px;
+width: 100px;
+-moz-box-shadow: 0px -100px 100px black;
+}
+</style>
+<p>
+<div style="position:absolute; background: white; top: 100px; left: 215px; height: 2px; width: 2px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/box-shadow/boxshadow-blur-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+body { margin: 0 }
+p {
+height: 1200px;
+width: 100px;
+-moz-box-shadow: 0px -100px 100px black;
+}
+</style>
+<p>
--- a/layout/reftests/box-shadow/reftest.list
+++ b/layout/reftests/box-shadow/reftest.list
@@ -1,11 +1,13 @@
 == boxshadow-basic.html boxshadow-basic-ref.html
 != boxshadow-blur.html boxshadow-blur-notref.html
 != boxshadow-blur.html boxshadow-blur-notref2.html
+== boxshadow-blur-2.html boxshadow-blur-2-ref.html
+!= boxshadow-blur-2.html boxshadow-blur-2-notref.html
 == boxshadow-multiple.html boxshadow-multiple-ref.html
 == boxshadow-spread.html boxshadow-spread-ref.html
 == tableboxshadow-basic.html tableboxshadow-basic-ref.html
 == tableboxshadow-trshadow.html tableboxshadow-trshadow-ref.html
 == tableboxshadow-tdshadow.html tableboxshadow-tdshadow-ref.html
 == boxshadow-rounding.html boxshadow-rounding-ref.html
 == boxshadow-button.html boxshadow-button-ref.html
 == boxshadow-fileupload.html boxshadow-fileupload-ref.html