This patch implements a crop rect for SkImageFilter. It has been implemented for SkColorFilterImageFilter and SkBlurImageFilter as examples.

In order to preserve the immutability of SkImageFilters, the crop rect is passed as a constructor parameter. If NULL (the default), the bounds of the input image are used, as before.

This also tightens up the boundary handling for SkImageBlurFilter on the GPU backend. Where we were previously using clamping semantics, we now respect decal semantics (so we don't oversaturate the edges). This brings the GPU and raster backends into closer alignment, but will require some new baselines for the GPU tests.

At a minimum, the following tests will need new baselines: imageblur, imagefiltersbase, imagefilterscropped, spritebitmap.

R=reed@google.com

Review URL: https://codereview.chromium.org/19775006

git-svn-id: http://skia.googlecode.com/svn/trunk@10251 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index eaac5ab..4456f51 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1387,7 +1387,7 @@
     SkAutoCachedTexture act(this, bitmap, NULL, &texture);
 
     SkImageFilter* filter = paint.getImageFilter();
-    SkIPoint offset = SkIPoint::Make(0, 0);
+    SkIPoint offset = SkIPoint::Make(left, top);
     // This bitmap will own the filtered result as a texture.
     SkBitmap filteredBitmap;
 
@@ -1396,6 +1396,8 @@
             texture = (GrTexture*) filteredBitmap.getTexture();
             w = filteredBitmap.width();
             h = filteredBitmap.height();
+        } else {
+            return;
         }
     }
 
@@ -1407,12 +1409,12 @@
     }
 
     fContext->drawRectToRect(grPaint,
-                             SkRect::MakeXYWH(SkIntToScalar(left),
-                                              SkIntToScalar(top),
-                                              SkIntToScalar(w),
-                                              SkIntToScalar(h)),
                              SkRect::MakeXYWH(SkIntToScalar(offset.fX),
                                               SkIntToScalar(offset.fY),
+                                              SkIntToScalar(w),
+                                              SkIntToScalar(h)),
+                             SkRect::MakeXYWH(0,
+                                              0,
                                               SK_Scalar1 * w / texture->width(),
                                               SK_Scalar1 * h / texture->height()));
 }
@@ -1481,6 +1483,8 @@
             h = filteredBitmap.height();
             x += offset.fX;
             y += offset.fY;
+        } else {
+            return;
         }
     }
 
diff --git a/src/gpu/effects/GrConvolutionEffect.cpp b/src/gpu/effects/GrConvolutionEffect.cpp
index 380581f..5682b9c 100644
--- a/src/gpu/effects/GrConvolutionEffect.cpp
+++ b/src/gpu/effects/GrConvolutionEffect.cpp
@@ -37,6 +37,7 @@
     int                 fRadius;
     UniformHandle       fKernelUni;
     UniformHandle       fImageIncrementUni;
+    UniformHandle       fCropRectUni;
     GrGLEffectMatrix    fEffectMatrix;
 
     typedef GrGLEffect INHERITED;
@@ -47,6 +48,7 @@
     : INHERITED(factory)
     , fKernelUni(kInvalidUniformHandle)
     , fImageIncrementUni(kInvalidUniformHandle)
+    , fCropRectUni(kInvalidUniformHandle)
     , fEffectMatrix(drawEffect.castEffect<GrConvolutionEffect>().coordsType()) {
     const GrConvolutionEffect& c = drawEffect.castEffect<GrConvolutionEffect>();
     fRadius = c.radius();
@@ -62,6 +64,8 @@
     fEffectMatrix.emitCodeMakeFSCoords2D(builder, key, &coords);
     fImageIncrementUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
                                              kVec2f_GrSLType, "ImageIncrement");
+    fCropRectUni = builder->addUniform(GrGLShaderBuilder::kFragment_ShaderType,
+                                       kVec4f_GrSLType, "CropRect");
     fKernelUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_ShaderType,
                                           kFloat_GrSLType, "Kernel", this->width());
 
@@ -70,6 +74,7 @@
     int width = this ->width();
     const GrGLShaderVar& kernel = builder->getUniformVariable(fKernelUni);
     const char* imgInc = builder->getUniformCStr(fImageIncrementUni);
+    const char* cropRect = builder->getUniformCStr(fCropRectUni);
 
     builder->fsCodeAppendf("\t\tvec2 coord = %s - %d.0 * %s;\n", coords, fRadius, imgInc);
 
@@ -81,9 +86,11 @@
         kernel.appendArrayAccess(index.c_str(), &kernelIndex);
         builder->fsCodeAppendf("\t\t%s += ", outputColor);
         builder->appendTextureLookup(GrGLShaderBuilder::kFragment_ShaderType, samplers[0], "coord");
-        builder->fsCodeAppendf(" * %s;\n", kernelIndex.c_str());
+        builder->fsCodeAppendf(" * float(coord.x >= %s.x && coord.x <= %s.y && coord.y >= %s.z && coord.y <= %s.w) * %s;\n",
+            cropRect, cropRect, cropRect, cropRect, kernelIndex.c_str());
         builder->fsCodeAppendf("\t\tcoord += %s;\n", imgInc);
     }
+
     SkString modulate;
     GrGLSLMulVarBy4f(&modulate, 2, outputColor, inputColor);
     builder->fsCodeAppend(modulate.c_str());
@@ -96,17 +103,26 @@
     // the code we generated was for a specific kernel radius
     GrAssert(conv.radius() == fRadius);
     float imageIncrement[2] = { 0 };
