Swap over to using SkImageFilter::filterImage instead of filterImageDeprecated

This CL relies on https://codereview.chromium.org/1757983002/ (Add SkSpecialImage-based methods to SkImageFilter)

GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1762013002

TBR=bsalomon@google.com

Review URL: https://codereview.chromium.org/1762013002
diff --git a/include/core/SkImageFilter.h b/include/core/SkImageFilter.h
index 162d503..93f3e3c 100644
--- a/include/core/SkImageFilter.h
+++ b/include/core/SkImageFilter.h
@@ -137,20 +137,19 @@
 
     /**
      *  Request a new (result) image to be created from the src image.
-     *  If the src has no pixels (isNull()) then the request just wants to
-     *  receive the config and width/height of the result.
      *
-     *  The matrix is the current matrix on the canvas.
+     *  The context contains the environment in which the filter is occurring.
+     *  It includes the clip bounds, CTM and cache.
      *
      *  Offset is the amount to translate the resulting image relative to the
      *  src when it is drawn. This is an out-param.
      *
-     *  If the result image cannot be created, return false, in which case both
-     *  the result and offset parameters will be ignored by the caller.
+     *  If the result image cannot be created, return null, in which case
+     *  the offset parameters will be ignored by the caller.
+     *
+     *  TODO: Right now the imagefilters sometimes return empty result bitmaps/
+     *        specialimages. That doesn't seem quite right.
      */
