Reduce size of filter mask.
http://codereview.appspot.com/4965057/

Reduce the size of filter masks, fix HQ blur when clipped, and add tests.


git-svn-id: http://skia.googlecode.com/svn/trunk@2211 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp
index 8d7f9b5..cb48255 100644
--- a/src/core/SkDraw.cpp
+++ b/src/core/SkDraw.cpp
@@ -2441,9 +2441,6 @@
         return false;
     }
 
-    SkIPoint   margin;
-    margin.set(0, 0);
-
     //  init our bounds from the path
     {
         SkRect pathBounds = devPath.getBounds();
@@ -2451,10 +2448,11 @@
         pathBounds.roundOut(bounds);
     }
 
+    SkIPoint margin;
     if (filter) {
         SkASSERT(filterMatrix);
 
-        SkMask  srcM, dstM;
+        SkMask srcM, dstM;
 
         srcM.fBounds = *bounds;
         srcM.fFormat = SkMask::kA8_Format;
@@ -2462,18 +2460,12 @@
         if (!filter->filterMask(&dstM, srcM, *filterMatrix, &margin)) {
             return false;
         }
-        *bounds = dstM.fBounds;
     }
 
-    if (clipBounds && !SkIRect::Intersects(*clipBounds, *bounds)) {
-        return false;
-    }
-
-    // (possibly) trim the srcM bounds to reflect the clip
+    // (possibly) trim the bounds to reflect the clip
     // (plus whatever slop the filter needs)
-    if (clipBounds && !clipBounds->contains(*bounds)) {
-        SkIRect tmp = *bounds;
-        (void)tmp.intersect(*clipBounds);
+    if (clipBounds) {
+        SkIRect tmp = *clipBounds;
         // Ugh. Guard against gigantic margins from wacky filters. Without this
         // check we can request arbitrary amounts of slop beyond our visible
         // clip, and bring down the renderer (at least on finite RAM machines
@@ -2483,7 +2475,9 @@
         static const int MAX_MARGIN = 128;
         tmp.inset(-SkMin32(margin.fX, MAX_MARGIN),
                   -SkMin32(margin.fY, MAX_MARGIN));
-        (void)bounds->intersect(tmp);
+        if (!bounds->intersect(tmp)) {
+            return false;
+        }
     }
 
     return true;
diff --git a/src/effects/SkBlurMask.cpp b/src/effects/SkBlurMask.cpp
index ac1e780..5dc9f44 100644
--- a/src/effects/SkBlurMask.cpp
+++ b/src/effects/SkBlurMask.cpp
@@ -222,7 +222,9 @@
 }
 
 bool SkBlurMask::Blur(SkMask* dst, const SkMask& src,
-                      SkScalar radius, Style style, Quality quality) {
+                      SkScalar radius, Style style, Quality quality,
+                      SkIPoint* margin)
+{
     if (src.fFormat != SkMask::kA8_Format) {
         return false;
     }
@@ -247,6 +249,9 @@
 
     int padx = passCount * rx;
     int pady = passCount * ry;
+    if (margin) {
+        margin->set(padx, pady);
+    }
     dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady,
         src.fBounds.fRight + padx, src.fBounds.fBottom + pady);
     dst->fRowBytes = dst->fBounds.width();
diff --git a/src/effects/SkBlurMask.h b/src/effects/SkBlurMask.h
index 75e7b75..3709dee 100644
--- a/src/effects/SkBlurMask.h
+++ b/src/effects/SkBlurMask.h
@@ -28,7 +28,9 @@
         kHigh_Quality   //!< three pass box blur (similar to gaussian)
     };
 
-    static bool Blur(SkMask* dst, const SkMask& src, SkScalar radius, Style, Quality quality);
+    static bool Blur(SkMask* dst, const SkMask& src,
+                     SkScalar radius, Style style, Quality quality,
+                     SkIPoint* margin = NULL);
 };
 
 #endif
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index cf0872e..9fdd127 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -93,16 +93,8 @@
         (fBlurFlags & SkBlurMaskFilter::kHighQuality_BlurFlag) ? 
             SkBlurMask::kHigh_Quality : SkBlurMask::kLow_Quality;
 
