Fix SkXfermodeImageFilter when an input is cropped out.

If one of inputs to SkXfermodeImageFilter draws nothing, either due to
it being cropped out upstream, or within the filter itself, the filter
should still draw the other input, since otherwise the result will be incorrect.

For the GPU path, since we can't detect this case in
canFilterImageGPU() without recursing, we'll just drop to
the generic path if either input is empty, since we can't use the effect in that case anyway.

While we're at it, let's drop to the generic path if the
xfermode can't be expressed as an effect, since the code
here was doing a 2-pass render in that case anyway, which
is equivalent to what the (xfermode == NULL) case was doing
anyway.

R=bsalomon@google.com, sugoi@chromium.org

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

git-svn-id: http://skia.googlecode.com/svn/trunk@14016 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/effects/SkXfermodeImageFilter.h b/include/effects/SkXfermodeImageFilter.h
index cacd9b0..a0cb728 100644
--- a/include/effects/SkXfermodeImageFilter.h
+++ b/include/effects/SkXfermodeImageFilter.h
@@ -37,7 +37,7 @@
                                SkBitmap* dst,
                                SkIPoint* offset) const SK_OVERRIDE;
 #if SK_SUPPORT_GPU
-    virtual bool canFilterImageGPU() const SK_OVERRIDE { return !cropRectIsSet(); }
+    virtual bool canFilterImageGPU() const SK_OVERRIDE;
     virtual bool filterImageGPU(Proxy* proxy, const SkBitmap& src, const Context& ctx,
                                 SkBitmap* result, SkIPoint* offset) const SK_OVERRIDE;
 #endif
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index 901353f..acb8fd3 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -53,22 +53,27 @@
     SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
     if (backgroundInput &&
         !backgroundInput->filterImage(proxy, src, ctx, &background, &backgroundOffset)) {
-        return false;
+        background.reset();
     }
     SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
     if (foregroundInput &&
         !foregroundInput->filterImage(proxy, src, ctx, &foreground, &foregroundOffset)) {
-        return false;
+        foreground.reset();
     }
 
     SkIRect bounds, foregroundBounds;
     if (!applyCropRect(ctx, foreground, foregroundOffset, &foregroundBounds)) {
-        return false;
+        foregroundBounds.setEmpty();
+        foreground.reset();
     }
     if (!applyCropRect(ctx, background, backgroundOffset, &bounds)) {
-        return false;
+        bounds.setEmpty();
+        background.reset();
     }
     bounds.join(foregroundBounds);