-    bool filterImageDeprecated(Proxy*, const SkBitmap& src, const Context&,
-                               SkBitmap* result, SkIPoint* offset) const;
-
     SkSpecialImage* filterImage(SkSpecialImage* src, const Context&, SkIPoint* offset) const;
 
     enum MapDirection {
@@ -460,6 +459,9 @@
     friend class SkGraphics;
     static void PurgeCache();
 
+    bool filterImageDeprecated(Proxy*, const SkBitmap& src, const Context&,
+                               SkBitmap* result, SkIPoint* offset) const;
+
     bool usesSrcInput() const { return fUsesSrcInput; }
 
     typedef SkFlattenable INHERITED;
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index f3f3838..35710fb 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -28,6 +28,7 @@
 #include "SkReadPixelsRec.h"
 #include "SkRRect.h"
 #include "SkSmallAllocator.h"
+#include "SkSpecialImage.h"
 #include "SkSurface_Base.h"
 #include "SkTextBlob.h"
 #include "SkTextFormatParams.h"
@@ -1392,19 +1393,29 @@
         SkIPoint pos = { x - iter.getX(), y - iter.getY() };
         if (filter && !dstDev->canHandleImageFilter(filter)) {
             SkImageFilter::DeviceProxy proxy(dstDev);
-            SkBitmap dst;
             SkIPoint offset = SkIPoint::Make(0, 0);
-            const SkBitmap& src = srcDev->accessBitmap(false);
+            const SkBitmap& srcBM = srcDev->accessBitmap(false);
             SkMatrix matrix = *iter.fMatrix;
             matrix.postTranslate(SkIntToScalar(-pos.x()), SkIntToScalar(-pos.y()));
-            SkIRect clipBounds = iter.fClip->getBounds().makeOffset(-pos.x(), -pos.y());
+            const SkIRect clipBounds = iter.fClip->getBounds().makeOffset(-pos.x(), -pos.y());
             SkAutoTUnref<SkImageFilter::Cache> cache(dstDev->getImageFilterCache());
             SkImageFilter::Context ctx(matrix, clipBounds, cache.get());
-            if (filter->filterImageDeprecated(&proxy, src, ctx, &dst, &offset)) {
+
+            SkAutoTUnref<SkSpecialImage> srcImg(SkSpecialImage::internal_fromBM(&proxy, srcBM));
+            if (!srcImg) {
+                continue; // something disastrous happened
+            }
+
+            SkAutoTUnref<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
+            if (resultImg) {
                 SkPaint tmpUnfiltered(*paint);
                 tmpUnfiltered.setImageFilter(nullptr);
-                dstDev->drawSprite(iter, dst, pos.x() + offset.x(), pos.y() + offset.y(),
-                                   tmpUnfiltered);
+                SkBitmap resultBM;
+                if (resultImg->internal_getBM(&resultBM)) {
+                    // TODO: add drawSprite(SkSpecialImage) to SkDevice? (see skbug.com/5073)
+                    dstDev->drawSprite(iter, resultBM, pos.x() + offset.x(), pos.y() + offset.y(),
+                                       tmpUnfiltered);
+                }
             }
         } else if (deviceIsBitmapDevice) {
             const SkBitmap& src = static_cast<SkBitmapDevice*>(srcDev)->fBitmap;
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 7adb0f4..fa4a4b8 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -17,6 +17,7 @@
 #include "SkRasterClip.h"
 #include "SkRSXform.h"
 #include "SkShader.h"
+#include "SkSpecialImage.h"
 #include "SkTextBlobRunIterator.h"
 #include "SkTextToPathIter.h"
 
@@ -408,17 +409,27 @@
     SkImageFilter* filter = paint.getImageFilter();
     if (filter && !this->canHandleImageFilter(filter)) {
         SkImageFilter::DeviceProxy proxy(this);
-        SkBitmap dst;
         SkIPoint offset = SkIPoint::Make(0, 0);
         SkMatrix matrix = *draw.fMatrix;
         matrix.postTranslate(SkIntToScalar(-x), SkIntToScalar(-y));
         const SkIRect clipBounds = draw.fClip->getBounds().makeOffset(-x, -y);
         SkAutoTUnref<SkImageFilter::Cache> cache(this->getImageFilterCache());
         SkImageFilter::Context ctx(matrix, clipBounds, cache.get());
-        if (filter->filterImageDeprecated(&proxy, bitmap, ctx, &dst, &offset)) {
+
+        SkAutoTUnref<SkSpecialImage> srcImg(SkSpecialImage::internal_fromBM(&proxy, bitmap));
+        if (!srcImg) {
+            return; // something disastrous happened
+        }
+
+        SkAutoTUnref<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
+        if (resultImg) {
             SkPaint tmpUnfiltered(paint);
             tmpUnfiltered.setImageFilter(nullptr);
-            this->drawSprite(draw, dst, x + offset.x(), y + offset.y(), tmpUnfiltered);
+            SkBitmap resultBM;
+            if (resultImg->internal_getBM(&resultBM)) {
+                // TODO: add drawSprite(SkSpecialImage) to SkDevice? (see skbug.com/5073)
+                this->drawSprite(draw, resultBM, x + offset.x(), y + offset.y(), tmpUnfiltered);
+            }
         }
     } else {
         this->drawSprite(draw, bitmap, x, y, paint);
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 5f90528..a233871 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -272,7 +272,20 @@
     if (!input) {
         return true;
     }
-    return input->filterImageDeprecated(proxy, src, this->mapContext(ctx), result, offset);
+
+    SkAutoTUnref<SkSpecialImage> specialSrc(SkSpecialImage::internal_fromBM(proxy, src));
+    if (!specialSrc) {
+        return false;
+    }
+
+    SkAutoTUnref<SkSpecialImage> tmp(input->onFilterImage(specialSrc,
+                                                          this->mapContext(ctx),
+                                                          offset));
+    if (!tmp) {
+        return false;
+    }
+
+    return tmp->internal_getBM(result);
 }
 
 bool SkImageFilter::filterBounds(const SkIRect& src, const SkMatrix& ctm, SkIRect* dst,
@@ -327,9 +340,15 @@
 
 bool SkImageFilter::onFilterImageDeprecated(Proxy*, const SkBitmap&, const Context&,
                                             SkBitmap*, SkIPoint*) const {
+    // Only classes that now use the new SkSpecialImage-based path will not have
+    // onFilterImageDeprecated methods. For those classes we should never be
+    // calling this method.
+    SkASSERT(0);
     return false;
 }
 
+// SkImageFilter-derived classes that do not yet have their own onFilterImage
+// implementation convert back to calling the deprecated filterImage method
 SkSpecialImage* SkImageFilter::onFilterImage(SkSpecialImage* src, const Context& ctx,
                                              SkIPoint* offset) const {
     SkBitmap srcBM, resultBM;
@@ -338,6 +357,7 @@
         return nullptr;
     }
 
+    // This is the only valid call to the old filterImage path
     if (!this->filterImageDeprecated(src->internal_getProxy(), srcBM, ctx, &resultBM, offset)) {
         return nullptr;
     }
@@ -583,27 +603,39 @@
     if (!input) {
         return true;
     }
-    // Ensure that GrContext calls under filterImage and filterImageGPU below will see an identity
-    // matrix with no clip and that the matrix, clip, and render target set before this function was
-    // called are restored before we return to the caller.
-    GrContext* context = src.getTexture()->getContext();
-    if (input->filterImageDeprecated(proxy, src, this->mapContext(ctx), result, offset)) {
-        if (!result->getTexture()) {
-            const SkImageInfo info = result->info();
-            if (kUnknown_SkColorType == info.colorType()) {
-                return false;
-            }
-            SkAutoTUnref<GrTexture> resultTex(
-                GrRefCachedBitmapTexture(context, *result, GrTextureParams::ClampNoFilter()));
-            if (!resultTex) {
-                return false;
-            }
-            result->setPixelRef(new SkGrPixelRef(info, resultTex))->unref();
-        }
-        return true;
-    } else {
+
+    SkAutoTUnref<SkSpecialImage> specialSrc(SkSpecialImage::internal_fromBM(proxy, src));
+    if (!specialSrc) {
         return false;
     }
+
+    SkAutoTUnref<SkSpecialImage> tmp(input->onFilterImage(specialSrc,
+                                                          this->mapContext(ctx),
+                                                          offset));
+    if (!tmp) {
+        return false;
+    }
+
+    if (!tmp->internal_getBM(result)) {
+        return false;
+    }
+
+    if (!result->getTexture()) {
+        GrContext* context = src.getTexture()->getContext();
+
+        const SkImageInfo info = result->info();
+        if (kUnknown_SkColorType == info.colorType()) {
+            return false;
+        }
+        SkAutoTUnref<GrTexture> resultTex(
+            GrRefCachedBitmapTexture(context, *result, GrTextureParams::ClampNoFilter()));
+        if (!resultTex) {
+            return false;
+        }
+        result->setPixelRef(new SkGrPixelRef(info, resultTex))->unref();
+    }
+
+    return true;
 }
 #endif
 
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
index 88d90b8..0bbe50a 100644
--- a/src/core/SkSpecialImage.cpp
+++ b/src/core/SkSpecialImage.cpp
@@ -20,10 +20,12 @@
 
     virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) const = 0;
 
-    virtual bool onPeekPixels(SkPixmap*) const { return false; }
+    virtual bool testingOnlyOnPeekPixels(SkPixmap*) const { return false; }
 
     virtual GrTexture* onPeekTexture() const { return nullptr; }
 
+    virtual bool testingOnlyOnGetROPixels(SkBitmap*) const = 0;
+
     // Delete this entry point ASAP (see skbug.com/4965)
     virtual bool getBitmap(SkBitmap* result) const = 0;
 
@@ -42,14 +44,18 @@
     return as_SIB(this)->onDraw(canvas, x, y, paint);
 }
 
-bool SkSpecialImage::peekPixels(SkPixmap* pixmap) const {
-    return as_SIB(this)->onPeekPixels(pixmap);
+bool SkSpecialImage::testingOnlyPeekPixels(SkPixmap* pixmap) const {
+    return as_SIB(this)->testingOnlyOnPeekPixels(pixmap);
 }
 
 GrTexture* SkSpecialImage::peekTexture() const {
     return as_SIB(this)->onPeekTexture();
 }
 
+bool SkSpecialImage::testingOnlyGetROPixels(SkBitmap* result) const {
+    return as_SIB(this)->testingOnlyOnGetROPixels(result);
+}
+
 SkSpecialSurface* SkSpecialImage::newSurface(const SkImageInfo& info) const {
     return as_SIB(this)->onNewSurface(info);
 }
@@ -125,7 +131,7 @@
                               dst, paint, SkCanvas::kStrict_SrcRectConstraint);
     }
 
-    bool onPeekPixels(SkPixmap* pixmap) const override {
+    bool testingOnlyOnPeekPixels(SkPixmap* pixmap) const override {
         return fImage->peekPixels(pixmap);
     }
 
@@ -135,6 +141,10 @@
         return false;
     }
 
+    bool testingOnlyOnGetROPixels(SkBitmap* result) const override {
+        return false;
+    }
+
     SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
 #if SK_SUPPORT_GPU
         GrTexture* texture = as_IB(fImage.get())->peekTexture();
@@ -156,6 +166,11 @@
 
 #ifdef SK_DEBUG
 static bool rect_fits(const SkIRect& rect, int width, int height) {
+    if (0 == width && 0 == height) {
+        SkASSERT(0 == rect.fLeft && 0 == rect.fRight && 0 == rect.fTop && 0 == rect.fBottom);
+        return true;
+    }
+
     return rect.fLeft >= 0 && rect.fLeft < width && rect.fLeft < rect.fRight &&
            rect.fRight >= 0 && rect.fRight <= width &&
            rect.fTop >= 0 && rect.fTop < height && rect.fTop < rect.fBottom &&
@@ -178,7 +193,7 @@
     SkSpecialImage_Raster(SkImageFilter::Proxy* proxy, const SkIRect& subset, const SkBitmap& bm)
         : INHERITED(proxy, subset, bm.getGenerationID())
         , fBitmap(bm) {
-        if (bm.pixelRef()->isPreLocked()) {
+        if (bm.pixelRef() && bm.pixelRef()->isPreLocked()) {
             // we only preemptively lock if there is no chance of triggering something expensive
             // like a lazy decode or imagegenerator. PreLocked means it is flat pixels already.
             fBitmap.lockPixels();
@@ -199,7 +214,7 @@
                                dst, paint, SkCanvas::kStrict_SrcRectConstraint);
     }
 
-    bool onPeekPixels(SkPixmap* pixmap) const override {
+    bool testingOnlyOnPeekPixels(SkPixmap* pixmap) const override {
         const SkImageInfo info = fBitmap.info();
         if ((kUnknown_SkColorType == info.colorType()) || !fBitmap.getPixels()) {
             return false;
@@ -219,6 +234,11 @@
         return true;
     }
 
+    bool testingOnlyOnGetROPixels(SkBitmap* result) const override {
+        *result = fBitmap;
+        return true;
+    }
+
     SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
         return SkSpecialSurface::NewRaster(this->proxy(), info, nullptr);
     }
@@ -285,6 +305,25 @@
         return true;
     }
 
+    bool testingOnlyOnGetROPixels(SkBitmap* result) const override {
+
+        const SkImageInfo info = SkImageInfo::MakeN32(this->width(), 
+                                                      this->height(),
+                                                      this->isOpaque() ? kOpaque_SkAlphaType
+                                                                       : kPremul_SkAlphaType);
+        if (!result->tryAllocPixels(info)) {
+            return false;
+        }
+
+        if (!fTexture->readPixels(0, 0, result->width(), result->height(), kSkia8888_GrPixelConfig,
+                                  result->getPixels(), result->rowBytes())) {
+            return false;
+        }
+
+        result->pixelRef()->setImmutable();
+        return true;
+    }
+
     SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
         GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(info);
         desc.fFlags = kRenderTarget_GrSurfaceFlag;
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
index 79fceeb..e80f82b 100644
--- a/src/core/SkSpecialImage.h
+++ b/src/core/SkSpecialImage.h
@@ -72,6 +72,13 @@
     static SkSpecialImage* internal_fromBM(SkImageFilter::Proxy*, const SkBitmap&);
     SkImageFilter::Proxy* internal_getProxy();
 
+    // TODO: hide this when GrLayerHoister uses SkSpecialImages more fully (see skbug.com/5063)
+    /**
+     *  If the SpecialImage is backed by a gpu texture, return that texture.
+     *  The active portion of the texture can be retrieved via 'subset'.
+     */
+    GrTexture* peekTexture() const;
+
 protected:
     SkSpecialImage(SkImageFilter::Proxy* proxy, const SkIRect& subset, uint32_t uniqueID)
         : fSubset(subset)
@@ -94,13 +101,11 @@
      *
      *  On failure, return false and ignore the pixmap parameter.
      */
-    bool peekPixels(SkPixmap*) const;
+    bool testingOnlyPeekPixels(SkPixmap*) const;
 
-    /**
-     *  If the SpecialImage is backed by a gpu texture, return that texture.
-     *  The active portion of the texture can be retrieved via 'subset'.
-     */
-    GrTexture* peekTexture() const;
+    // This entry point is for testing only. It does a readback from VRAM for
+    // GPU-backed special images.
+    bool testingOnlyGetROPixels(SkBitmap*) const;
 
     // TODO: remove this ASAP (see skbug.com/4965)
     SkImageFilter::Proxy* proxy() const { return fProxy; }
diff --git a/src/gpu/GrLayerHoister.cpp b/src/gpu/GrLayerHoister.cpp
index c39aa20..a446be8 100644
--- a/src/gpu/GrLayerHoister.cpp
+++ b/src/gpu/GrLayerHoister.cpp
@@ -14,6 +14,7 @@
 #include "SkGpuDevice.h"
 #include "SkLayerInfo.h"
 #include "SkRecordDraw.h"
+#include "SkSpecialImage.h"
 #include "SkSurface.h"
 #include "SkSurface_Gpu.h"
 
@@ -285,18 +286,14 @@
 
     static const int kDefaultCacheSize = 32 * 1024 * 1024;
 
-    SkBitmap filteredBitmap;
-    SkIPoint offset = SkIPoint::Make(0, 0);
-
     const SkIPoint filterOffset = SkIPoint::Make(layer->srcIR().fLeft, layer->srcIR().fTop);
 
-    SkMatrix totMat = SkMatrix::I();
-    totMat.preConcat(info.fPreMat);
+    SkMatrix totMat(info.fPreMat);
     totMat.preConcat(info.fLocalMat);
     totMat.postTranslate(-SkIntToScalar(filterOffset.fX), -SkIntToScalar(filterOffset.fY));
 
     SkASSERT(0 == layer->rect().fLeft && 0 == layer->rect().fTop);
-    SkIRect clipBounds = layer->rect();
+    const SkIRect& clipBounds = layer->rect();
 
     // This cache is transient, and is freed (along with all its contained
     // textures) when it goes out of scope.
@@ -304,18 +301,24 @@
     SkImageFilter::Context filterContext(totMat, clipBounds, cache);
 
     SkImageFilter::DeviceProxy proxy(device);
-    SkBitmap src;
-    GrWrapTextureInBitmap(layer->texture(), layer->texture()->width(), layer->texture()->height(),
-                          false, &src);
 
-    if (!layer->filter()->filterImageDeprecated(&proxy, src, filterContext,
-                                                &filteredBitmap, &offset)) {
+    // TODO: should the layer hoister store stand alone layers as SkSpecialImages internally?
+    const SkIRect subset = SkIRect::MakeWH(layer->texture()->width(), layer->texture()->height());
+    SkAutoTUnref<SkSpecialImage> img(SkSpecialImage::NewFromGpu(&proxy, subset,
+                                                                kNeedNewImageUniqueID_SpecialImage,
+                                                                layer->texture()));
+
+    SkIPoint offset = SkIPoint::Make(0, 0);
+    SkAutoTUnref<SkSpecialImage> result(layer->filter()->filterImage(img,
+                                                                     filterContext,
+                                                                     &offset));
+    if (!result) {
         // Filtering failed. Press on with the unfiltered version.
         return;
     }
 
-    SkIRect newRect = SkIRect::MakeWH(filteredBitmap.width(), filteredBitmap.height());
-    layer->setTexture(filteredBitmap.getTexture(), newRect, false);
+    SkASSERT(result->peekTexture());
+    layer->setTexture(result->peekTexture(), result->subset(), false);
     layer->setOffset(offset);
 }
 
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index 89c069c..3def8f5 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -32,11 +32,14 @@
 #include "SkPoint3.h"
 #include "SkReadBuffer.h"
 #include "SkRect.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
 #include "SkSurface.h"
 #include "SkTableColorFilter.h"
 #include "SkTileImageFilter.h"
 #include "SkXfermodeImageFilter.h"
 #include "Test.h"
+#include "TestingSpecialImageAccess.h"
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
@@ -155,6 +158,35 @@
     return SkColorFilterImageFilter::Create(filter, input, cropRect);
 }
 
+static SkSpecialImage* create_empty_special_image(GrContext* context,
+                                                  SkImageFilter::Proxy* proxy,
+                                                  int widthHeight) {
+    SkAutoTUnref<SkSpecialSurface> surf;
+
+    if (context) {
+        GrSurfaceDesc desc;
+        desc.fConfig = kSkia8888_GrPixelConfig;
+        desc.fFlags  = kRenderTarget_GrSurfaceFlag;
+        desc.fWidth  = widthHeight;
+        desc.fHeight = widthHeight;
+        surf.reset(SkSpecialSurface::NewRenderTarget(proxy, context, desc));
+    } else {
+        const SkImageInfo info = SkImageInfo::MakeN32(widthHeight, widthHeight,
+                                                      kOpaque_SkAlphaType);
+        surf.reset(SkSpecialSurface::NewRaster(proxy, info));
+    }
+
+    SkASSERT(surf);
+
+    SkCanvas* canvas = surf->getCanvas();
+    SkASSERT(canvas);
+
+    canvas->clear(0x0);
+
+    return surf->newImageSnapshot();
+}
+
+
 DEF_TEST(ImageFilter, reporter) {
     {
         // Check that two non-clipping color-matrice-filters concatenate into a single filter.
@@ -271,13 +303,14 @@
     }
 }
 
-static void test_crop_rects(SkImageFilter::Proxy* proxy, skiatest::Reporter* reporter) {
+static void test_crop_rects(SkImageFilter::Proxy* proxy,
+                            skiatest::Reporter* reporter,
+                            GrContext* context) {
     // Check that all filters offset to their absolute crop rect,
     // unaffected by the input crop rect.
     // Tests pass by not asserting.
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    bitmap.eraseARGB(0, 0, 0, 0);
+    SkAutoTUnref<SkSpecialImage> srcImg(create_empty_special_image(context, proxy, 100));
+    SkASSERT(srcImg);
 
     SkImageFilter::CropRect inputCropRect(SkRect::MakeXYWH(8, 13, 80, 80));
     SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(20, 30, 60, 60));
@@ -316,15 +349,12 @@
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(filters); ++i) {
         SkImageFilter* filter = filters[i];
-        SkBitmap result;
         SkIPoint offset;
         SkString str;
         str.printf("filter %d", static_cast<int>(i));
         SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr);
-        REPORTER_ASSERT_MESSAGE(reporter,
-                                filter->filterImageDeprecated(proxy, bitmap, ctx, 
-                                                              &result, &offset),
-                                str.c_str());
+        SkAutoTUnref<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
+        REPORTER_ASSERT_MESSAGE(reporter, resultImg, str.c_str());
         REPORTER_ASSERT_MESSAGE(reporter, offset.fX == 20 && offset.fY == 30, str.c_str());
     }
 
@@ -354,7 +384,11 @@
     return bitmap;
 }
 
-static void test_negative_blur_sigma(SkImageFilter::Proxy* proxy, skiatest::Reporter* reporter) {
+
+
+static void test_negative_blur_sigma(SkImageFilter::Proxy* proxy,
+                                     skiatest::Reporter* reporter,
+                                     GrContext* context) {
     // Check that SkBlurImageFilter will accept a negative sigma, either in
     // the given arguments or after CTM application.
     const int width = 32, height = 32;
@@ -364,41 +398,65 @@
     SkAutoTUnref<SkImageFilter> negativeFilter(SkBlurImageFilter::Create(-five, five));
 
     SkBitmap gradient = make_gradient_circle(width, height);
-    SkBitmap positiveResult1, negativeResult1;
-    SkBitmap positiveResult2, negativeResult2;
+    SkAutoTUnref<SkSpecialImage> imgSrc(SkSpecialImage::NewFromRaster(proxy,
+                                                                      SkIRect::MakeWH(width,
+                                                                                      height),
+                                                                      gradient));
+
     SkIPoint offset;
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(32, 32), nullptr);
-    REPORTER_ASSERT(reporter,
-                    positiveFilter->filterImageDeprecated(proxy, gradient, ctx,
-                                                          &positiveResult1, &offset));
-    REPORTER_ASSERT(reporter,
-                    negativeFilter->filterImageDeprecated(proxy, gradient, ctx,
-                                                          &negativeResult1, &offset));
+
+    SkAutoTUnref<SkSpecialImage> positiveResult1(positiveFilter->filterImage(imgSrc, ctx, &offset));
+    REPORTER_ASSERT(reporter, positiveResult1);
+
+    SkAutoTUnref<SkSpecialImage> negativeResult1(negativeFilter->filterImage(imgSrc, ctx, &offset));
+    REPORTER_ASSERT(reporter, negativeResult1);
+
     SkMatrix negativeScale;
     negativeScale.setScale(-SK_Scalar1, SK_Scalar1);
     SkImageFilter::Context negativeCTX(negativeScale, SkIRect::MakeWH(32, 32), nullptr);
-    REPORTER_ASSERT(reporter,
-                    positiveFilter->filterImageDeprecated(proxy, gradient, negativeCTX,
-                                                          &negativeResult2, &offset));
-    REPORTER_ASSERT(reporter,
-                    negativeFilter->filterImageDeprecated(proxy, gradient, negativeCTX,
-                                                          &positiveResult2, &offset));
-    SkAutoLockPixels lockP1(positiveResult1);
-    SkAutoLockPixels lockP2(positiveResult2);
-    SkAutoLockPixels lockN1(negativeResult1);
-    SkAutoLockPixels lockN2(negativeResult2);
+
+    SkAutoTUnref<SkSpecialImage> negativeResult2(positiveFilter->filterImage(imgSrc,
+                                                                             negativeCTX,
+                                                                             &offset));
+    REPORTER_ASSERT(reporter, negativeResult2);
+
+    SkAutoTUnref<SkSpecialImage> positiveResult2(negativeFilter->filterImage(imgSrc,
+                                                                             negativeCTX,
+                                                                             &offset));
+    REPORTER_ASSERT(reporter, positiveResult2);
+
+
+    SkBitmap positiveResultBM1, positiveResultBM2;
+    SkBitmap negativeResultBM1, negativeResultBM2;
+
+    TestingSpecialImageAccess::GetROPixels(positiveResult1, &positiveResultBM1);
+    TestingSpecialImageAccess::GetROPixels(positiveResult2, &positiveResultBM2);
+    TestingSpecialImageAccess::GetROPixels(negativeResult1, &negativeResultBM1);
+    TestingSpecialImageAccess::GetROPixels(negativeResult2, &negativeResultBM2);
+
+    SkAutoLockPixels lockP1(positiveResultBM1);
+    SkAutoLockPixels lockP2(positiveResultBM2);
+    SkAutoLockPixels lockN1(negativeResultBM1);
+    SkAutoLockPixels lockN2(negativeResultBM2);
     for (int y = 0; y < height; y++) {
-        int diffs = memcmp(positiveResult1.getAddr32(0, y), negativeResult1.getAddr32(0, y), positiveResult1.rowBytes());
+        int diffs = memcmp(positiveResultBM1.getAddr32(0, y),
+                           negativeResultBM1.getAddr32(0, y),
+                           positiveResultBM1.rowBytes());
         REPORTER_ASSERT(reporter, !diffs);
         if (diffs) {
             break;
         }
-        diffs = memcmp(positiveResult1.getAddr32(0, y), negativeResult2.getAddr32(0, y), positiveResult1.rowBytes());
+        diffs = memcmp(positiveResultBM1.getAddr32(0, y),
+                       negativeResultBM2.getAddr32(0, y),
+                       positiveResultBM1.rowBytes());
         REPORTER_ASSERT(reporter, !diffs);
         if (diffs) {
             break;
         }
-        diffs = memcmp(positiveResult1.getAddr32(0, y), positiveResult2.getAddr32(0, y), positiveResult1.rowBytes());
+        diffs = memcmp(positiveResultBM1.getAddr32(0, y),
+                       positiveResultBM2.getAddr32(0, y),
+                       positiveResultBM1.rowBytes());
         REPORTER_ASSERT(reporter, !diffs);
         if (diffs) {
             break;
@@ -406,16 +464,53 @@
     }
 }
 
-DEF_TEST(TestNegativeBlurSigma, reporter) {
-    const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
+typedef void (*PFTest)(SkImageFilter::Proxy* proxy,
+                       skiatest::Reporter* reporter,
+                       GrContext* context);
+
+static void run_raster_test(skiatest::Reporter* reporter,
+                            int widthHeight,
+                            PFTest test) {
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
 
+    const SkImageInfo info = SkImageInfo::MakeN32Premul(widthHeight, widthHeight);
+
     SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(info, props));
     SkImageFilter::DeviceProxy proxy(device);
 
-    test_negative_blur_sigma(&proxy, reporter);
+    (*test)(&proxy, reporter, nullptr);
 }
 
+#if SK_SUPPORT_GPU
+static void run_gpu_test(skiatest::Reporter* reporter,
+                         GrContext* context,
+                         int widthHeight,
+                         PFTest test) {
+    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
+
+    SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
+                                                         SkBudgeted::kNo,
+                                                         SkImageInfo::MakeN32Premul(widthHeight, 
+                                                                                    widthHeight),
+                                                         0,
+                                                         &props,
+                                                         SkGpuDevice::kUninit_InitContents));
+    SkImageFilter::DeviceProxy proxy(device);
+
+    (*test)(&proxy, reporter, context);
+}
+#endif
+
+DEF_TEST(TestNegativeBlurSigma, reporter) {
+    run_raster_test(reporter, 100, test_negative_blur_sigma);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(TestNegativeBlurSigma_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 100, test_negative_blur_sigma);
+}
+#endif
+
 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.
