Bug 467518. Interpret gfxBlur's border radius properly, as the actual boundary of the shadow, not the box-blur size. r=vlad
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 08 Dec 2008 13:59:21 +1300
changeset 22493 ce9f05b57b957d1731fe2dd86e149d1652dcc365
parent 22492 eb91ec5673df7bd4d7198a140a3fcb43d500d6e6
child 22494 04f25a6957cc0462d49931bd530815cf734285f3
push id4030
push userrocallahan@mozilla.com
push dateMon, 08 Dec 2008 07:52:53 +0000
treeherdermozilla-central@d64c207f76f2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvlad
bugs467518
milestone1.9.2a1pre
Bug 467518. Interpret gfxBlur's border radius properly, as the actual boundary of the shadow, not the box-blur size. r=vlad
gfx/thebes/src/gfxBlur.cpp
--- a/gfx/thebes/src/gfxBlur.cpp
+++ b/gfx/thebes/src/gfxBlur.cpp
@@ -181,54 +181,90 @@ BoxBlurVertical(unsigned char* aInput,
             aOutput[aStride * y + x] = alphaSum/boxSize;
 
             alphaSum += aInput[aStride * next + x] -
                         aInput[aStride * last + x];
         }
     }
 }
 
+static void ComputeLobes(PRInt32 aRadius, PRInt32 aLobes[3][2])
+{
+    PRInt32 major, minor, final;
+
+    /* See http://www.w3.org/TR/SVG/filters.html#feGaussianBlur for
+     * some notes about approximating the Gaussian blur with box-blurs.
+     * The comments below are in the terminology of that page.
+     */
+    PRInt32 z = aRadius/3;
+    switch (aRadius % 3) {
+    case 0:
+        // aRadius = z*3; choose d = 2*z + 1
+        major = minor = final = z;
+        break;
+    case 1:
+        // aRadius = z*3 + 1
+        // This is a tricky case since there is no value of d which will
+        // yield a radius of exactly aRadius. If d is odd, i.e. d=2*k + 1
+        // for some integer k, then the radius will be 3*k. If d is even,
+        // i.e. d=2*k, then the radius will be 3*k - 1.
+        // So we have to choose values that don't match the standard
+        // algorithm.
+        major = z + 1;
+        minor = final = z;
+        break;
+    case 2:
+        // aRadius = z*3 + 2; choose d = 2*z + 2
+        major = final = z + 1;
+        minor = z;
+        break;
+    }
+    NS_ASSERTION(major + minor + final == aRadius,
+                 "Lobes don't sum to the right length");
+
+    aLobes[0][0] = major;
+    aLobes[0][1] = minor;
+    aLobes[1][0] = minor;
+    aLobes[1][1] = major;
+    aLobes[2][0] = final;
+    aLobes[2][1] = final;
+}
+
 void
 gfxAlphaBoxBlur::Paint(gfxContext* aDestinationCtx, const gfxPoint& offset)
 {
     if (!mContext)
         return;
 
     unsigned char* boxData = mImageSurface->Data();
 
     // no need to do all this if not blurring
     if (mBlurRadius.width != 0 || mBlurRadius.height != 0) {
-        // A blur radius of 1 achieves nothing (1/2 = 0 in int terms),
-        // but we still want a blur!
-        // XXX this may not be appropriate... perhaps just use actuall Gaussian here?
-        mBlurRadius.width = PR_MAX(mBlurRadius.width, 2);
-        mBlurRadius.height = PR_MAX(mBlurRadius.height, 2);
-
         nsTArray<unsigned char> tempAlphaDataBuf;
         if (!tempAlphaDataBuf.SetLength(mImageSurface->GetDataSize()))
             return; // OOM
 
         unsigned char* tmpData = tempAlphaDataBuf.Elements();
         PRInt32 stride = mImageSurface->Stride();
         PRInt32 rows = mImageSurface->Height();
 
         if (mBlurRadius.width > 0) {
-            PRInt32 longLobe = mBlurRadius.width / 2;
-            PRInt32 shortLobe = (mBlurRadius.width & 1) ? longLobe : longLobe - 1;
-            BoxBlurHorizontal(boxData, tmpData, longLobe, shortLobe, stride, rows);
-            BoxBlurHorizontal(tmpData, boxData, shortLobe, longLobe, stride, rows);
-            BoxBlurHorizontal(boxData, tmpData, longLobe, longLobe, stride, rows);
+            PRInt32 lobes[3][2];
+            ComputeLobes(mBlurRadius.width, lobes);
+            BoxBlurHorizontal(boxData, tmpData, lobes[0][0], lobes[0][1], stride, rows);
+            BoxBlurHorizontal(tmpData, boxData, lobes[1][0], lobes[1][1], stride, rows);
+            BoxBlurHorizontal(boxData, tmpData, lobes[2][0], lobes[2][1], stride, rows);
         }
 
         if (mBlurRadius.height > 0) {
-            PRInt32 longLobe = mBlurRadius.height / 2;
-            PRInt32 shortLobe = (mBlurRadius.height & 1) ? longLobe : longLobe - 1;
-            BoxBlurVertical(tmpData, boxData, longLobe, shortLobe, stride, rows);
-            BoxBlurVertical(boxData, tmpData, shortLobe, longLobe, stride, rows);
-            BoxBlurVertical(tmpData, boxData, longLobe, longLobe, stride, rows);
+            PRInt32 lobes[3][2];
+            ComputeLobes(mBlurRadius.height, lobes);
+            BoxBlurVertical(tmpData, boxData, lobes[0][0], lobes[0][1], stride, rows);
+            BoxBlurVertical(boxData, tmpData, lobes[1][0], lobes[1][1], stride, rows);
+            BoxBlurVertical(tmpData, boxData, lobes[2][0], lobes[2][1], stride, rows);
         }
     }
 
     // Avoid a semi-expensive clip operation if we can, otherwise
     // clip to the dirty rect
     if (mHasDirtyRect) {
       aDestinationCtx->Save();
       aDestinationCtx->NewPath();
@@ -236,16 +272,17 @@ gfxAlphaBoxBlur::Paint(gfxContext* aDest
       aDestinationCtx->Clip();
       aDestinationCtx->Mask(mImageSurface, offset);
       aDestinationCtx->Restore();
     } else {
       aDestinationCtx->Mask(mImageSurface, offset);
     }
 }
 
-static const gfxFloat GAUSSIAN_SCALE_FACTOR = 3 * sqrt(2 * M_PI) / 4;
+// 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);
 
 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)));
 }