add ways to peer into an image to get its pixels

BUG=skia:
R=bsalomon@google.com, scroggo@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@13339 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkImage.h b/include/core/SkImage.h
index 60f8bfb..2353655 100644
--- a/include/core/SkImage.h
+++ b/include/core/SkImage.h
@@ -76,6 +76,17 @@
     void draw(SkCanvas*, const SkRect* src, const SkRect& dst, const SkPaint*);
 
     /**
+     *  If the image has direct access to its pixels (i.e. they are in local
+     *  RAM) 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.
+     *
+     *  On failure, returns NULL and the info and rowBytes parameters are
+     *  ignored.
+     */
+    const void* peekPixels(SkImageInfo* info, size_t* rowBytes) const;
+    
+    /**
      *  Encode the image's pixels and return the result as a new SkData, which
      *  the caller must manage (i.e. call unref() when they are done).
      *
@@ -103,6 +114,29 @@
     static uint32_t NextUniqueID();
 
     typedef SkRefCnt INHERITED;
+
+    /**
+     *  Return a copy of the image's pixels, limiting them to the subset
+     *  rectangle's intersection wit the image bounds. If subset is NULL, then
+     *  the entire image will be considered.
+     *
+     *  If the bitmap's pixels have already been allocated, then readPixels()
+     *  will succeed only if it can support converting the image's pixels into
+     *  the bitmap's ColorType/AlphaType. Any pixels in the bitmap that do not
+     *  intersect with the image's bounds and the subset (if not null) will be
+     *  left untouched.
+     *
+     *  If the bitmap is initially empty/unallocated, then it will be allocated
+     *  using the default allocator, and the ColorType/AlphaType will be chosen
+     *  to most closely fit the image's configuration.
+     *
+     *  On failure, false will be returned, and bitmap will unmodified.
+     */
+    // On ice for now:
+    // - should it respect the particular colortype/alphatype of the src
+    // - should it have separate entrypoints for preallocated and not bitmaps?
+    // - isn't it enough to allow the caller to draw() the image into a canvas?
+    bool readPixels(SkBitmap* bitmap, const SkIRect* subset = NULL) const;
 };
 
 #endif
diff --git a/src/image/SkImage.cpp b/src/image/SkImage.cpp
index 620922f..9b0a0b2 100644
--- a/src/image/SkImage.cpp
+++ b/src/image/SkImage.cpp
@@ -39,6 +39,41 @@
     as_IB(this)->onDrawRectToRect(canvas, src, dst, paint);
 }
 
+const void* SkImage::peekPixels(SkImageInfo* info, size_t* rowBytes) const {
+    SkImageInfo infoStorage;
+    size_t rowBytesStorage;
+    if (NULL == info) {
+        info = &infoStorage;
+    }
+    if (NULL == rowBytes) {
+        rowBytes = &rowBytesStorage;
+    }
+    return as_IB(this)->onPeekPixels(info, rowBytes);
+}
+
+bool SkImage::readPixels(SkBitmap* bitmap, const SkIRect* subset) const {
+    if (NULL == bitmap) {
+        return false;
+    }
+    
+    SkIRect bounds = SkIRect::MakeWH(this->width(), this->height());
+
+    // trim against the bitmap, if its already been allocated
+    if (bitmap->pixelRef()) {
+        bounds.fRight = SkMin32(bounds.fRight, bitmap->width());
+        bounds.fBottom = SkMin32(bounds.fBottom, bitmap->height());
+        if (bounds.isEmpty()) {
+            return false;
+        }
+    }
+
+    if (subset && !bounds.intersect(*subset)) {
+        // perhaps we could return true + empty-bitmap?
+        return false;
+    }
+    return as_IB(this)->onReadPixels(bitmap, bounds);
+}
+
 GrTexture* SkImage::getTexture() {
     return as_IB(this)->onGetTexture();
 }