@@ -654,7 +749,9 @@
     REPORTER_ASSERT(reporter, boundsDst == expectedBounds);
 }
 
-DEF_TEST(ImageFilterMergeResultSize, reporter) {
+static void test_imagefilter_merge_result_size(SkImageFilter::Proxy* proxy,
+                                               skiatest::Reporter* reporter,
+                                               GrContext* context) {
     SkBitmap greenBM;
     greenBM.allocN32Pixels(20, 20);
     greenBM.eraseColor(SK_ColorGREEN);
@@ -662,20 +759,27 @@
     SkAutoTUnref<SkImageFilter> source(SkImageSource::Create(greenImage.get()));
     SkAutoTUnref<SkImageFilter> merge(SkMergeImageFilter::Create(source.get(), source.get()));
 
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(1, 1);
-    bitmap.eraseColor(0);
-    const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(info, props));
-    SkImageFilter::DeviceProxy proxy(device);
+    SkAutoTUnref<SkSpecialImage> srcImg(create_empty_special_image(context, proxy, 1));
+
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(0, 0, 100, 100), nullptr);
-    SkBitmap result;
     SkIPoint offset;
-    REPORTER_ASSERT(reporter, merge->filterImageDeprecated(&proxy, bitmap, ctx, &result, &offset));
-    REPORTER_ASSERT(reporter, result.width() == 20 && result.height() == 20);
+
+    SkAutoTUnref<SkSpecialImage> resultImg(merge->filterImage(srcImg, ctx, &offset));
+    REPORTER_ASSERT(reporter, resultImg);
+
+    REPORTER_ASSERT(reporter, resultImg->width() == 20 && resultImg->height() == 20);
 }
 
