Add SkSpecialImage & SkSpecialSurface classes

Initial classes.

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

Review URL: https://codereview.chromium.org/1579323002
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 80f0eac..db54377 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -249,6 +249,10 @@
         '<(skia_src_path)/core/SkSharedMutex.cpp',
         '<(skia_src_path)/core/SkSharedMutex.h',
         '<(skia_src_path)/core/SkSmallAllocator.h',
+        '<(skia_src_path)/core/SkSpecialImage.cpp',
+        '<(skia_src_path)/core/SkSpecialImage.h',
+        '<(skia_src_path)/core/SkSpecialSurface.cpp',
+        '<(skia_src_path)/core/SkSpecialSurface.h',
         '<(skia_src_path)/core/SkSpinlock.cpp',
         '<(skia_src_path)/core/SkSpriteBlitter_ARGB32.cpp',
         '<(skia_src_path)/core/SkSpriteBlitter_RGB16.cpp',
diff --git a/include/core/SkPixelRef.h b/include/core/SkPixelRef.h
index 9cdcd85..eccbe4b 100644
--- a/include/core/SkPixelRef.h
+++ b/include/core/SkPixelRef.h
@@ -381,6 +381,7 @@
 
     bool isPreLocked() const { return fPreLocked; }
     friend class SkImage_Raster;