+    if (bounds.isEmpty()) {
+        return false;
+    }
 
     SkAutoTUnref<SkBaseDevice> device(proxy->createDevice(bounds.width(), bounds.height()));
     if (NULL == device.get()) {
@@ -94,6 +99,10 @@
 
 #if SK_SUPPORT_GPU
 
+bool SkXfermodeImageFilter::canFilterImageGPU() const {
+    return fMode && fMode->asNewEffect(NULL, NULL) && !cropRectIsSet();
+}
+
 bool SkXfermodeImageFilter::filterImageGPU(Proxy* proxy,
                                            const SkBitmap& src,
                                            const Context& ctx,
@@ -103,14 +112,14 @@
     SkIPoint backgroundOffset = SkIPoint::Make(0, 0);
     if (getInput(0) && !getInput(0)->getInputResultGPU(proxy, src, ctx, &background,
                                                        &backgroundOffset)) {
-        return false;
+        return onFilterImage(proxy, src, ctx, result, offset);
     }
     GrTexture* backgroundTex = background.getTexture();
     SkBitmap foreground = src;
     SkIPoint foregroundOffset = SkIPoint::Make(0, 0);
     if (getInput(1) && !getInput(1)->getInputResultGPU(proxy, src, ctx, &foreground,
                                                        &foregroundOffset)) {
-        return false;
+        return onFilterImage(proxy, src, ctx, result, offset);
     }
     GrTexture* foregroundTex = foreground.getTexture();
     GrContext* context = foregroundTex->getContext();
@@ -128,8 +137,9 @@
 
     GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
 
-    SkXfermode::Coeff sm, dm;
-    if (!SkXfermode::AsNewEffectOrCoeff(fMode, &xferEffect, &sm, &dm, backgroundTex)) {
+    if (!fMode || !fMode->asNewEffect(&xferEffect, backgroundTex)) {
+        // canFilterImageGPU() should've taken care of this
+        SkASSERT(false);
         return false;
     }
 
@@ -140,22 +150,12 @@
 
     SkRect srcRect;
     src.getBounds(&srcRect);
-    if (NULL != xferEffect) {
-        GrPaint paint;
-        paint.addColorTextureEffect(foregroundTex, foregroundMatrix);
-        paint.addColorEffect(xferEffect)->unref();
-        context->drawRect(paint, srcRect);
-    } else {
-        GrPaint backgroundPaint;
-        SkMatrix backgroundMatrix = GrEffect::MakeDivByTextureWHMatrix(backgroundTex);
-        backgroundPaint.addColorTextureEffect(backgroundTex, backgroundMatrix);
-        context->drawRect(backgroundPaint, srcRect);
 
-        GrPaint foregroundPaint;
-        foregroundPaint.setBlendFunc(sk_blend_to_grblend(sm), sk_blend_to_grblend(dm));
-        foregroundPaint.addColorTextureEffect(foregroundTex, foregroundMatrix);
-        context->drawRect(foregroundPaint, srcRect);
-    }
+    GrPaint paint;
+    paint.addColorTextureEffect(foregroundTex, foregroundMatrix);
+    paint.addColorEffect(xferEffect)->unref();
+    context->drawRect(paint, srcRect);
+
     offset->fX = backgroundOffset.fX;
     offset->fY = backgroundOffset.fY;
     WrapTexture(dst, src.width(), src.height(), result);
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index 7d465c1..cfba1fa 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -317,6 +317,58 @@
     test_huge_blur(&device, reporter);
 }
 
+static void test_xfermode_cropped_input(SkBaseDevice* device, skiatest::Reporter* reporter) {
+    SkCanvas canvas(device);
+    canvas.clear(0);
+
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(1, 1);
+    bitmap.eraseARGB(255, 255, 255, 255);
+
+    SkAutoTUnref<SkColorFilter> green(
+        SkColorFilter::CreateModeFilter(SK_ColorGREEN, SkXfermode::kSrcIn_Mode));
+    SkAutoTUnref<SkColorFilterImageFilter> greenFilter(
+        SkColorFilterImageFilter::Create(green.get()));
+    SkImageFilter::CropRect cropRect(SkRect::MakeEmpty());
+    SkAutoTUnref<SkColorFilterImageFilter> croppedOut(
+        SkColorFilterImageFilter::Create(green.get(), NULL, &cropRect));
+
+    // Check that an xfermode image filter whose input has been cropped out still draws the other
+    // input. Also check that drawing with both inputs cropped out doesn't cause a GPU warning.
+    SkXfermode* mode = SkXfermode::Create(SkXfermode::kSrcOver_Mode);
+    SkAutoTUnref<SkImageFilter> xfermodeNoFg(
+        SkXfermodeImageFilter::Create(mode, greenFilter, croppedOut));
+    SkAutoTUnref<SkImageFilter> xfermodeNoBg(
+        SkXfermodeImageFilter::Create(mode, croppedOut, greenFilter));
+    SkAutoTUnref<SkImageFilter> xfermodeNoFgNoBg(
+        SkXfermodeImageFilter::Create(mode, croppedOut, croppedOut));
+
+    SkPaint paint;
+    paint.setImageFilter(xfermodeNoFg);
+    canvas.drawSprite(bitmap, 0, 0, &paint);
+
+    uint32_t pixel;
+    SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+
+    paint.setImageFilter(xfermodeNoBg);
+    canvas.drawSprite(bitmap, 0, 0, &paint);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+
+    paint.setImageFilter(xfermodeNoFgNoBg);
+    canvas.drawSprite(bitmap, 0, 0, &paint);
+    canvas.readPixels(info, &pixel, 4, 0, 0);
+    REPORTER_ASSERT(reporter, pixel == SK_ColorGREEN);
+}
+
+DEF_TEST(XfermodeImageFilterCroppedInput, reporter) {
+    SkBitmap temp;
+    temp.allocN32Pixels(100, 100);
+    SkBitmapDevice device(temp);
+    test_xfermode_cropped_input(&device, reporter);
+}
 
 #if SK_SUPPORT_GPU
 DEF_GPUTEST(ImageFilterCropRectGPU, reporter, factory) {
@@ -334,4 +386,12 @@
                                                          0));
     test_huge_blur(device, reporter);
 }
+
+DEF_GPUTEST(XfermodeImageFilterCroppedInputGPU, reporter, factory) {
+    GrContext* context = factory->get(static_cast<GrContextFactory::GLContextType>(0));
+    SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
+                                                         SkImageInfo::MakeN32Premul(1, 1),
+                                                         0));
+    test_xfermode_cropped_input(device, reporter);
+}
 #endif