+DEF_TEST(ImageFilterMergeResultSize, reporter) {
+    run_raster_test(reporter, 100, test_imagefilter_merge_result_size);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(ImageFilterMergeResultSize_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 100, test_imagefilter_merge_result_size);
+}
+#endif
+
 static void draw_blurred_rect(SkCanvas* canvas) {
     SkAutoTUnref<SkImageFilter> filter(SkBlurImageFilter::Create(SkIntToScalar(8), 0));
     SkPaint filterPaint;
@@ -803,15 +907,15 @@
 }
 
 DEF_TEST(ImageFilterCropRect, reporter) {
-    const SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100);
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-
-    SkAutoTUnref<SkBaseDevice> device(SkBitmapDevice::Create(info, props));
-    SkImageFilter::DeviceProxy proxy(device);
-
-    test_crop_rects(&proxy, reporter);
+    run_raster_test(reporter, 100, test_crop_rects);
 }
 
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(ImageFilterCropRect_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 100, test_crop_rects);
+}
+#endif
+
 DEF_TEST(ImageFilterMatrix, reporter) {
     SkBitmap temp;
     temp.allocN32Pixels(100, 100);
@@ -901,31 +1005,44 @@
         ? pixel != SK_ColorGREEN : pixel == SK_ColorGREEN);
 }
 
