Implement crop rect for the dilate and erode (morphology) filters. This provoked some cleanup on the GPU side: apply_morphology() now deals with SkBitmaps, rather than GrTextures. There's still a clear opportunity for more refactoring between the two filters.

Note: this adds some test cases to the morphology GM, so it will require a rebaseline.

R=bsalomon@google.com, reed@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk/src@11313 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/effects/SkMorphologyImageFilter.cpp b/effects/SkMorphologyImageFilter.cpp
index 83157be..949dc4a 100644
--- a/effects/SkMorphologyImageFilter.cpp
+++ b/effects/SkMorphologyImageFilter.cpp
@@ -26,8 +26,8 @@
     fRadius.fHeight = buffer.readInt();
 }
 
-SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input)
-    : INHERITED(input), fRadius(SkISize::Make(radiusX, radiusY)) {
+SkMorphologyImageFilter::SkMorphologyImageFilter(int radiusX, int radiusY, SkImageFilter* input, const SkIRect* cropRect)
+    : INHERITED(input, cropRect), fRadius(SkISize::Make(radiusX, radiusY)) {
 }
 
 
@@ -71,17 +71,17 @@
     }
 }
 
-static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+static void erodeX(const SkBitmap& src, SkBitmap* dst, int radiusX, const SkIRect& bounds)
 {
-    erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
-          radiusX, src.width(), src.height(),
+    erode(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
+          radiusX, bounds.width(), bounds.height(),
           1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
 }
 
-static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY, const SkIRect& bounds)
 {
-    erode(src.getAddr32(0, 0), dst->getAddr32(0, 0),
-          radiusY, src.height(), src.width(),
+    erode(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
+          radiusY, bounds.height(), bounds.width(),
           src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
 }
 
@@ -119,17 +119,17 @@
     }
 }
 
-static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX)
+static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX, const SkIRect& bounds)
 {
-    dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
-           radiusX, src.width(), src.height(),
+    dilate(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
+           radiusX, bounds.width(), bounds.height(),
            1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels());
 }
 
-static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY)
+static void dilateY(const SkBitmap& src, SkBitmap* dst, int radiusY, const SkIRect& bounds)
 {
-    dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0),
-           radiusY, src.height(), src.width(),
+    dilate(src.getAddr32(bounds.left(), bounds.top()), dst->getAddr32(0, 0),
+           radiusY, bounds.height(), bounds.width(),
            src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1);
 }
 
@@ -145,12 +145,18 @@
         return false;
     }
 
+    SkIRect bounds;
+    src.getBounds(&bounds);
+    if (!this->applyCropRect(&bounds, ctm)) {
+        return false;
+    }
+
     SkAutoLockPixels alp(src);
     if (!src.getPixels()) {
         return false;
     }
 
-    dst->setConfig(src.config(), src.width(), src.height());
+    dst->setConfig(src.config(), bounds.width(), bounds.height());
     dst->allocPixels();
 
     int width = radius().width();
@@ -161,7 +167,9 @@
     }
 
     if (width == 0 && height == 0) {
-        src.copyTo(dst, dst->config());
+        src.extractSubset(dst, bounds);
+        offset->fX += bounds.left();
+        offset->fY += bounds.top();
         return true;
     }
 
@@ -172,13 +180,16 @@
     }
 
     if (width > 0 && height > 0) {
-        erodeX(src, &temp, width);
-        erodeY(temp, dst, height);
+        erodeX(src, &temp, width, bounds);
+        SkIRect tmpBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
+        erodeY(temp, dst, height, tmpBounds);
     } else if (width > 0) {
-        erodeX(src, dst, width);
+        erodeX(src, dst, width, bounds);
     } else if (height > 0) {
-        erodeY(src, dst, height);
+        erodeY(src, dst, height, bounds);
     }
+    offset->fX += bounds.left();
+    offset->fY += bounds.top();
     return true;
 }
 
@@ -193,12 +204,18 @@
         return false;
     }
 
+    SkIRect bounds;
+    src.getBounds(&bounds);
+    if (!this->applyCropRect(&bounds, ctm)) {
+        return false;
+    }
+
     SkAutoLockPixels alp(src);
     if (!src.getPixels()) {
         return false;
     }
 
-    dst->setConfig(src.config(), src.width(), src.height());
+    dst->setConfig(src.config(), bounds.width(), bounds.height());
     dst->allocPixels();
 
     int width = radius().width();
@@ -209,7 +226,9 @@
     }
 
     if (width == 0 && height == 0) {
-        src.copyTo(dst, dst->config());
+        src.extractSubset(dst, bounds);
+        offset->fX += bounds.left();
+        offset->fY += bounds.top();
         return true;
     }
 
@@ -220,13 +239,16 @@
     }
 
     if (width > 0 && height > 0) {
-        dilateX(src, &temp, width);
-        dilateY(temp, dst, height);
+        dilateX(src, &temp, width, bounds);
+        SkIRect tmpBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
+        dilateY(temp, dst, height, tmpBounds);
     } else if (width > 0) {
-        dilateX(src, dst, width);
+        dilateX(src, dst, width, bounds);
     } else if (height > 0) {
-        dilateY(src, dst, height);
+        dilateY(src, dst, height, bounds);
     }
+    offset->fX += bounds.left();
+    offset->fY += bounds.top();
     return true;
 }
 
@@ -446,7 +468,8 @@
 
 void apply_morphology_pass(GrContext* context,
                            GrTexture* texture,
-                           const SkIRect& rect,
+                           const SkIRect& srcRect,
+                           const SkIRect& dstRect,
                            int radius,
                            GrMorphologyEffect::MorphologyType morphType,
                            Gr1DKernelEffect::Direction direction) {
@@ -455,15 +478,19 @@
                                                     direction,
                                                     radius,
                                                     morphType))->unref();