@@ -50,3 +85,55 @@
     }
     return NULL;
 }
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool raster_canvas_supports(const SkImageInfo& info) {
+    switch (info.fColorType) {
+        case kPMColor_SkColorType:
+            return kUnpremul_SkAlphaType != info.fAlphaType;
+        case kRGB_565_SkColorType:
+            return true;
+        case kAlpha_8_SkColorType:
+            return true;
+        default:
+            break;
+    }
+    return false;
+}
+
+bool SkImage_Base::onReadPixels(SkBitmap* bitmap, const SkIRect& subset) const {
+    SkImageInfo info;
+
+    if (bitmap->pixelRef()) {
+        if (!bitmap->asImageInfo(&info)) {
+            return false;
+        }
+        if (!raster_canvas_supports(info)) {
+            return false;
+        }
+    } else {
+        SkImageInfo info = SkImageInfo::MakeN32Premul(subset.width(),
+                                                      subset.height());
+        SkBitmap tmp;
+        if (!tmp.allocPixels(info)) {
+            return false;
+        }
+        *bitmap = tmp;
+    }
+
+    SkRect srcR, dstR;
+    srcR.set(subset);
+    dstR = srcR;
+    dstR.offset(-dstR.left(), -dstR.top());
+    
+    SkCanvas canvas(*bitmap);
+
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kClear_Mode);
+    canvas.drawRect(dstR, paint);
+
+    const_cast<SkImage_Base*>(this)->onDrawRectToRect(&canvas, &srcR, dstR, NULL);
+    return true;
+}
+
diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h
index 7bd1f7e..9fdfcd2 100644
--- a/src/image/SkImage_Base.h
+++ b/src/image/SkImage_Base.h
@@ -17,6 +17,14 @@
     virtual void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) = 0;
     virtual void onDrawRectToRect(SkCanvas*, const SkRect* src,
                                   const SkRect& dst, const SkPaint*) = 0;
+
+    // Default impl calls onDraw
+    virtual bool onReadPixels(SkBitmap*, const SkIRect& subset) const;
+
+    virtual const void* onPeekPixels(SkImageInfo*, size_t* /*rowBytes*/) const {
+        return NULL;
+    }
+
     virtual GrTexture* onGetTexture() { return NULL; }
 
     // return a read-only copy of the pixels. We promise to not modify them,
diff --git a/src/image/SkImage_Raster.cpp b/src/image/SkImage_Raster.cpp
index ab8a635..262fb97 100644
--- a/src/image/SkImage_Raster.cpp
+++ b/src/image/SkImage_Raster.cpp
@@ -55,6 +55,8 @@
 
     virtual void onDraw(SkCanvas*, SkScalar, SkScalar, const SkPaint*) SK_OVERRIDE;
     virtual void onDrawRectToRect(SkCanvas*, const SkRect*, const SkRect&, const SkPaint*) SK_OVERRIDE;
+    virtual bool onReadPixels(SkBitmap*, const SkIRect&) const SK_OVERRIDE;
+    virtual const void* onPeekPixels(SkImageInfo*, size_t* /*rowBytes*/) const SK_OVERRIDE;
     virtual bool getROPixels(SkBitmap*) const SK_OVERRIDE;
 
     // exposed for SkSurface_Raster via SkNewImageFromPixelRef
@@ -82,13 +84,20 @@
     return gEmpty;
 }
 
+static void release_data(void* addr, void* context) {
+    SkData* data = static_cast<SkData*>(context);
+    data->unref();
+}
+
 SkImage_Raster::SkImage_Raster(const Info& info, SkData* data, size_t rowBytes)
-        : INHERITED(info.fWidth, info.fHeight) {
-    fBitmap.setConfig(info, rowBytes);
-    SkAutoTUnref<SkPixelRef> ref(
-        SkMallocPixelRef::NewWithData(info, rowBytes, NULL, data, 0));
-    fBitmap.setPixelRef(ref);
+    : INHERITED(info.fWidth, info.fHeight)
+{
+    data->ref();
+    void* addr = const_cast<void*>(data->data());
+
+    fBitmap.installPixels(info, addr, rowBytes, release_data, data);
     fBitmap.setImmutable();
+    fBitmap.lockPixels();
 }
 
 SkImage_Raster::SkImage_Raster(const Info& info, SkPixelRef* pr, size_t rowBytes)
@@ -96,6 +105,7 @@
 {
     fBitmap.setConfig(info, rowBytes);
     fBitmap.setPixelRef(pr);
+    fBitmap.lockPixels();
 }
 
 SkImage_Raster::~SkImage_Raster() {}
@@ -104,10 +114,34 @@
     canvas->drawBitmap(fBitmap, x, y, paint);
 }
 