-DEF_TEST(ImageFilterClippedPictureImageFilter, reporter) {
-    SkRTreeFactory factory;
-    SkPictureRecorder recorder;
-    SkCanvas* recordingCanvas = recorder.beginRecording(1, 1, &factory, 0);
+static void test_clipped_picture_imagefilter(SkImageFilter::Proxy* proxy,
+                                             skiatest::Reporter* reporter,
+                                             GrContext* context) {
+    SkAutoTUnref<SkPicture> picture;
 
-    // Create an SkPicture which simply draws a green 1x1 rectangle.
-    SkPaint greenPaint;
-    greenPaint.setColor(SK_ColorGREEN);
-    recordingCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(1, 1)), greenPaint);
-    SkAutoTUnref<SkPicture> picture(recorder.endRecording());
+    {
+        SkRTreeFactory factory;
+        SkPictureRecorder recorder;
+        SkCanvas* recordingCanvas = recorder.beginRecording(1, 1, &factory, 0);
+
+        // Create an SkPicture which simply draws a green 1x1 rectangle.
+        SkPaint greenPaint;
+        greenPaint.setColor(SK_ColorGREEN);
+        recordingCanvas->drawRect(SkRect::Make(SkIRect::MakeWH(1, 1)), greenPaint);
+        picture.reset(recorder.endRecording());
+    }
+
+    SkAutoTUnref<SkSpecialImage> srcImg(create_empty_special_image(context, proxy, 2));
 
     SkAutoTUnref<SkImageFilter> imageFilter(SkPictureImageFilter::Create(picture.get()));
 