-    context->drawRect(paint, SkRect::MakeFromIRect(rect));
+    context->drawRectToRect(paint, SkRect::MakeFromIRect(dstRect), SkRect::MakeFromIRect(srcRect));
 }
 
-GrTexture* apply_morphology(GrTexture* srcTexture,
-                            const SkIRect& rect,
-                            GrMorphologyEffect::MorphologyType morphType,
-                            SkISize radius) {
+bool apply_morphology(const SkBitmap& input,
+                      const SkIRect& rect,
+                      GrMorphologyEffect::MorphologyType morphType,
+                      SkISize radius,
+                      SkBitmap* dst) {
+    GrTexture* srcTexture = input.getTexture();
+    SkASSERT(NULL != srcTexture);
     GrContext* context = srcTexture->getContext();
     srcTexture->ref();
+    SkAutoTUnref<GrTexture> src(srcTexture);
 
     GrContext::AutoMatrix am;
     am.setIdentity(context);
@@ -471,62 +498,101 @@
     GrContext::AutoClip acs(context, SkRect::MakeWH(SkIntToScalar(srcTexture->width()),
                                                     SkIntToScalar(srcTexture->height())));
 
+    SkIRect dstRect = SkIRect::MakeWH(rect.width(), rect.height());
     GrTextureDesc desc;
     desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
     desc.fWidth = rect.width();
     desc.fHeight = rect.height();
     desc.fConfig = kSkia8888_GrPixelConfig;
+    SkIRect srcRect = rect;
 
     if (radius.fWidth > 0) {
         GrAutoScratchTexture ast(context, desc);
         GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
-        apply_morphology_pass(context, srcTexture, rect, radius.fWidth,
+        apply_morphology_pass(context, src, srcRect, dstRect, radius.fWidth,
                               morphType, Gr1DKernelEffect::kX_Direction);
-        SkIRect clearRect = SkIRect::MakeXYWH(rect.fLeft, rect.fBottom,
-                                              rect.width(), radius.fHeight);
+        SkIRect clearRect = SkIRect::MakeXYWH(dstRect.fLeft, dstRect.fBottom,
+                                              dstRect.width(), radius.fHeight);
         context->clear(&clearRect, 0x0);
-        srcTexture->unref();
-        srcTexture = ast.detach();
+        src.reset(ast.detach());
+        srcRect = dstRect;
     }
     if (radius.fHeight > 0) {
         GrAutoScratchTexture ast(context, desc);
         GrContext::AutoRenderTarget art(context, ast.texture()->asRenderTarget());
-        apply_morphology_pass(context, srcTexture, rect, radius.fHeight,
+        apply_morphology_pass(context, src, srcRect, dstRect, radius.fHeight,
                               morphType, Gr1DKernelEffect::kY_Direction);
-        srcTexture->unref();
-        srcTexture = ast.detach();
+        src.reset(ast.detach());
     }
-    return srcTexture;
+    return SkImageFilterUtils::WrapTexture(src, rect.width(), rect.height(), dst);
 }
 
 };
 
 bool SkDilateImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
                                          SkBitmap* result, SkIPoint* offset) {
-    SkBitmap inputBM;
-    if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+    SkBitmap input;
+    if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &input, offset)) {
         return false;
     }
-    GrTexture* input = inputBM.getTexture();
     SkIRect bounds;
     src.getBounds(&bounds);
-    SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
-        GrMorphologyEffect::kDilate_MorphologyType, radius()));
-    return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+    if (!this->applyCropRect(&bounds, ctm)) {
+        return false;
+    }
+    int width = radius().width();
+    int height = radius().height();
+
+    if (width < 0 || height < 0) {
+        return false;
+    }
+
+    if (width == 0 && height == 0) {
+        src.extractSubset(result, bounds);
+        offset->fX += bounds.left();
+        offset->fY += bounds.top();
+        return true;
+    }
+
+    if (!apply_morphology(input, bounds, GrMorphologyEffect::kDilate_MorphologyType, radius(), result)) {
+        return false;
+    }
+    offset->fX += bounds.left();
+    offset->fY += bounds.top();
+    return true;
 }
 
 bool SkErodeImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, const SkMatrix& ctm,
                                         SkBitmap* result, SkIPoint* offset) {
-    SkBitmap inputBM;
-    if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &inputBM, offset)) {
+    SkBitmap input;
+    if (!SkImageFilterUtils::GetInputResultGPU(getInput(0), proxy, src, ctm, &input, offset)) {
         return false;
     }
-    GrTexture* input = inputBM.getTexture();
     SkIRect bounds;
     src.getBounds(&bounds);
-    SkAutoTUnref<GrTexture> resultTex(apply_morphology(input, bounds,
-        GrMorphologyEffect::kErode_MorphologyType, radius()));
-    return SkImageFilterUtils::WrapTexture(resultTex, src.width(), src.height(), result);
+    if (!this->applyCropRect(&bounds, ctm)) {
+        return false;
+    }
+    int width = radius().width();
+    int height = radius().height();
+
+    if (width < 0 || height < 0) {
+        return false;
+    }
+
+    if (width == 0 && height == 0) {
+        src.extractSubset(result, bounds);
+        offset->fX += bounds.left();
+        offset->fY += bounds.top();
+        return true;
+    }
+
+    if (!apply_morphology(input, bounds, GrMorphologyEffect::kErode_MorphologyType, radius(), result)) {
+        return false;
+    }
+    offset->fX += bounds.left();
+    offset->fY += bounds.top();
+    return true;
 }
 
 #endif