+    friend class SkSpecialImage_Raster;
 
     // When copying a bitmap to another with the same shape and config, we can safely
     // clone the pixelref generation ID too, which makes them equivalent under caching.
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
new file mode 100644
index 0000000..50447fc
--- /dev/null
+++ b/src/core/SkSpecialImage.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file
+ */
+
+#include "SkCanvas.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
+
+///////////////////////////////////////////////////////////////////////////////
+class SkSpecialImage_Base : public SkSpecialImage {
+public:
+    SkSpecialImage_Base(const SkIRect& subset) : INHERITED(subset) { }
+    virtual ~SkSpecialImage_Base() { }
+
+    virtual void onDraw(SkCanvas*, int x, int y, const SkPaint*) const = 0;
+
+    virtual bool onPeekPixels(SkPixmap*) const { return false; }
+
+    virtual GrTexture* onPeekTexture() const { return nullptr; }
+
+    virtual SkSpecialSurface* onNewSurface(const SkImageInfo& info) const { return nullptr; }
+
+private:
+    typedef SkSpecialImage INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+static inline const SkSpecialImage_Base* as_IB(const SkSpecialImage* image) {
+    return static_cast<const SkSpecialImage_Base*>(image);
+}
+
+void SkSpecialImage::draw(SkCanvas* canvas, int x, int y, const SkPaint* paint) const {
+    return as_IB(this)->onDraw(canvas, x, y, paint);
+}
+
+bool SkSpecialImage::peekPixels(SkPixmap* pixmap) const {
+    return as_IB(this)->onPeekPixels(pixmap);
+}
+
+GrTexture* SkSpecialImage::peekTexture() const {
+    return as_IB(this)->onPeekTexture();
+}
+
+SkSpecialSurface* SkSpecialImage::newSurface(const SkImageInfo& info) const {
+    return as_IB(this)->onNewSurface(info);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#include "SkImage.h"
+#if SK_SUPPORT_GPU
+#include "SkGr.h"
+#include "SkGrPriv.h"
+#endif
+
+class SkSpecialImage_Image : public SkSpecialImage_Base {
+public:
+    SkSpecialImage_Image(const SkIRect& subset, const SkImage* image)
+        : INHERITED(subset)
+        , fImage(SkRef(image)) {
+    }
+
+    ~SkSpecialImage_Image() override { }
+
+    void onDraw(SkCanvas* canvas, int x, int y, const SkPaint* paint) const override {
+        SkRect dst = SkRect::MakeXYWH(x, y, this->subset().width(), this->subset().height());
+
+        canvas->drawImageRect(fImage, this->subset(),
+                              dst, paint, SkCanvas::kStrict_SrcRectConstraint);
+    }
+
+    bool onPeekPixels(SkPixmap* pixmap) const override {
+        return fImage->peekPixels(pixmap);
+    }
+
+    GrTexture* onPeekTexture() const override { return fImage->getTexture(); }
+
+    SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
+#if SK_SUPPORT_GPU
+        GrTexture* texture = fImage->getTexture();
+        if (texture) {
+            GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(info);
+            desc.fFlags = kRenderTarget_GrSurfaceFlag;
+
+            return SkSpecialSurface::NewRenderTarget(texture->getContext(), desc);
+        }
+#endif
+        return SkSpecialSurface::NewRaster(info, nullptr);
+    }
+
+private:
+    SkAutoTUnref<const SkImage> fImage;
+
+    typedef SkSpecialImage_Base INHERITED;
+};
+
+#ifdef SK_DEBUG
+static bool rect_fits(const SkIRect& rect, int width, int height) {
+    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 &&
+           rect.fBottom >= 0 && rect.fBottom <= height;
+}
+#endif
+
+SkSpecialImage* SkSpecialImage::NewFromImage(const SkIRect& subset, const SkImage* image) {
+    SkASSERT(rect_fits(subset, image->width(), image->height()));
+    return new SkSpecialImage_Image(subset, image);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#include "SkBitmap.h"
+#include "SkImageInfo.h"
+#include "SkPixelRef.h"
+
+class SkSpecialImage_Raster : public SkSpecialImage_Base {
+public:
+    SkSpecialImage_Raster(const SkIRect& subset, const SkBitmap& bm)
+        : INHERITED(subset)
+        , fBitmap(bm) {
+        if (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();
+        }
+    }
+
+    ~SkSpecialImage_Raster() override { }
+
+    void onDraw(SkCanvas* canvas, int x, int y, const SkPaint* paint) const override {
+        SkRect dst = SkRect::MakeXYWH(x, y,
+                                      this->subset().width(), this->subset().height());
+
+        canvas->drawBitmapRect(fBitmap, this->subset(),
+                               dst, paint, SkCanvas::kStrict_SrcRectConstraint);
+    }
+
+    bool onPeekPixels(SkPixmap* pixmap) const override {
+        const SkImageInfo info = fBitmap.info();
+        if ((kUnknown_SkColorType == info.colorType()) || !fBitmap.getPixels()) {
+            return false;
+        }
+        const void* pixels = fBitmap.getPixels();
+        if (pixels) {
+            if (pixmap) {
+                pixmap->reset(info, pixels, fBitmap.rowBytes());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
+        return SkSpecialSurface::NewRaster(info, nullptr);
+    }
+
+private:
+    SkBitmap fBitmap;
+
+    typedef SkSpecialImage_Base INHERITED;
+};
+
+SkSpecialImage* SkSpecialImage::NewFromRaster(const SkIRect& subset, const SkBitmap& bm) {
+    SkASSERT(nullptr == bm.getTexture());
+    SkASSERT(rect_fits(subset, bm.width(), bm.height()));
+    return new SkSpecialImage_Raster(subset, bm);
+}
+
+#if SK_SUPPORT_GPU
+///////////////////////////////////////////////////////////////////////////////
+#include "GrTexture.h"
+
+class SkSpecialImage_Gpu : public SkSpecialImage_Base {
+public:
+    SkSpecialImage_Gpu(const SkIRect& subset, GrTexture* tex)
+        : INHERITED(subset)
+        , fTexture(SkRef(tex)) {
+    }
+
+    ~SkSpecialImage_Gpu() override { }
+
+    void onDraw(SkCanvas* canvas, int x, int y, const SkPaint* paint) const override {
+        SkRect dst = SkRect::MakeXYWH(x, y,
+                                      this->subset().width(), this->subset().height());
+
+        SkBitmap bm;
+
+        static const bool kUnknownOpacity = false;
+        GrWrapTextureInBitmap(fTexture,
+                              fTexture->width(), fTexture->height(), kUnknownOpacity, &bm);
+
+        canvas->drawBitmapRect(bm, this->subset(),
+                               dst, paint, SkCanvas::kStrict_SrcRectConstraint);
+    }
+
+    GrTexture* onPeekTexture() const override { return fTexture; }
+
+    SkSpecialSurface* onNewSurface(const SkImageInfo& info) const override {
+        GrSurfaceDesc desc = GrImageInfoToSurfaceDesc(info);
+        desc.fFlags = kRenderTarget_GrSurfaceFlag;
+
+        return SkSpecialSurface::NewRenderTarget(fTexture->getContext(), desc);
+    }
+
+private:
+    SkAutoTUnref<GrTexture> fTexture;
+
+    typedef SkSpecialImage_Base INHERITED;
+};
+
+SkSpecialImage* SkSpecialImage::NewFromGpu(const SkIRect& subset, GrTexture* tex) {
+    SkASSERT(rect_fits(subset, tex->width(), tex->height()));
+    return new SkSpecialImage_Gpu(subset, tex);
+}
+
+#else
+
+SkSpecialImage* SkSpecialImage::NewFromGpu(const SkIRect& subset, GrTexture* tex) {
+    return nullptr;
+}
+
+#endif
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
new file mode 100644
index 0000000..23ef573
--- /dev/null
+++ b/src/core/SkSpecialImage.h
@@ -0,0 +1,84 @@
+/*
+ * 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 SkSpecialImage_DEFINED
+#define SkSpecialImage_DEFINED
+
+#include "SkRefCnt.h"
+
+class GrTexture;
+class SkBitmap;
+class SkCanvas;
+class SkImage;
+struct SkImageInfo;
+class SkPaint;
+class SkSpecialSurface;
+
+/**
+ * This is a restricted form of SkImage solely intended for internal use. It
+ * differs from SkImage in that:
+ *      - it can only be backed by raster or gpu (no generators)
+ *      - it can be backed by a GrTexture larger than its nominal bounds
+ *      - it can't be drawn tiled
+ *      - it can't be drawn with MIPMAPs
+ * It is similar to SkImage in that it abstracts how the pixels are stored/represented.
+ *
+ * Note: the contents of the backing storage outside of the subset rect are undefined.
+ */
+class SkSpecialImage : public SkRefCnt {
+public:
+    int width() const { return fSubset.width(); }
+    int height() const { return fSubset.height(); }
+
+    /**
+     *  Draw this SpecialImage into the canvas.
+     */
+    void draw(SkCanvas*, int x, int y, const SkPaint*) const;
+
+    static SkSpecialImage* NewFromImage(const SkIRect& subset, const SkImage*);
+    static SkSpecialImage* NewFromRaster(const SkIRect& subset, const SkBitmap&);
+    static SkSpecialImage* NewFromGpu(const SkIRect& subset, GrTexture*);
+
+    /**
+     *  Create a new surface with a backend that is compatible with this image.
+     */
+    SkSpecialSurface* newSurface(const SkImageInfo&) const;
+
+protected:
+    SkSpecialImage(const SkIRect& subset) : fSubset(subset) { }
+
+    // The following 3 are for testing and shouldn't be used.
+    friend class TestingSpecialImageAccess;
+    friend class TestingSpecialSurfaceAccess;
+    const SkIRect& subset() const { return fSubset; }
+
+    /**
+     *  If the SpecialImage is backed by cpu pixels, return the const address
+     *  of those pixels and, if not null, return the ImageInfo and rowBytes.
+     *  The returned address is only valid while the image object is in scope.
+     *
+     *  The returned ImageInfo represents the backing memory. Use 'subset'
+     *  to get the active portion's dimensions.
+     *
+     *  On failure, return false and ignore the pixmap parameter.
+     */
+    bool peekPixels(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;
+
+private:
+    const SkIRect fSubset;
+
+    typedef SkRefCnt INHERITED;
+};
+
+#endif
+
diff --git a/src/core/SkSpecialSurface.cpp b/src/core/SkSpecialSurface.cpp
new file mode 100644
index 0000000..5d57dcf
--- /dev/null
+++ b/src/core/SkSpecialSurface.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file
+ */
+
+#include "SkCanvas.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
+#include "SkSurfacePriv.h"
+
+ ///////////////////////////////////////////////////////////////////////////////
+class SkSpecialSurface_Base : public SkSpecialSurface {
+public:
+    SkSpecialSurface_Base(const SkIRect& subset, const SkSurfaceProps* props)
+        : INHERITED(subset, props)
+        , fCanvas(nullptr) {
+    }
+
+    virtual ~SkSpecialSurface_Base() { }
+
+    // reset is called after an SkSpecialImage has been snapped
+    void reset() { fCanvas.reset(); }
+
+    // This can return nullptr if reset has already been called or something when wrong in the ctor
+    SkCanvas* onGetCanvas() { return fCanvas; }
+
+    virtual SkSpecialImage* onNewImageSnapshot() = 0;
+
+protected:
+    SkAutoTUnref<SkCanvas> fCanvas;   // initialized by derived classes in ctors
+
+private:
+    typedef SkSpecialSurface INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+static SkSpecialSurface_Base* as_SB(SkSpecialSurface* surface) {
+    return static_cast<SkSpecialSurface_Base*>(surface);
+}
+
+SkSpecialSurface::SkSpecialSurface(const SkIRect& subset, const SkSurfaceProps* props)
+    : fProps(SkSurfacePropsCopyOrDefault(props))
+    , fSubset(subset) {
+    SkASSERT(fSubset.width() > 0);
+    SkASSERT(fSubset.height() > 0);
+}
+
+SkCanvas* SkSpecialSurface::getCanvas() {
+    return as_SB(this)->onGetCanvas();
+}
+
+SkSpecialImage* SkSpecialSurface::newImageSnapshot() {
+    SkSpecialImage* image = as_SB(this)->onNewImageSnapshot();
+    as_SB(this)->reset();
+    return SkSafeRef(image);   // the caller will call unref() to balance this
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#include "SkMallocPixelRef.h"
+
+class SkSpecialSurface_Raster : public SkSpecialSurface_Base {
+public:
+    SkSpecialSurface_Raster(SkPixelRef* pr, const SkIRect& subset, const SkSurfaceProps* props)
+        : INHERITED(subset, props) {   
+        const SkImageInfo& info = pr->info();
+
+        fBitmap.setInfo(info, info.minRowBytes());
+        fBitmap.setPixelRef(pr);
+
+        fCanvas.reset(new SkCanvas(fBitmap));
+    }
+
+    ~SkSpecialSurface_Raster() override { }
+
+    SkSpecialImage* onNewImageSnapshot() override {
+        return SkSpecialImage::NewFromRaster(this->subset(), fBitmap);
+    }
+
+private:
+    SkBitmap fBitmap;
+
+    typedef SkSpecialSurface_Base INHERITED;
+};
+
+SkSpecialSurface* SkSpecialSurface::NewFromBitmap(const SkIRect& subset, SkBitmap& bm,
+                                                  const SkSurfaceProps* props) {
+    return new SkSpecialSurface_Raster(bm.pixelRef(), subset, props);
+}
+
+SkSpecialSurface* SkSpecialSurface::NewRaster(const SkImageInfo& info,
+                                              const SkSurfaceProps* props) {
+    SkAutoTUnref<SkPixelRef> pr(SkMallocPixelRef::NewZeroed(info, 0, nullptr));
+    if (nullptr == pr.get()) {
+        return nullptr;
+    }
+
+    const SkIRect subset = SkIRect::MakeWH(pr->info().width(), pr->info().height());
+
+    return new SkSpecialSurface_Raster(pr, subset, props);
+}
+
+#if SK_SUPPORT_GPU
+///////////////////////////////////////////////////////////////////////////////
+#include "GrContext.h"
+#include "SkGpuDevice.h"
+
+class SkSpecialSurface_Gpu : public SkSpecialSurface_Base {
+public:
+    SkSpecialSurface_Gpu(GrTexture* texture, const SkIRect& subset, const SkSurfaceProps* props)
+        : INHERITED(subset, props)
+        , fTexture(texture) {
+
+        SkASSERT(fTexture->asRenderTarget());
+
+        SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(fTexture->asRenderTarget(), props,
+                                                             SkGpuDevice::kUninit_InitContents));
+        if (!device) {
+            return;
+        }
+
+        fCanvas.reset(new SkCanvas(device));
+    }
+
+    ~SkSpecialSurface_Gpu() override { }
+
+    SkSpecialImage* onNewImageSnapshot() override {
+        return SkSpecialImage::NewFromGpu(this->subset(), fTexture);
+    }
+
+private:
+    SkAutoTUnref<GrTexture> fTexture;
+
+    typedef SkSpecialSurface_Base INHERITED;
+};
+
+SkSpecialSurface* SkSpecialSurface::NewFromTexture(const SkIRect& subset, GrTexture* texture,
+                                                   const SkSurfaceProps* props) {
+    if (!texture->asRenderTarget()) {
+        return nullptr;
+    }
+
+    return new SkSpecialSurface_Gpu(texture, subset, props);
+}
+
+SkSpecialSurface* SkSpecialSurface::NewRenderTarget(GrContext* context,
+                                                    const GrSurfaceDesc& desc,
+                                                    const SkSurfaceProps* props) {
+    if (!context || !SkToBool(desc.fFlags & kRenderTarget_GrSurfaceFlag)) {
+        return nullptr;
+    }
+
+    GrTexture* temp = context->textureProvider()->createApproxTexture(desc);
+    if (!temp) {
+        return nullptr;
+    }
+
+    const SkIRect subset = SkIRect::MakeWH(desc.fWidth, desc.fHeight);
+
+    return new SkSpecialSurface_Gpu(temp, subset, props);
+}
+
+#else
+
+SkSpecialSurface* SkSpecialSurface::NewFromTexture(const SkIRect& subset, GrTexture*,
+                                                   const SkSurfaceProps*) {
+    return nullptr;
+}
+
+SkSpecialSurface* SkSpecialSurface::NewRenderTarget(GrContext* context,
+                                                    const GrSurfaceDesc& desc,
+                                                    const SkSurfaceProps* props) {
+    return nullptr;
+}
+
+#endif
diff --git a/src/core/SkSpecialSurface.h b/src/core/SkSpecialSurface.h
new file mode 100644
index 0000000..2751c4a
--- /dev/null
+++ b/src/core/SkSpecialSurface.h
@@ -0,0 +1,93 @@
+/*
+ * 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 SkSpecialSurface_DEFINED
+#define SkSpecialSurface_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkSurfaceProps.h"
+
+class GrContext;
+struct GrSurfaceDesc;
+class SkCanvas;
+struct SkImageInfo;
+class SkSpecialImage;
+
+/**
+ * SkSpecialSurface is a restricted form of SkSurface solely for internal use. It differs
+ * from SkSurface in that:
+ *     - it can be backed by GrTextures larger than [ fWidth, fHeight ]
+ *     - it can't be used for tiling
+ *     - it becomes inactive once a snapshot of it is taken (i.e., no copy-on-write)
+ *     - it has no generation ID
+ */
+class SkSpecialSurface : public SkRefCnt {
+public:
+    const SkSurfaceProps& props() const { return fProps; }
+
+    int width() const { return fSubset.width(); }
+    int height() const { return fSubset.height(); }
+
+    /**
+    *  Return a canvas that will draw into this surface. This will always
+    *  return the same canvas for a given surface, and is managed/owned by the
+    *  surface. 
+    *
+    *  The canvas will be invalid after 'newImageSnapshot' is called.
+    */
+    SkCanvas* getCanvas();
+
+    /**
+    *  Returns an image of the current state of the surface pixels up to this
+    *  point. The canvas returned by 'getCanvas' becomes invalidated by this
+    *  call and no more drawing to this surface is allowed.
+    */
+    SkSpecialImage* newImageSnapshot();
+
+    /**
+     *  Use an existing (renderTarget-capable) GrTexture as the backing store.
+     */
+    static SkSpecialSurface* NewFromTexture(const SkIRect& subset, GrTexture*,
+                                            const SkSurfaceProps* = nullptr);
+
+    /**
+     *  Allocate a new GPU-backed SkSpecialSurface. If the requested surface cannot
+     *  be created, nullptr will be returned.
+     */
+    static SkSpecialSurface* NewRenderTarget(GrContext*, const GrSurfaceDesc&,
+                                             const SkSurfaceProps* = nullptr);
+
+    /**
+     * Use and existing SkBitmap as the backing store.
+     */
+    static SkSpecialSurface* NewFromBitmap(const SkIRect& subset, SkBitmap& bm,
+                                           const SkSurfaceProps* = nullptr);
+
+    /**
+     *  Return a new CPU-backed surface, with the memory for the pixels automatically
+     *  allocated.
+     *
+     *  If the requested surface cannot be created, or the request is not a
+     *  supported configuration, nullptr will be returned.
+     */
+    static SkSpecialSurface* NewRaster(const SkImageInfo&, const SkSurfaceProps* = nullptr);
+
+protected:
+    SkSpecialSurface(const SkIRect& subset, const SkSurfaceProps*);
+
+    // For testing only
+    friend class TestingSpecialSurfaceAccess;
+    const SkIRect& subset() const { return fSubset; }
+
+private:
+    const SkSurfaceProps fProps;
+    const SkIRect        fSubset;
+
+    typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/tests/SpecialImageTest.cpp b/tests/SpecialImageTest.cpp
new file mode 100644
index 0000000..3ae8d33
--- /dev/null
+++ b/tests/SpecialImageTest.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file
+ */
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkImage.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
+#include "Test.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.
+// It then draws the SkSpecialImage to a full sized (all blue) canvas and checks that none
+// of the inactive (green) region leaked out.
+
+static const int kSmallerSize = 10;
+static const int kPad = 3;
+static const int kFullSize = kSmallerSize + 2 * kPad;
+
+// Create a bitmap with red in the center and green around it
+static SkBitmap create_bm() {
+    SkBitmap bm;
+    bm.allocN32Pixels(kFullSize, kFullSize, true);
+
+    SkCanvas temp(bm);
+
+    temp.clear(SK_ColorGREEN);
+    SkPaint p;
+    p.setColor(SK_ColorRED);
+    p.setAntiAlias(false);
+
+    temp.drawRect(SkRect::MakeXYWH(SkIntToScalar(kPad), SkIntToScalar(kPad),
+                                   SkIntToScalar(kSmallerSize), SkIntToScalar(kSmallerSize)), 
+                  p);
+
+    return bm;
+}
+
+// Basic test of the SkSpecialImage public API (e.g., peekTexture, peekPixels & draw)
+static void test_image(SkSpecialImage* img, skiatest::Reporter* reporter,
+                       bool peekPixelsSucceeds, bool peekTextureSucceeds) {
+    const SkIRect subset = TestingSpecialImageAccess::Subset(img);
+    REPORTER_ASSERT(reporter, kPad == subset.left());
+    REPORTER_ASSERT(reporter, kPad == subset.top());
+    REPORTER_ASSERT(reporter, kSmallerSize == subset.width());
+    REPORTER_ASSERT(reporter, kSmallerSize == subset.height());
+
+    //--------------
+    REPORTER_ASSERT(reporter, peekTextureSucceeds == !!TestingSpecialImageAccess::PeekTexture(img));
+
+    //--------------
+    SkPixmap pixmap;
+    REPORTER_ASSERT(reporter, peekPixelsSucceeds ==
+                              !!TestingSpecialImageAccess::PeekPixels(img, &pixmap));
+    if (peekPixelsSucceeds) {
+        REPORTER_ASSERT(reporter, kFullSize == pixmap.width());
+        REPORTER_ASSERT(reporter, kFullSize == pixmap.height());
+    }
+
+    //--------------
+    SkImageInfo info = SkImageInfo::MakeN32(kFullSize, kFullSize, kOpaque_SkAlphaType);
+
+    SkAutoTUnref<SkSpecialSurface> surf(img->newSurface(info));
+
+    SkCanvas* canvas = surf->getCanvas();
+
+    canvas->clear(SK_ColorBLUE);
+    img->draw(canvas, kPad, kPad, nullptr);
+
+    SkBitmap bm;
+    bm.allocN32Pixels(kFullSize, kFullSize, true);
+
+    bool result = canvas->readPixels(bm.info(), bm.getPixels(), bm.rowBytes(), 0, 0);
+    SkASSERT_RELEASE(result);
+
+    // Only the center (red) portion should've been drawn into the canvas
+    REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kPad-1, kPad-1));
+    REPORTER_ASSERT(reporter, SK_ColorRED  == bm.getColor(kPad, kPad));
+    REPORTER_ASSERT(reporter, SK_ColorRED  == bm.getColor(kSmallerSize+kPad-1,
+                                                          kSmallerSize+kPad-1));
+    REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kSmallerSize+kPad,
+                                                          kSmallerSize+kPad));
+}
+
+DEF_TEST(SpecialImage_Raster, reporter) {
+    SkBitmap bm = create_bm();
+
+    const SkIRect& subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    SkAutoTUnref<SkSpecialImage> img(SkSpecialImage::NewFromRaster(subset, bm));
+    test_image(img, reporter, true, false);
+}
+
+DEF_TEST(SpecialImage_Image, reporter) {
+    SkBitmap bm = create_bm();
+
+    SkAutoTUnref<SkImage> fullImage(SkImage::NewFromBitmap(bm));
+
+    const SkIRect& subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    SkAutoTUnref<SkSpecialImage> img(SkSpecialImage::NewFromImage(subset, fullImage));
+    test_image(img, reporter, true, false);
+}
+
+#if SK_SUPPORT_GPU
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SpecialImage_Gpu, reporter, context) {
+    SkBitmap bm = create_bm();
+
+    GrSurfaceDesc desc;
+    desc.fConfig = kSkia8888_GrPixelConfig;
+    desc.fFlags  = kNone_GrSurfaceFlags;
+    desc.fWidth  = kFullSize;
+    desc.fHeight = kFullSize;
+
+    SkAutoTUnref<GrTexture> texture(context->textureProvider()->createTexture(desc, false,
+                                                                              bm.getPixels(), 0));
+    if (!texture) {
+        return;
+    }
+
+    const SkIRect& subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    SkAutoTUnref<SkSpecialImage> img(SkSpecialImage::NewFromGpu(subset, texture));
+    test_image(img, reporter, false, true);
+}
+
+#endif
diff --git a/tests/SpecialSurfaceTest.cpp b/tests/SpecialSurfaceTest.cpp
new file mode 100644
index 0000000..b1cbf68
--- /dev/null
+++ b/tests/SpecialSurfaceTest.cpp
@@ -0,0 +1,115 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file
+*/
+
+#include "SkCanvas.h"
+#include "SkSpecialImage.h"
+#include "SkSpecialSurface.h"
+#include "Test.h"
+
+#if SK_SUPPORT_GPU
+#include "GrContext.h"
+#include "SkGr.h"
+#endif
+
+class TestingSpecialSurfaceAccess {
+public:
+    static const SkIRect& Subset(const SkSpecialSurface* surf) {
+        return surf->subset();
+    }
+
+    static const SkIRect& Subset(const SkSpecialImage* img) {
+        return img->subset();
+    }
+};
+
+// Both 'kSmallerSize' and 'kFullSize' need to be a non-power-of-2 to exercise
+// the gpu's loose fit behavior
+static const int kSmallerSize = 10;
+static const int kPad = 5;
+static const int kFullSize = kSmallerSize + 2 * kPad;
+
+// Exercise the public API of SkSpecialSurface (e.g., getCanvas, newImageSnapshot)
+static void test_surface(SkSpecialSurface* surf, skiatest::Reporter* reporter, int offset) {
+
+    const SkIRect surfSubset = TestingSpecialSurfaceAccess::Subset(surf);
+    REPORTER_ASSERT(reporter, offset == surfSubset.fLeft);
+    REPORTER_ASSERT(reporter, offset == surfSubset.fTop);
+    REPORTER_ASSERT(reporter, kSmallerSize == surfSubset.width());
+    REPORTER_ASSERT(reporter, kSmallerSize == surfSubset.height());
+
+    SkCanvas* canvas = surf->getCanvas();
+    SkASSERT_RELEASE(canvas);
+
+    canvas->clear(SK_ColorRED);
+
+    SkAutoTUnref<SkSpecialImage> img(surf->newImageSnapshot());
+    REPORTER_ASSERT(reporter, img);
+
+    const SkIRect imgSubset = TestingSpecialSurfaceAccess::Subset(img);
+    REPORTER_ASSERT(reporter, surfSubset == imgSubset);
+
+    // the canvas was invalidated by the newImageSnapshot call
+    REPORTER_ASSERT(reporter, !surf->getCanvas());
+}
+
+DEF_TEST(SpecialSurface_Raster, reporter) {
+
+    SkImageInfo info = SkImageInfo::MakeN32(kSmallerSize, kSmallerSize, kOpaque_SkAlphaType);
+    SkAutoTUnref<SkSpecialSurface> surf(SkSpecialSurface::NewRaster(info));
+
+    test_surface(surf, reporter, 0);
+}
+
+DEF_TEST(SpecialSurface_Raster2, reporter) {
+
+    SkBitmap bm;
+    bm.allocN32Pixels(kFullSize, kFullSize, true);
+
+    const SkIRect subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    SkAutoTUnref<SkSpecialSurface> surf(SkSpecialSurface::NewFromBitmap(subset, bm));
+
+    test_surface(surf, reporter, kPad);
+
+    // TODO: check that the clear didn't escape the active region
+}
+
+#if SK_SUPPORT_GPU
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SpecialSurface_Gpu1, reporter, context) {
+    GrSurfaceDesc desc;
+    desc.fConfig = kSkia8888_GrPixelConfig;
+    desc.fFlags  = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth  = kSmallerSize;
+    desc.fHeight = kSmallerSize;
+
+    SkAutoTUnref<SkSpecialSurface> surf(SkSpecialSurface::NewRenderTarget(context, desc));
+
+    test_surface(surf, reporter, 0);
+}
+
+// test the more flexible factory
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SpecialSurface_Gpu2, reporter, context) {
+    GrSurfaceDesc desc;
+    desc.fConfig = kSkia8888_GrPixelConfig;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth = kFullSize;
+    desc.fHeight = kFullSize;
+
+    SkAutoTUnref<GrTexture> temp(context->textureProvider()->createApproxTexture(desc));
+    SkASSERT_RELEASE(temp);
+
+    const SkIRect subset = SkIRect::MakeXYWH(kPad, kPad, kSmallerSize, kSmallerSize);
+
+    SkAutoTUnref<SkSpecialSurface> surf(SkSpecialSurface::NewFromTexture(subset, temp));
+
+    test_surface(surf, reporter, kPad);
+
+    // TODO: check that the clear didn't escape the active region
+}
+
+#endif