-    if (SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
-                         blurQuality)) {
-        if (margin) {
-            // we need to integralize radius for our margin, so take the ceil
-            // just to be safe.
-            margin->set(SkScalarCeil(radius), SkScalarCeil(radius));
-        }
-        return true;
-    }
-    return false;
+    return SkBlurMask::Blur(dst, src, radius, (SkBlurMask::Style)fBlurStyle,
+                            blurQuality, margin);
 }
 
 SkFlattenable* SkBlurMaskFilterImpl::CreateProc(SkFlattenableReadBuffer& buffer) {
diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp
index 76a9871..61fff63 100644
--- a/tests/BlurTest.cpp
+++ b/tests/BlurTest.cpp
@@ -7,34 +7,151 @@
  */
 #include "Test.h"
 #include "SkBlurMaskFilter.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
 #include "SkRandom.h"
 
 ///////////////////////////////////////////////////////////////////////////////
 
 #define ILLEGAL_MODE    ((SkXfermode::Mode)-1)
 
+static const int outset = 100;
+static const SkColor bgColor = SK_ColorWHITE;
+static const int strokeWidth = 4;
+
+static void create(SkBitmap* bm, SkIRect bound, SkBitmap::Config config) {
+    bm->setConfig(config, bound.width(), bound.height());
+    bm->allocPixels();
+}
+
+static void drawBG(SkCanvas* canvas) {
+    canvas->drawColor(bgColor);
+}
+
+
+struct BlurTest {
+    void (*addPath)(SkPath*);
+    int viewLen;
+    SkIRect views[9];
+};
+
+//Path Draw Procs
+//Beware that paths themselves my draw differently depending on the clip.
+static void draw50x50Rect(SkPath* path) {
+    path->addRect(0, 0, SkIntToScalar(50), SkIntToScalar(50));
+}
+
+//Tests
+static BlurTest tests[] = {
+    { draw50x50Rect, 3, {
+        //inner half of blur
+        { 0, 0, 50, 50 },
+        //blur, but no path.
+        { 50 + strokeWidth/2, 50 + strokeWidth/2, 100, 100 },
+        //just an edge
+        { 40, strokeWidth, 60, 50 - strokeWidth },
+    }},
+};
+
+/** Assumes that the ref draw was completely inside ref canvas --
+    implies that everything outside is "bgColor".
+    Checks that all overlap is the same and that all non-overlap on the
+    ref is "bgColor".
+ */
+static bool compare(const SkBitmap& ref, const SkIRect& iref,
+                    const SkBitmap& test, const SkIRect& itest)
+{
+    const int xOff = itest.fLeft - iref.fLeft;
+    const int yOff = itest.fTop - iref.fTop;
+
+    SkAutoLockPixels alpRef(ref);
+    SkAutoLockPixels alpTest(test);
+
+    for (int y = 0; y < test.height(); ++y) {
+        for (int x = 0; x < test.width(); ++x) {
+            SkColor testColor = test.getColor(x, y);
+            int refX = x + xOff;
+            int refY = y + yOff;
+            SkColor refColor;
+            if (refX >= 0 && refX < ref.width() &&
+                refY >= 0 && refY < ref.height())
+            {
+                refColor = ref.getColor(refX, refY);
+            } else {
+                refColor = bgColor;
+            }
+            if (refColor != testColor) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 static void test_blur(skiatest::Reporter* reporter) {
-    SkScalar radius = SkIntToScalar(2);
 
-    for (int i = 0; i < SkBlurMaskFilter::kBlurStyleCount; ++i) {
-        SkMaskFilter* filter;
-        SkMaskFilter::BlurInfo info;
+    SkPaint paint;
+    paint.setColor(SK_ColorGRAY);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(SkIntToScalar(strokeWidth));
 
-        uint32_t flags = i & 3;
+    SkScalar radius = SkIntToScalar(5);
+    for (int style = 0; style < SkBlurMaskFilter::kBlurStyleCount; ++style) {
+        SkBlurMaskFilter::BlurStyle blurStyle =
+            static_cast<SkBlurMaskFilter::BlurStyle>(style);
 
-        filter = SkBlurMaskFilter::Create(radius, (SkBlurMaskFilter::BlurStyle)i,
-                                          flags);
+        const uint32_t flagPermutations = SkBlurMaskFilter::kAll_BlurFlag;
+        for (uint32_t flags = 0; flags < flagPermutations; ++flags) {
+            SkMaskFilter* filter;
+            filter = SkBlurMaskFilter::Create(radius, blurStyle, flags);
 
-        sk_bzero(&info, sizeof(info));
-        SkMaskFilter::BlurType type = filter->asABlur(&info);
-        REPORTER_ASSERT(reporter, type == (SkMaskFilter::BlurType)(i + 1));
-        REPORTER_ASSERT(reporter, info.fRadius == radius);
-        REPORTER_ASSERT(reporter, info.fIgnoreTransform ==
-                        SkToBool(flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag));
-        REPORTER_ASSERT(reporter, info.fHighQuality ==
-                        SkToBool(flags & SkBlurMaskFilter::kHighQuality_BlurFlag));
+            SkMaskFilter::BlurInfo info;
+            sk_bzero(&info, sizeof(info));
+            SkMaskFilter::BlurType type = filter->asABlur(&info);
 
-        filter->unref();
+            REPORTER_ASSERT(reporter, type ==
+                static_cast<SkMaskFilter::BlurType>(style + 1));
+            REPORTER_ASSERT(reporter, info.fRadius == radius);
+            REPORTER_ASSERT(reporter, info.fIgnoreTransform ==
+                SkToBool(flags & SkBlurMaskFilter::kIgnoreTransform_BlurFlag));
+            REPORTER_ASSERT(reporter, info.fHighQuality ==
+                SkToBool(flags & SkBlurMaskFilter::kHighQuality_BlurFlag));
+
+            paint.setMaskFilter(filter);
+            filter->unref();
+
+            for (int test = 0; test < SK_ARRAY_COUNT(tests); ++test) {
+                SkPath path;
+                tests[test].addPath(&path);
+                SkRect refBound = path.getBounds();
+                refBound.outset(outset, outset);
+                SkIRect iref;
+                refBound.roundOut(&iref);
+                SkBitmap refBitmap;
+                create(&refBitmap, iref, SkBitmap::kARGB_8888_Config);
+
+                SkCanvas refCanvas(refBitmap);
+                refCanvas.translate(SkIntToScalar(-iref.fLeft),
+                                    SkIntToScalar(-iref.fTop));
+                drawBG(&refCanvas);
+                refCanvas.drawPath(path, paint);
+
+                for (int view = 0; view < tests[test].viewLen; ++view) {
+                    SkIRect itest = tests[test].views[view];
+                    SkBitmap testBitmap;
+                    create(&testBitmap, itest, SkBitmap::kARGB_8888_Config);
+
+                    SkCanvas testCanvas(testBitmap);
+                    testCanvas.translate(SkIntToScalar(-itest.fLeft),
+                                         SkIntToScalar(-itest.fTop));
+                    drawBG(&testCanvas);
+                    testCanvas.drawPath(path, paint);
+
+                    REPORTER_ASSERT(reporter,
+                        compare(refBitmap, iref, testBitmap, itest));
+                }
+            }
+        }
     }
 }
 
diff --git a/tests/skia_test.cpp b/tests/skia_test.cpp
index fb3310bf..0678748 100644
--- a/tests/skia_test.cpp
+++ b/tests/skia_test.cpp
@@ -104,11 +104,20 @@
 
 int main (int argc, char * const argv[]) {
     SkAutoGraphics ag;
-    
+
     bool androidMode = false;
-    for (int i = 1; i < argc; i++) {
-        if (!strcmp(argv[i], "-android")) {
+    const char* matchStr = NULL;
+
+    char* const* stop = argv + argc;
+    for (++argv; argv < stop; ++argv) {
+        if (strcmp(*argv, "-android") == 0) {
             androidMode = true;
+        
+        } else if (strcmp(*argv, "--match") == 0) {
+            ++argv;
+            if (argv < stop && **argv) {
+                matchStr = *argv;
+            }
         }
     }
 
@@ -118,17 +127,24 @@
 
     const int count = Iter::Count();
     int index = 0;
-    int successCount = 0;
+    int failCount = 0;
+    int skipCount = 0;
     while ((test = iter.next()) != NULL) {
         reporter.setIndexOfTotal(index, count);
-        successCount += test->run();
+        if (NULL != matchStr && !strstr(test->getName(), matchStr)) {
+            ++skipCount;
+        } else {
+            if (!test->run()) {
+                ++failCount;
+            }
+        }
         SkDELETE(test);
         index += 1;
     }
 
     if (!androidMode) {
-        SkDebugf("Finished %d tests, %d failures.\n", count,
-                 count - successCount);
+        SkDebugf("Finished %d tests, %d failures, %d skipped.\n",
+                 count, failCount, skipCount);
     }
-    return (count == successCount) ? 0 : 1;
+    return (failCount == 0) ? 0 : 1;
 }