diff --git a/include/core/SkImage.h b/include/core/SkImage.h index 60f8bfb311..2353655781 100644 --- a/include/core/SkImage.h +++ b/include/core/SkImage.h @@ -75,6 +75,17 @@ public: */ 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 @@ private: 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 620922f645..9b0a0b2cf8 100644 --- a/src/image/SkImage.cpp +++ b/src/image/SkImage.cpp @@ -39,6 +39,41 @@ void SkImage::draw(SkCanvas* canvas, const SkRect* src, const SkRect& dst, 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 @@ SkData* SkImage::encode(SkImageEncoder::Type type, int quality) const { } 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(this)->onDrawRectToRect(&canvas, &srcR, dstR, NULL); + return true; +} + diff --git a/src/image/SkImage_Base.h b/src/image/SkImage_Base.h index 7bd1f7e6c9..9fdfcd2677 100644 --- a/src/image/SkImage_Base.h +++ b/src/image/SkImage_Base.h @@ -17,6 +17,14 @@ public: 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 ab8a635c1c..262fb97bfa 100644 --- a/src/image/SkImage_Raster.cpp +++ b/src/image/SkImage_Raster.cpp @@ -55,6 +55,8 @@ public: 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 @@ SkImage* SkImage_Raster::NewEmpty() { return gEmpty; } +static void release_data(void* addr, void* context) { + SkData* data = static_cast(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 ref( - SkMallocPixelRef::NewWithData(info, rowBytes, NULL, data, 0)); - fBitmap.setPixelRef(ref); + : INHERITED(info.fWidth, info.fHeight) +{ + data->ref(); + void* addr = const_cast(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 @@ SkImage_Raster::SkImage_Raster(const Info& info, SkPixelRef* pr, size_t rowBytes { fBitmap.setConfig(info, rowBytes); fBitmap.setPixelRef(pr); + fBitmap.lockPixels(); } SkImage_Raster::~SkImage_Raster() {} @@ -104,10 +114,34 @@ void SkImage_Raster::onDraw(SkCanvas* canvas, SkScalar x, SkScalar y, const SkPa 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 20e7505966..179a33c8bf 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 @@ enum SurfaceType { }; 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 @@ static void test_image(skiatest::Reporter* reporter) { 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 @@ static void test_image(skiatest::Reporter* reporter) { 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 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 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 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 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 @@ DEF_GPUTEST(Surface, reporter, factory) { 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);