Allow negative values in SkBlurImageFilter sigma.

There are two ways negative sigma values may occur: in
the original filter parameters, or after multiplication
by a negative scaling CTM. The former case is
invalid according to the spec, so we continue to check
for it at validation time. In the latter case, we should
interpret it as a horizontal flip in the kernel pixel
access, and simply take the absolute value (since the
filter kernel is symmetric).

Also refactor all this logic into a single place for the
CPU, GPU and onFilterBounds() paths.

BUG=https://code.google.com/p/chromium/issues/detail?id=409602
R=sugoi@google.com, reed@google.com, sugoi@chromium.org

Author: senorblanco@chromium.org

Review URL: https://codereview.chromium.org/555603002
diff --git a/src/effects/SkBlurImageFilter.cpp b/src/effects/SkBlurImageFilter.cpp
index 8590400..4166d20 100644
--- a/src/effects/SkBlurImageFilter.cpp
+++ b/src/effects/SkBlurImageFilter.cpp
@@ -23,6 +23,14 @@
 // raster paths.
 #define MAX_SIGMA SkIntToScalar(532)
 
+static SkVector mapSigma(const SkSize& localSigma, const SkMatrix& ctm) {
+    SkVector sigma = SkVector::Make(localSigma.width(), localSigma.height());
+    ctm.mapVectors(&sigma, 1);
+    sigma.fX = SkMinScalar(SkScalarAbs(sigma.fX), MAX_SIGMA);
+    sigma.fY = SkMinScalar(SkScalarAbs(sigma.fY), MAX_SIGMA);
+    return sigma;
+}
+
 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
 SkBlurImageFilter::SkBlurImageFilter(SkReadBuffer& buffer)
   : INHERITED(1, buffer) {
@@ -177,10 +185,7 @@
     }
     dst->getBounds(&dstBounds);
 
-    SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
-    ctx.ctm().mapVectors(&sigma, 1);
-    sigma.fX = SkMinScalar(sigma.fX, MAX_SIGMA);
-    sigma.fY = SkMinScalar(sigma.fY, MAX_SIGMA);
+    SkVector sigma = mapSigma(fSigma, ctx.ctm());
 
     int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
     int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
@@ -253,8 +258,7 @@
 bool SkBlurImageFilter::onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
                                        SkIRect* dst) const {
     SkIRect bounds = src;
-    SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
-    ctm.mapVectors(&sigma, 1);
+    SkVector sigma = mapSigma(fSigma, ctm);
     bounds.outset(SkScalarCeilToInt(SkScalarMul(sigma.x(), SkIntToScalar(3))),
                   SkScalarCeilToInt(SkScalarMul(sigma.y(), SkIntToScalar(3))));
     if (getInput(0) && !getInput(0)->filterBounds(bounds, ctm, &bounds)) {
@@ -277,10 +281,7 @@
         return false;
     }
     GrTexture* source = input.getTexture();
-    SkVector sigma = SkVector::Make(fSigma.width(), fSigma.height());
-    ctx.ctm().mapVectors(&sigma, 1);
-    sigma.fX = SkMinScalar(sigma.fX, MAX_SIGMA);
-    sigma.fY = SkMinScalar(sigma.fY, MAX_SIGMA);
+    SkVector sigma = mapSigma(fSigma, ctx.ctm());
     offset->fX = rect.fLeft;
     offset->fY = rect.fTop;
     rect.offset(-srcOffset);
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index ebe9c4d..8b6e428 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -307,6 +307,63 @@
     return bitmap;
 }
 
+static void test_negative_blur_sigma(SkBaseDevice* device, skiatest::Reporter* reporter) {
+    // Check that SkBlurImageFilter will accept a negative sigma, either in
+    // the given arguments or after CTM application.
+    int width = 32, height = 32;
+    SkDeviceImageFilterProxy proxy(device);
+    SkScalar five = SkIntToScalar(5);
+
+    SkAutoTUnref<SkBlurImageFilter> positiveFilter(
+        SkBlurImageFilter::Create(five, five)
+    );
+
+    SkAutoTUnref<SkBlurImageFilter> negativeFilter(
+        SkBlurImageFilter::Create(-five, five)
+    );
+
+    SkBitmap gradient = make_gradient_circle(width, height);
+    SkBitmap positiveResult1, negativeResult1;
+    SkBitmap positiveResult2, negativeResult2;
+    SkIPoint offset;
+    SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeLargest(), NULL);
+    positiveFilter->filterImage(&proxy, gradient, ctx, &positiveResult1, &offset);
+    negativeFilter->filterImage(&proxy, gradient, ctx, &negativeResult1, &offset);
+    SkMatrix negativeScale;
+    negativeScale.setScale(-SK_Scalar1, SK_Scalar1);
+    SkImageFilter::Context negativeCTX(negativeScale, SkIRect::MakeLargest(), NULL);
+    positiveFilter->filterImage(&proxy, gradient, negativeCTX, &negativeResult2, &offset);
+    negativeFilter->filterImage(&proxy, gradient, negativeCTX, &positiveResult2, &offset);
+    SkAutoLockPixels lockP1(positiveResult1);
+    SkAutoLockPixels lockP2(positiveResult2);
+    SkAutoLockPixels lockN1(negativeResult1);
+    SkAutoLockPixels lockN2(negativeResult2);
+    for (int y = 0; y < height; y++) {
+        int diffs = memcmp(positiveResult1.getAddr32(0, y), negativeResult1.getAddr32(0, y), positiveResult1.rowBytes());
+        REPORTER_ASSERT(reporter, !diffs);
+        if (diffs) {
+            break;
+        }
+        diffs = memcmp(positiveResult1.getAddr32(0, y), negativeResult2.getAddr32(0, y), positiveResult1.rowBytes());
+        REPORTER_ASSERT(reporter, !diffs);
+        if (diffs) {
+            break;
+        }
+        diffs = memcmp(positiveResult1.getAddr32(0, y), positiveResult2.getAddr32(0, y), positiveResult1.rowBytes());
+        REPORTER_ASSERT(reporter, !diffs);
+        if (diffs) {
+            break;
+        }
+    }
+}
+
+DEF_TEST(TestNegativeBlurSigma, reporter) {
+    SkBitmap temp;
+    temp.allocN32Pixels(100, 100);
+    SkBitmapDevice device(temp);
+    test_negative_blur_sigma(&device, reporter);
+}
+
 DEF_TEST(ImageFilterDrawTiled, reporter) {
     // Check that all filters when drawn tiled (with subsequent clip rects) exactly
     // match the same filters drawn with a single full-canvas bitmap draw.
@@ -966,4 +1023,12 @@
                                                          0));
     test_xfermode_cropped_input(device, reporter);
 }
+
+DEF_GPUTEST(TestNegativeBlurSigmaGPU, reporter, factory) {
+    GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
+    SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
+                                                         SkImageInfo::MakeN32Premul(1, 1),
+                                                         0));
+    test_negative_blur_sigma(device, reporter);
+}
 #endif