-    SkBitmap result;
     SkIPoint offset;
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeXYWH(1, 1, 1, 1), nullptr);
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(2, 2);
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkBitmapDevice device(bitmap, props);
-    SkImageFilter::DeviceProxy proxy(&device);
-    REPORTER_ASSERT(reporter,
-                    !imageFilter->filterImageDeprecated(&proxy, bitmap, ctx, &result, &offset));
+
+    SkAutoTUnref<SkSpecialImage> resultImage(imageFilter->filterImage(srcImg, ctx, &offset));
+    REPORTER_ASSERT(reporter, !resultImage);
 }
 
+DEF_TEST(ImageFilterClippedPictureImageFilter, reporter) {
+    run_raster_test(reporter, 2, test_clipped_picture_imagefilter);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(ImageFilterClippedPictureImageFilter_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 2, test_clipped_picture_imagefilter);
+}
+#endif
+
 DEF_TEST(ImageFilterEmptySaveLayer, reporter) {
     // Even when there's an empty saveLayer()/restore(), ensure that an image
     // filter or color filter which affects transparent black still draws.
@@ -1154,13 +1271,10 @@
     test_xfermode_cropped_input(&canvas, reporter);
 }
 
-DEF_TEST(ComposedImageFilterOffset, reporter) {
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    bitmap.eraseARGB(0, 0, 0, 0);
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkBitmapDevice device(bitmap, props);
-    SkImageFilter::DeviceProxy proxy(&device);
+static void test_composed_imagefilter_offset(SkImageFilter::Proxy* proxy,
+                                             skiatest::Reporter* reporter,
+                                             GrContext* context) {
+    SkAutoTUnref<SkSpecialImage> srcImg(create_empty_special_image(context, proxy, 100));
 
     SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(1, 0, 20, 20));
     SkAutoTUnref<SkImageFilter> offsetFilter(SkOffsetImageFilter::Create(0, 0, nullptr, &cropRect));
@@ -1168,36 +1282,54 @@
                                                                      nullptr, &cropRect));
     SkAutoTUnref<SkImageFilter> composedFilter(SkComposeImageFilter::Create(blurFilter,
                                                                             offsetFilter.get()));