+    float ySign = texture.origin() != kTopLeft_GrSurfaceOrigin ? 1.0f : -1.0f;
     switch (conv.direction()) {
         case Gr1DKernelEffect::kX_Direction:
             imageIncrement[0] = 1.0f / texture.width();
             break;
         case Gr1DKernelEffect::kY_Direction:
-            imageIncrement[1] = 1.0f / texture.height();
+            imageIncrement[1] = ySign / texture.height();
             break;
         default:
             GrCrash("Unknown filter direction.");
     }
     uman.set2fv(fImageIncrementUni, 0, 1, imageIncrement);
+    float c[4];
+    memcpy(c, conv.cropRect(), sizeof(c));
+    if (texture.origin() != kTopLeft_GrSurfaceOrigin) {
+        float tmp = 1.0f - c[2];
+        c[2] = 1.0f - c[3];
+        c[3] = tmp;
+    }
+    uman.set4fv(fCropRectUni, 0, 1, c);
     uman.set1fv(fKernelUni, 0, this->width(), conv.kernel());
     fEffectMatrix.setData(uman, conv.getMatrix(), drawEffect, conv.texture(0));
 }
@@ -128,7 +144,8 @@
 GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
                                          Direction direction,
                                          int radius,
-                                         const float* kernel)
+                                         const float* kernel,
+                                         float cropRect[4])
     : Gr1DKernelEffect(texture, direction, radius) {
     GrAssert(radius <= kMaxKernelRadius);
     GrAssert(NULL != kernel);
@@ -136,12 +153,14 @@
     for (int i = 0; i < width; i++) {
         fKernel[i] = kernel[i];
     }
+    memcpy(fCropRect, cropRect, sizeof(fCropRect));
 }
 
 GrConvolutionEffect::GrConvolutionEffect(GrTexture* texture,
                                          Direction direction,
                                          int radius,
-                                         float gaussianSigma)
+                                         float gaussianSigma,
+                                         float cropRect[4])
     : Gr1DKernelEffect(texture, direction, radius) {
     GrAssert(radius <= kMaxKernelRadius);
     int width = this->width();
@@ -160,6 +179,7 @@
     for (int i = 0; i < width; ++i) {
         fKernel[i] *= scale;
     }
+    memcpy(fCropRect, cropRect, sizeof(fCropRect));
 }
 
 GrConvolutionEffect::~GrConvolutionEffect() {
@@ -174,6 +194,7 @@
     return (this->texture(0) == s.texture(0) &&
             this->radius() == s.radius() &&
             this->direction() == s.direction() &&
+            0 == memcmp(fCropRect, s.fCropRect, sizeof(fCropRect)) &&
             0 == memcmp(fKernel, s.fKernel, this->width() * sizeof(float)));
 }
 
@@ -190,9 +211,13 @@
     Direction dir = random->nextBool() ? kX_Direction : kY_Direction;
     int radius = random->nextRangeU(1, kMaxKernelRadius);
     float kernel[kMaxKernelRadius];
+    float cropRect[4];
     for (int i = 0; i < kMaxKernelRadius; ++i) {
         kernel[i] = random->nextSScalar1();
     }
+    for (int i = 0; i < 4; ++i) {
+        cropRect[i] = random->nextF();
+    }
 
-    return GrConvolutionEffect::Create(textures[texIdx], dir, radius,kernel);
+    return GrConvolutionEffect::Create(textures[texIdx], dir, radius, kernel, cropRect);
 }
diff --git a/src/gpu/effects/GrConvolutionEffect.h b/src/gpu/effects/GrConvolutionEffect.h
index e4faa94..f75cfe9 100644
--- a/src/gpu/effects/GrConvolutionEffect.h
+++ b/src/gpu/effects/GrConvolutionEffect.h
@@ -22,11 +22,16 @@
 public:
 
     /// Convolve with an arbitrary user-specified kernel
-    static GrEffectRef* Create(GrTexture* tex, Direction dir, int halfWidth, const float* kernel) {
+    static GrEffectRef* Create(GrTexture* tex,
+                               Direction dir,
+                               int halfWidth,
+                               const float* kernel,
+                               float cropRect[4]) {
         AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
                                                                 dir,
                                                                 halfWidth,
-                                                                kernel)));
+                                                                kernel,
+                                                                cropRect)));
         return CreateEffectRef(effect);
     }
 
@@ -34,11 +39,13 @@
     static GrEffectRef* CreateGaussian(GrTexture* tex,
                                        Direction dir,
                                        int halfWidth,
-                                       float gaussianSigma) {
+                                       float gaussianSigma,
+                                       float cropRect[4]) {
         AutoEffectUnref effect(SkNEW_ARGS(GrConvolutionEffect, (tex,
                                                                 dir,
                                                                 halfWidth,
-                                                                gaussianSigma)));
+                                                                gaussianSigma,
+                                                                cropRect)));
         return CreateEffectRef(effect);
     }
 
@@ -46,6 +53,8 @@
 
     const float* kernel() const { return fKernel; }
 
+    const float* cropRect() const { return fCropRect; }
+
     static const char* Name() { return "Convolution"; }
 
     typedef GrGLConvolutionEffect GLEffect;
@@ -72,15 +81,17 @@
 protected:
 
     float fKernel[kMaxKernelWidth];
+    float fCropRect[4];
 
 private:
     GrConvolutionEffect(GrTexture*, Direction,
-                        int halfWidth, const float* kernel);
+                        int halfWidth, const float* kernel, float cropRect[4]);
 
     /// Convolve with a Gaussian kernel
     GrConvolutionEffect(GrTexture*, Direction,
                         int halfWidth,
-                        float gaussianSigma);
+                        float gaussianSigma,
+                        float cropRect[4]);
 
     virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;