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