-    SkBitmap result;
     SkIPoint offset;
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr);
-    REPORTER_ASSERT(reporter,
-                    composedFilter->filterImageDeprecated(&proxy, bitmap, ctx, &result, &offset));
+
+    SkAutoTUnref<SkSpecialImage> resultImg(composedFilter->filterImage(srcImg, ctx, &offset));
+    REPORTER_ASSERT(reporter, resultImg);
     REPORTER_ASSERT(reporter, offset.fX == 1 && offset.fY == 0);
 }
 
-DEF_TEST(PartialCropRect, reporter) {
-    SkBitmap bitmap;
-    bitmap.allocN32Pixels(100, 100);
-    bitmap.eraseARGB(0, 0, 0, 0);
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-    SkBitmapDevice device(bitmap, props);
-    SkImageFilter::DeviceProxy proxy(&device);
+DEF_TEST(ComposedImageFilterOffset, reporter) {
+    run_raster_test(reporter, 100, test_composed_imagefilter_offset);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(ComposedImageFilterOffset_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 100, test_composed_imagefilter_offset);
+}
+#endif
+
+static void test_partial_crop_rect(SkImageFilter::Proxy* proxy,
+                                   skiatest::Reporter* reporter,
+                                   GrContext* context) {
+    SkAutoTUnref<SkSpecialImage> srcImg(create_empty_special_image(context, proxy, 100));
 
     SkImageFilter::CropRect cropRect(SkRect::MakeXYWH(100, 0, 20, 30),
         SkImageFilter::CropRect::kHasWidth_CropEdge | SkImageFilter::CropRect::kHasHeight_CropEdge);
     SkAutoTUnref<SkImageFilter> filter(make_grayscale(nullptr, &cropRect));
-    SkBitmap result;
     SkIPoint offset;
     SkImageFilter::Context ctx(SkMatrix::I(), SkIRect::MakeWH(100, 100), nullptr);
-    REPORTER_ASSERT(reporter,
-                    filter->filterImageDeprecated(&proxy, bitmap, ctx, &result, &offset));
+
+    SkAutoTUnref<SkSpecialImage> resultImg(filter->filterImage(srcImg, ctx, &offset));
+    REPORTER_ASSERT(reporter, resultImg);
+
     REPORTER_ASSERT(reporter, offset.fX == 0);
     REPORTER_ASSERT(reporter, offset.fY == 0);
-    REPORTER_ASSERT(reporter, result.width() == 20);
-    REPORTER_ASSERT(reporter, result.height() == 30);
+    REPORTER_ASSERT(reporter, resultImg->width() == 20);
+    REPORTER_ASSERT(reporter, resultImg->height() == 30);
 }
 
+DEF_TEST(PartialCropRect, reporter) {
+    run_raster_test(reporter, 100, test_partial_crop_rect);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_NATIVE_CONTEXT(PartialCropRect_Gpu, reporter, context) {
+    run_gpu_test(reporter, context, 100, test_partial_crop_rect);
+}
+#endif
+
 DEF_TEST(ImageFilterCanComputeFastBounds, reporter) {
 
     SkPoint3 location = SkPoint3::Make(0, 0, SK_Scalar1);
@@ -1325,20 +1457,6 @@
 
 #if SK_SUPPORT_GPU
 
-DEF_GPUTEST_FOR_NATIVE_CONTEXT(ImageFilterCropRect_Gpu, reporter, context) {
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-
-    SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
-                                                         SkBudgeted::kNo,
-                                                         SkImageInfo::MakeN32Premul(100, 100),
-                                                         0,
-                                                         &props,
-                                                         SkGpuDevice::kUninit_InitContents));
-    SkImageFilter::DeviceProxy proxy(device);
-
-    test_crop_rects(&proxy, reporter);
-}
-
 DEF_GPUTEST_FOR_NATIVE_CONTEXT(HugeBlurImageFilter_Gpu, reporter, context) {
     const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
 
@@ -1367,20 +1485,6 @@
     test_xfermode_cropped_input(&canvas, reporter);
 }
 
-DEF_GPUTEST_FOR_NATIVE_CONTEXT(TestNegativeBlurSigma_Gpu, reporter, context) {
-    const SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
-
-    SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(context,
-                                                         SkBudgeted::kNo,
-                                                         SkImageInfo::MakeN32Premul(1, 1),
-                                                         0,
-                                                         &props,
-                                                         SkGpuDevice::kUninit_InitContents));
-    SkImageFilter::DeviceProxy proxy(device);
-
-    test_negative_blur_sigma(&proxy, reporter);
-}
-
 DEF_GPUTEST_FOR_ALL_CONTEXTS(BlurLargeImage_Gpu, reporter, context) {
     SkAutoTUnref<SkSurface> surface(
         SkSurface::NewRenderTarget(context, SkBudgeted::kYes,
diff --git a/tests/SpecialImageTest.cpp b/tests/SpecialImageTest.cpp
index bde140d..3dd6da9 100644
--- a/tests/SpecialImageTest.cpp
+++ b/tests/SpecialImageTest.cpp
@@ -11,25 +11,12 @@
 #include "SkSpecialImage.h"
 #include "SkSpecialSurface.h"
 #include "Test.h"
+#include "TestingSpecialImageAccess.h"
 
 #if SK_SUPPORT_GPU
 #include "GrContext.h"
 #endif
 
-class TestingSpecialImageAccess {
-public:
-    static const SkIRect& Subset(const SkSpecialImage* img) {
-        return img->subset();
-    }
-
-    static bool PeekPixels(const SkSpecialImage* img, SkPixmap* pixmap) {
-        return img->peekPixels(pixmap);
-    }
-
-    static GrTexture* PeekTexture(const SkSpecialImage* img) {
-        return img->peekTexture();
-    }
-};
 
 // This test creates backing resources exactly sized to [kFullSize x kFullSize].
 // It then wraps them in an SkSpecialImage with only the center (red) region being active.
diff --git a/tests/TestingSpecialImageAccess.h b/tests/TestingSpecialImageAccess.h
new file mode 100644
index 0000000..cd9d77c
--- /dev/null
+++ b/tests/TestingSpecialImageAccess.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file
+ */
+
+#ifndef TestingSpecialImageAccess_DEFINED
+#define TestingSpecialImageAccess_DEFINED
+
+class TestingSpecialImageAccess {
+public:
+    static const SkIRect& Subset(const SkSpecialImage* img) {
+        return img->subset();
+    }
+
+    static bool PeekPixels(const SkSpecialImage* img, SkPixmap* pixmap) {
+        return img->testingOnlyPeekPixels(pixmap);
+    }
+
+    static GrTexture* PeekTexture(const SkSpecialImage* img) {
+        return img->peekTexture();
+    }
+
+    static bool GetROPixels(const SkSpecialImage* img, SkBitmap* result) {
+        return img->testingOnlyGetROPixels(result);
+    }
+};
+
+#endif