-void SkImage_Raster::onDrawRectToRect(SkCanvas* canvas, const SkRect* src, const SkRect& dst, const SkPaint* paint) {
+void SkImage_Raster::onDrawRectToRect(SkCanvas* canvas, const SkRect* src,
+                                      const SkRect& dst, const SkPaint* paint) {
     canvas->drawBitmapRectToRect(fBitmap, src, dst, paint);
 }
 
+bool SkImage_Raster::onReadPixels(SkBitmap* dst, const SkIRect& subset) const {
+    if (dst->pixelRef()) {
+        return this->INHERITED::onReadPixels(dst, subset);
+    } else {
+        SkBitmap src;
+        if (!fBitmap.extractSubset(&src, subset)) {
+            return false;
+        }
+        return src.copyTo(dst, src.config());
+    }
+}
+
+const void* SkImage_Raster::onPeekPixels(SkImageInfo* infoPtr,
+                                         size_t* rowBytesPtr) const {
+    SkImageInfo info;
+    if (!fBitmap.asImageInfo(&info) || !fBitmap.getPixels()) {
+        return false;
+    }
+    *infoPtr = info;
+    *rowBytesPtr = fBitmap.rowBytes();
+    return fBitmap.getPixels();
+}
+
 bool SkImage_Raster::getROPixels(SkBitmap* dst) const {
     *dst = fBitmap;
     return true;
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 20e7505..179a33c 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -6,8 +6,11 @@
  */
 
 #include "SkCanvas.h"
+#include "SkData.h"
+#include "SkImageEncoder.h"
 #include "SkRRect.h"
 #include "SkSurface.h"
+#include "SkUtils.h"
 #include "Test.h"
 
 #if SK_SUPPORT_GPU
@@ -24,31 +27,32 @@
 };
 
 static SkSurface* createSurface(SurfaceType surfaceType, GrContext* context) {
-    static const SkImageInfo imageSpec = {
-        10,  // width
-        10,  // height
-        kPMColor_SkColorType,
-        kPremul_SkAlphaType
-    };
+    static const SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10);
 
     switch (surfaceType) {
     case kRaster_SurfaceType:
-        return SkSurface::NewRaster(imageSpec);
+        return SkSurface::NewRaster(info);
     case kGpu_SurfaceType:
 #if SK_SUPPORT_GPU
         SkASSERT(NULL != context);
-        return SkSurface::NewRenderTarget(context, imageSpec);
+        return SkSurface::NewRenderTarget(context, info);
 #else
         SkASSERT(0);
 #endif
     case kPicture_SurfaceType:
-        return SkSurface::NewPicture(10, 10);
+        return SkSurface::NewPicture(info.fWidth, info.fHeight);
     }
     SkASSERT(0);
     return NULL;
 }
 
-#include "SkData.h"
+enum ImageType {
+    kRasterCopy_ImageType,
+    kRasterData_ImageType,
+    kGpu_ImageType,
+    kPicture_ImageType,
+    kCodec_ImageType,
+};
 
 static void test_image(skiatest::Reporter* reporter) {
     SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
@@ -56,7 +60,7 @@
     size_t size = info.getSafeSize(rowBytes);
     void* addr = sk_malloc_throw(size);
     SkData* data = SkData::NewFromMalloc(addr, size);
-
+    
     REPORTER_ASSERT(reporter, 1 == data->getRefCnt());
     SkImage* image = SkImage::NewRasterData(info, data, rowBytes);
     REPORTER_ASSERT(reporter, 2 == data->getRefCnt());
@@ -65,6 +69,81 @@
     data->unref();
 }
 
+static SkImage* createImage(ImageType imageType, GrContext* context,
+                            SkColor color) {
+    const SkPMColor pmcolor = SkPreMultiplyColor(color);
+    const SkImageInfo info = SkImageInfo::MakeN32Premul(10, 10);
+    const size_t rowBytes = info.minRowBytes();
+    const size_t size = rowBytes * info.fHeight;
+
+    void* addr = sk_malloc_throw(size);
+    sk_memset32((SkPMColor*)addr, pmcolor, SkToInt(size >> 2));
+    SkAutoTUnref<SkData> data(SkData::NewFromMalloc(addr, size));
+
+    switch (imageType) {
+        case kRasterCopy_ImageType:
+            return SkImage::NewRasterCopy(info, addr, rowBytes);
+        case kRasterData_ImageType:
+            return SkImage::NewRasterData(info, data, rowBytes);
+        case kGpu_ImageType:
+            return NULL;        // TODO
+        case kPicture_ImageType: {
+            SkAutoTUnref<SkSurface> surf(SkSurface::NewPicture(info.fWidth,
+                                                               info.fHeight));
+            surf->getCanvas()->drawColor(SK_ColorRED);
+            return surf->newImageSnapshot();
+        }
+        case kCodec_ImageType: {
+            SkBitmap bitmap;
+            bitmap.installPixels(info, addr, rowBytes, NULL, NULL);
+            SkAutoTUnref<SkData> src(
+                 SkImageEncoder::EncodeData(bitmap, SkImageEncoder::kPNG_Type,
+                                            100));
+            return SkImage::NewEncodedData(src);
+        }
+    }
+    SkASSERT(false);
+    return NULL;
+}
+
+static void test_imagepeek(skiatest::Reporter* reporter) {
+    static const struct {
+        ImageType   fType;
+        bool        fPeekShouldSucceed;
+    } gRec[] = {
+        { kRasterCopy_ImageType,    true    },
+        { kRasterData_ImageType,    true    },
+        { kGpu_ImageType,           false    },
+        { kPicture_ImageType,       false   },
+        { kCodec_ImageType,         false   },
+    };
+
+    const SkColor color = SK_ColorRED;
+    const SkPMColor pmcolor = SkPreMultiplyColor(color);
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
+        SkImageInfo info;
+        size_t rowBytes;
+        
+        SkAutoTUnref<SkImage> image(createImage(gRec[i].fType, NULL, color));
+        if (!image.get()) {
+            continue;   // gpu may not be enabled
+        }
+        const void* addr = image->peekPixels(&info, &rowBytes);
+        bool success = (NULL != addr);
+        REPORTER_ASSERT(reporter, gRec[i].fPeekShouldSucceed == success);
+        if (success) {
+            REPORTER_ASSERT(reporter, 10 == info.fWidth);
+            REPORTER_ASSERT(reporter, 10 == info.fHeight);
+            REPORTER_ASSERT(reporter, kPMColor_SkColorType == info.fColorType);
+            REPORTER_ASSERT(reporter, kPremul_SkAlphaType == info.fAlphaType ||
+                                      kOpaque_SkAlphaType == info.fAlphaType);
+            REPORTER_ASSERT(reporter, info.minRowBytes() <= rowBytes);
+            REPORTER_ASSERT(reporter, pmcolor == *(const SkPMColor*)addr);
+        }
+    }
+}
+
 static void TestSurfaceCopyOnWrite(skiatest::Reporter* reporter, SurfaceType surfaceType,
                                    GrContext* context) {
     // Verify that the right canvas commands trigger a copy on write
@@ -257,6 +336,7 @@
     TestSurfaceWritableAfterSnapshotRelease(reporter, kPicture_SurfaceType, NULL);
     TestSurfaceNoCanvas(reporter, kRaster_SurfaceType, NULL, SkSurface::kDiscard_ContentChangeMode);
     TestSurfaceNoCanvas(reporter, kRaster_SurfaceType, NULL, SkSurface::kRetain_ContentChangeMode);
+    test_imagepeek(reporter);
 #if SK_SUPPORT_GPU
     TestGetTexture(reporter, kRaster_SurfaceType, NULL);
     TestGetTexture(reporter, kPicture_SurfaceType, NULL);