diff --git a/gm/factory.cpp b/gm/factory.cpp index 19eb63be0c..efd817db7d 100644 --- a/gm/factory.cpp +++ b/gm/factory.cpp @@ -12,6 +12,7 @@ #include "SkDiscardableMemoryPool.h" #include "SkDiscardablePixelRef.h" #include "SkImageDecoder.h" +#include "SkImageGenerator.h" #include "SkOSFile.h" #include "SkStream.h" @@ -35,8 +36,10 @@ protected: // bitmap is unlocked. SkAutoTUnref pool( SkNEW_ARGS(SkDiscardableMemoryPool, (1))); - SkAssertResult(SkDecodingImageGenerator::Install(data, - &fBitmap, pool)); + SkAssertResult(SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create( + data, SkDecodingImageGenerator::Options()), + &fBitmap, pool)); } } @@ -68,4 +71,4 @@ private: static GM* MyFactory(void*) { return new FactoryGM; } static GMRegistry reg(MyFactory); -} +} // namespace skiagm diff --git a/include/core/SkImageGenerator.h b/include/core/SkImageGenerator.h index d56f8f8bbb..220973a0f8 100644 --- a/include/core/SkImageGenerator.h +++ b/include/core/SkImageGenerator.h @@ -21,6 +21,8 @@ class SkImageGenerator; * the generator. If it succeeds, it will modify destination * bitmap. * + * If generator is NULL, will safely return false. + * * If this fails or when the SkDiscardablePixelRef that is * installed into destination is destroyed, it will call * SkDELETE() on the generator. Therefore, generator should be diff --git a/samplecode/SamplePicture.cpp b/samplecode/SamplePicture.cpp index af06e823b8..767fd617a3 100644 --- a/samplecode/SamplePicture.cpp +++ b/samplecode/SamplePicture.cpp @@ -40,7 +40,8 @@ static SkBitmap load_bitmap() { SkString path = SkOSPath::SkPathJoin(directory.c_str(), "mandrill_512.png"); SkAutoDataUnref data(SkData::NewFromFileName(path.c_str())); if (data.get() != NULL) { - SkDecodingImageGenerator::Install(data.get(), &bm); + SkInstallDiscardablePixelRef(SkDecodingImageGenerator::Create( + data, SkDecodingImageGenerator::Options()), &bm, NULL); } return bm; } diff --git a/src/image/SkImagePriv.cpp b/src/image/SkImagePriv.cpp index 976a5b3333..43cc44b2fa 100644 --- a/src/image/SkImagePriv.cpp +++ b/src/image/SkImagePriv.cpp @@ -9,8 +9,8 @@ #include "SkCanvas.h" #include "SkPicture.h" -SkBitmap::Config SkImageInfoToBitmapConfig(const SkImageInfo& info) { - switch (info.fColorType) { +SkBitmap::Config SkColorTypeToBitmapConfig(SkColorType colorType) { + switch (colorType) { case kAlpha_8_SkColorType: return SkBitmap::kA8_Config; @@ -33,6 +33,39 @@ SkBitmap::Config SkImageInfoToBitmapConfig(const SkImageInfo& info) { return SkBitmap::kNo_Config; } +SkBitmap::Config SkImageInfoToBitmapConfig(const SkImageInfo& info) { + return SkColorTypeToBitmapConfig(info.fColorType); +} + +bool SkBitmapConfigToColorType(SkBitmap::Config config, SkColorType* ctOut) { + SkColorType ct; + switch (config) { + case SkBitmap::kA8_Config: + ct = kAlpha_8_SkColorType; + break; + case SkBitmap::kIndex8_Config: + ct = kIndex_8_SkColorType; + break; + case SkBitmap::kRGB_565_Config: + ct = kRGB_565_SkColorType; + break; + case SkBitmap::kARGB_4444_Config: + ct = kARGB_4444_SkColorType; + break; + case SkBitmap::kARGB_8888_Config: + ct = kPMColor_SkColorType; + break; + case SkBitmap::kNo_Config: + default: + return false; + } + if (ctOut) { + *ctOut = ct; + } + return true; +} + + SkImage* SkNewImageFromBitmap(const SkBitmap& bm, bool canSharePixelRef) { SkImageInfo info; if (!bm.asImageInfo(&info)) { diff --git a/src/image/SkImagePriv.h b/src/image/SkImagePriv.h index bf28f598c5..7c19c734c2 100644 --- a/src/image/SkImagePriv.h +++ b/src/image/SkImagePriv.h @@ -14,6 +14,8 @@ class SkPicture; extern SkBitmap::Config SkImageInfoToBitmapConfig(const SkImageInfo&); +extern SkBitmap::Config SkColorTypeToBitmapConfig(SkColorType); +extern bool SkBitmapConfigToColorType(SkBitmap::Config, SkColorType* ctOut); // Call this if you explicitly want to use/share this pixelRef in the image extern SkImage* SkNewImageFromPixelRef(const SkImageInfo&, SkPixelRef*, diff --git a/src/images/SkDecodingImageGenerator.cpp b/src/images/SkDecodingImageGenerator.cpp index a833c636ff..153d1e220b 100644 --- a/src/images/SkDecodingImageGenerator.cpp +++ b/src/images/SkDecodingImageGenerator.cpp @@ -5,13 +5,14 @@ * found in the LICENSE file. */ -#include "SkDecodingImageGenerator.h" #include "SkData.h" +#include "SkDecodingImageGenerator.h" #include "SkImageDecoder.h" +#include "SkImageInfo.h" #include "SkImageGenerator.h" #include "SkImagePriv.h" #include "SkStream.h" - +#include "SkUtils.h" namespace { /** @@ -20,56 +21,55 @@ namespace { */ class TargetAllocator : public SkBitmap::Allocator { public: - TargetAllocator(void* target, size_t rowBytes, const SkImageInfo& info) + TargetAllocator(void* target, + size_t rowBytes, + int width, + int height, + SkBitmap::Config config) : fTarget(target) , fRowBytes(rowBytes) - , fInfo(info) { } + , fWidth(width) + , fHeight(height) + , fConfig(config) { } - virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) SK_OVERRIDE { - if ((SkImageInfoToBitmapConfig(fInfo) != bm->config()) - || (bm->width() != fInfo.fWidth) - || (bm->height() != fInfo.fHeight)) { - return false; + bool isReady() { return (fTarget != NULL); } + + virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) { + if ((NULL == fTarget) + || (fConfig != bm->config()) + || (fWidth != bm->width()) + || (fHeight != bm->height()) + || (ct != NULL)) { + // Call default allocator. + return bm->allocPixels(NULL, ct); } - bm->setConfig(bm->config(), bm->width(), bm->height(), - fRowBytes, bm->alphaType()); - bm->setPixels(fTarget, ct); + // make sure fRowBytes is correct. + bm->setConfig(fConfig, fWidth, fHeight, fRowBytes, bm->alphaType()); + // TODO(halcanary): verify that all callers of this function + // will respect new RowBytes. Will be moot once rowbytes belongs + // to PixelRef. + bm->setPixels(fTarget, NULL); + fTarget = NULL; // never alloc same pixels twice! return true; } private: - void* fTarget; - size_t fRowBytes; - SkImageInfo fInfo; + void* fTarget; // Block of memory to be supplied as pixel memory + // in allocPixelRef. Must be large enough to hold + // a bitmap described by fWidth, fHeight, and + // fRowBytes. + size_t fRowBytes; // rowbytes for the destination bitmap + int fWidth; // Along with fHeight and fConfig, the information + int fHeight; // about the bitmap whose pixels this allocator is + // expected to allocate. If they do not match the + // bitmap passed to allocPixelRef, it is assumed + // that the bitmap will be copied to a bitmap with + // the correct info using this allocator, so the + // default allocator will be used instead of + // fTarget. + SkBitmap::Config fConfig; typedef SkBitmap::Allocator INHERITED; }; -} // namespace -//////////////////////////////////////////////////////////////////////////////// - -SkDecodingImageGenerator::SkDecodingImageGenerator(SkData* data) - : fData(data) - , fHasInfo(false) - , fDoCopyTo(false) { - SkASSERT(fData != NULL); - fStream = SkNEW_ARGS(SkMemoryStream, (fData)); - SkASSERT(fStream != NULL); - SkASSERT(fStream->unique()); - fData->ref(); -} - -SkDecodingImageGenerator::SkDecodingImageGenerator(SkStreamRewindable* stream) - : fData(NULL) - , fStream(stream) - , fHasInfo(false) - , fDoCopyTo(false) { - SkASSERT(fStream != NULL); - SkASSERT(fStream->unique()); -} - -SkDecodingImageGenerator::~SkDecodingImageGenerator() { - SkSafeUnref(fData); - fStream->unref(); -} // TODO(halcanary): Give this macro a better name and move it into SkTypes.h #ifdef SK_DEBUG @@ -78,8 +78,49 @@ SkDecodingImageGenerator::~SkDecodingImageGenerator() { #define SkCheckResult(expr, value) (void)(expr) #endif +#ifdef SK_DEBUG +inline bool check_alpha(SkAlphaType reported, SkAlphaType actual) { + return ((reported == actual) + || ((reported == kPremul_SkAlphaType) + && (actual == kOpaque_SkAlphaType))); +} +#endif // SK_DEBUG + +} // namespace +//////////////////////////////////////////////////////////////////////////////// + +SkDecodingImageGenerator::SkDecodingImageGenerator( + SkData* data, + SkStreamRewindable* stream, + const SkImageInfo& info, + int sampleSize, + bool ditherImage, + SkBitmap::Config requestedConfig) + : fData(data) + , fStream(stream) + , fInfo(info) + , fSampleSize(sampleSize) + , fDitherImage(ditherImage) + , fRequestedConfig(requestedConfig) { + SkASSERT(stream != NULL); + SkSafeRef(fData); // may be NULL. +} + +SkDecodingImageGenerator::~SkDecodingImageGenerator() { + SkSafeUnref(fData); + fStream->unref(); +} + +bool SkDecodingImageGenerator::getInfo(SkImageInfo* info) { + if (info != NULL) { + *info = fInfo; + } + return true; +} + SkData* SkDecodingImageGenerator::refEncodedData() { // This functionality is used in `gm --serialize` + // Does not encode options. if (fData != NULL) { return SkSafeRef(fData); } @@ -98,111 +139,149 @@ SkData* SkDecodingImageGenerator::refEncodedData() { return SkSafeRef(fData); } -bool SkDecodingImageGenerator::getInfo(SkImageInfo* info) { - // info can be NULL. If so, will update fInfo, fDoCopyTo, and fHasInfo. - if (fHasInfo) { - if (info != NULL) { - *info = fInfo; - } - return true; - } - SkAssertResult(fStream->rewind()); - SkAutoTDelete decoder(SkImageDecoder::Factory(fStream)); - if (NULL == decoder.get()) { - return false; - } - SkBitmap bitmap; - if (!decoder->decode(fStream, &bitmap, - SkImageDecoder::kDecodeBounds_Mode)) { - return false; - } - if (bitmap.config() == SkBitmap::kNo_Config) { - return false; - } - if (!bitmap.asImageInfo(&fInfo)) { - // We can't use bitmap.config() as is. - if (!bitmap.canCopyTo(SkBitmap::kARGB_8888_Config)) { - SkDEBUGFAIL("!bitmap->canCopyTo(SkBitmap::kARGB_8888_Config)"); - return false; - } - fDoCopyTo = true; - fInfo.fWidth = bitmap.width(); - fInfo.fHeight = bitmap.height(); - fInfo.fColorType = kPMColor_SkColorType; - fInfo.fAlphaType = bitmap.alphaType(); - } - if (info != NULL) { - *info = fInfo; - } - fHasInfo = true; - return true; -} - bool SkDecodingImageGenerator::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { if (NULL == pixels) { return false; } - if (!this->getInfo(NULL)) { + if (fInfo != info) { + // The caller has specified a different info. This is an + // error for this kind of SkImageGenerator. Use the Options + // to change the settings. return false; } - if (SkImageInfoToBitmapConfig(info) == SkBitmap::kNo_Config) { - return false; // Unsupported SkColorType. + int bpp = SkBitmap::ComputeBytesPerPixel(fRequestedConfig); + if (static_cast(bpp * info.fWidth) > rowBytes) { + // The caller has specified a bad rowBytes. + return false; } + SkAssertResult(fStream->rewind()); SkAutoTDelete decoder(SkImageDecoder::Factory(fStream)); if (NULL == decoder.get()) { return false; } - if (fInfo != info) { - // The caller has specified a different info. For now, this - // is an error. In the future, we will check to see if we can - // convert. - return false; - } - int bpp = SkBitmap::ComputeBytesPerPixel(SkImageInfoToBitmapConfig(info)); - if (static_cast(bpp * info.fWidth) > rowBytes) { - return false; - } - SkBitmap bitmap; - if (!bitmap.setConfig(info, rowBytes)) { - return false; - } + decoder->setDitherImage(fDitherImage); + decoder->setSampleSize(fSampleSize); - TargetAllocator allocator(pixels, rowBytes, info); - if (!fDoCopyTo) { - decoder->setAllocator(&allocator); - } - bool success = decoder->decode(fStream, &bitmap, + SkBitmap bitmap; + TargetAllocator allocator(pixels, rowBytes, info.fWidth, + info.fHeight, fRequestedConfig); + decoder->setAllocator(&allocator); + bool success = decoder->decode(fStream, &bitmap, fRequestedConfig, SkImageDecoder::kDecodePixels_Mode); decoder->setAllocator(NULL); if (!success) { return false; } - if (fDoCopyTo) { - SkBitmap bm8888; - bitmap.copyTo(&bm8888, SkBitmap::kARGB_8888_Config, &allocator); + if (allocator.isReady()) { // Did not use pixels! + SkBitmap bm; + SkASSERT(bitmap.canCopyTo(fRequestedConfig)); + if (!bitmap.copyTo(&bm, fRequestedConfig, &allocator) + || allocator.isReady()) { + SkDEBUGFAIL("bitmap.copyTo(requestedConfig) failed."); + // Earlier we checked canCopyto(); we expect consistency. + return false; + } + SkASSERT(check_alpha(fInfo.fAlphaType, bm.alphaType())); + } else { + SkASSERT(check_alpha(fInfo.fAlphaType, bitmap.alphaType())); } return true; } -bool SkDecodingImageGenerator::Install(SkData* data, SkBitmap* dst, - SkDiscardableMemory::Factory* factory) { + +SkImageGenerator* SkDecodingImageGenerator::Create( + SkData* data, + const SkDecodingImageGenerator::Options& opts) { SkASSERT(data != NULL); - SkASSERT(dst != NULL); - SkImageGenerator* gen(SkNEW_ARGS(SkDecodingImageGenerator, (data))); - return SkInstallDiscardablePixelRef(gen, dst, factory); + if (NULL == data) { + return NULL; + } + SkStreamRewindable* stream = SkNEW_ARGS(SkMemoryStream, (data)); + SkASSERT(stream != NULL); + SkASSERT(stream->unique()); + return SkDecodingImageGenerator::Create(data, stream, opts); } -bool SkDecodingImageGenerator::Install(SkStreamRewindable* stream, - SkBitmap* dst, - SkDiscardableMemory::Factory* factory) { +SkImageGenerator* SkDecodingImageGenerator::Create( + SkStreamRewindable* stream, + const SkDecodingImageGenerator::Options& opts) { SkASSERT(stream != NULL); - SkASSERT(dst != NULL); + SkASSERT(stream->unique()); if ((stream == NULL) || !stream->unique()) { SkSafeUnref(stream); - return false; + return NULL; } - SkImageGenerator* gen(SkNEW_ARGS(SkDecodingImageGenerator, (stream))); - return SkInstallDiscardablePixelRef(gen, dst, factory); + return SkDecodingImageGenerator::Create(NULL, stream, opts); +} + +// A contructor-type function that returns NULL on failure. This +// prevents the returned SkImageGenerator from ever being in a bad +// state. Called by both Create() functions +SkImageGenerator* SkDecodingImageGenerator::Create( + SkData* data, + SkStreamRewindable* stream, + const SkDecodingImageGenerator::Options& opts) { + SkASSERT(stream); + SkAutoTUnref autoStream(stream); // always unref this. + if (opts.fUseRequestedColorType && + (kIndex_8_SkColorType == opts.fRequestedColorType)) { + // We do not support indexed color with SkImageGenerators, + return NULL; + } + SkAssertResult(autoStream->rewind()); + SkAutoTDelete decoder(SkImageDecoder::Factory(autoStream)); + if (NULL == decoder.get()) { + return NULL; + } + SkBitmap bitmap; + decoder->setSampleSize(opts.fSampleSize); + if (!decoder->decode(stream, &bitmap, + SkImageDecoder::kDecodeBounds_Mode)) { + return NULL; + } + if (bitmap.config() == SkBitmap::kNo_Config) { + return NULL; + } + + SkImageInfo info; + SkBitmap::Config config; + + if (!opts.fUseRequestedColorType) { + // Use default config. + if (SkBitmap::kIndex8_Config == bitmap.config()) { + // We don't support kIndex8 because we don't support + // colortables in this workflow. + config = SkBitmap::kARGB_8888_Config; + info.fWidth = bitmap.width(); + info.fHeight = bitmap.height(); + info.fColorType = kPMColor_SkColorType; + info.fAlphaType = bitmap.alphaType(); + } else { + config = bitmap.config(); // Save for later! + if (!bitmap.asImageInfo(&info)) { + SkDEBUGFAIL("Getting SkImageInfo from bitmap failed."); + return NULL; + } + } + } else { + config = SkColorTypeToBitmapConfig(opts.fRequestedColorType); + if (!bitmap.canCopyTo(config)) { + SkASSERT(bitmap.config() != config); + return NULL; // Can not translate to needed config. + } + info.fWidth = bitmap.width(); + info.fHeight = bitmap.height(); + info.fColorType = opts.fRequestedColorType; + info.fAlphaType = bitmap.alphaType(); + + // Sanity check. + SkDEBUGCODE(SkColorType tmp;) + SkASSERT(SkBitmapConfigToColorType(config, &tmp)); + SkASSERT(tmp == opts.fRequestedColorType); + } + return SkNEW_ARGS(SkDecodingImageGenerator, + (data, autoStream.detach(), info, + opts.fSampleSize, opts.fDitherImage, config)); } diff --git a/src/images/SkDecodingImageGenerator.h b/src/images/SkDecodingImageGenerator.h index dba234bcf1..12a49d59c4 100644 --- a/src/images/SkDecodingImageGenerator.h +++ b/src/images/SkDecodingImageGenerator.h @@ -8,113 +8,134 @@ #ifndef SkDecodingImageGenerator_DEFINED #define SkDecodingImageGenerator_DEFINED -#include "SkDiscardableMemory.h" +#include "SkBitmap.h" #include "SkImageGenerator.h" -#include "SkImageInfo.h" -class SkBitmap; +class SkData; class SkStreamRewindable; /** - * Calls into SkImageDecoder::DecodeMemoryToTarget to implement a - * SkImageGenerator + * An implementation of SkImageGenerator that calls into + * SkImageDecoder. */ class SkDecodingImageGenerator : public SkImageGenerator { public: - /* - * The constructor will take a reference to the SkData. The - * destructor will unref() it. - */ - explicit SkDecodingImageGenerator(SkData* data); - - /* - * The SkData version of this constructor is preferred. If the - * stream has an underlying SkData (such as a SkMemoryStream) - * pass that in. + virtual ~SkDecodingImageGenerator(); + virtual SkData* refEncodedData() SK_OVERRIDE; + // This implementaion of getInfo() always returns true. + virtual bool getInfo(SkImageInfo* info) SK_OVERRIDE; + virtual bool getPixels(const SkImageInfo& info, + void* pixels, + size_t rowBytes) SK_OVERRIDE; + /** + * These options will be passed on to the image decoder. The + * defaults are sensible. * - * This object will unref the stream when done. Since streams - * have internal state (position), the caller should not pass a - * shared stream in. Pass either a new duplicated stream in or - * transfer ownership of the stream. In the latter case, be sure - * that there are no other consumers of the stream who will - * modify the stream's position. This constructor asserts + * @param fSampleSize If set to > 1, tells the decoder to return a + * smaller than original bitmap, sampling 1 pixel for + * every size pixels. e.g. if sample size is set to 3, + * then the returned bitmap will be 1/3 as wide and high, + * and will contain 1/9 as many pixels as the original. + * Note: this is a hint, and the codec may choose to + * ignore this, or only approximate the sample size. + * + * @param fDitherImage Set to true if the the decoder should try to + * dither the resulting image when decoding to a smaller + * color-space. The default is true. + * + * @param fRequestedColorType If not given, then use whichever + * config the decoder wants. Else try to use this color + * type. If the decoder won't support this color type, + * SkDecodingImageGenerator::Create will return + * NULL. kIndex_8_SkColorType is not supported. + */ + struct Options { + Options() + : fSampleSize(1) + , fDitherImage(true) + , fUseRequestedColorType(false) + , fRequestedColorType() { } + Options(int sampleSize, bool dither) + : fSampleSize(sampleSize) + , fDitherImage(dither) + , fUseRequestedColorType(false) + , fRequestedColorType() { } + Options(int sampleSize, bool dither, SkColorType colorType) + : fSampleSize(sampleSize) + , fDitherImage(dither) + , fUseRequestedColorType(true) + , fRequestedColorType(colorType) { } + const int fSampleSize; + const bool fDitherImage; + const bool fUseRequestedColorType; + const SkColorType fRequestedColorType; + }; + + /** + * These two functions return a SkImageGenerator that calls into + * SkImageDecoder. They return NULL on failure. + * + * The SkData version of this function is preferred. If the stream + * has an underlying SkData (such as a SkMemoryStream) pass that in. + * + * This object will unref the stream when done or on failure. Since + * streams have internal state (position), the caller should not pass + * a shared stream in. Pass either a new duplicated stream in or + * transfer ownership of the stream. This factory asserts * stream->unique(). * * For example: * SkStreamRewindable* stream; * ... * SkImageGenerator* gen - * = SkNEW_ARGS(SkDecodingImageGenerator, - * (stream->duplicate())); + * = SkDecodingImageGenerator::Create( + * stream->duplicate(), SkDecodingImageGenerator::Options()); * ... * SkDELETE(gen); + * + * @param Options (see above) + * + * @return NULL on failure, a new SkImageGenerator on success. */ - explicit SkDecodingImageGenerator(SkStreamRewindable* stream); - - virtual ~SkDecodingImageGenerator(); - - virtual SkData* refEncodedData() SK_OVERRIDE; - - virtual bool getInfo(SkImageInfo* info) SK_OVERRIDE; - - virtual bool getPixels(const SkImageInfo& info, - void* pixels, - size_t rowBytes) SK_OVERRIDE; + static SkImageGenerator* Create(SkStreamRewindable* stream, + const Options& opt); /** - * Install the SkData into the destination bitmap, using a new - * SkDiscardablePixelRef and a new SkDecodingImageGenerator. - * - * @param data Contains the encoded image data that will be used - * by the SkDecodingImageGenerator. Will be ref()ed. - * - * @param destination Upon success, this bitmap will be - * configured and have a pixelref installed. - * - * @param factory If not NULL, this object will be used as a - * source of discardable memory when decoding. If NULL, then - * SkDiscardableMemory::Create() will be called. - * - * @return true iff successful. + * @param data Contains the encoded image data that will be used by + * the SkDecodingImageGenerator. Will be ref()ed by the + * SkImageGenerator constructor and and unref()ed on deletion. */ - static bool Install(SkData* data, SkBitmap* destination, - SkDiscardableMemory::Factory* factory = NULL); - /** - * Install the stream into the destination bitmap, using a new - * SkDiscardablePixelRef and a new SkDecodingImageGenerator. - * - * The SkData version of this function is preferred. If the - * stream has an underlying SkData (such as a SkMemoryStream) - * pass that in. - * - * @param stream The source of encoded data that will be passed - * to the decoder. The installed SkDecodingImageGenerator will - * unref the stream when done. If false is returned, this - * function will perform the unref. Since streams have internal - * state (position), the caller should not pass a shared stream - * in. Pass either a new duplicated stream in or transfer - * ownership of the stream. In the latter case, be sure that - * there are no other consumers of the stream who will modify the - * stream's position. This function will fail if - * (!stream->unique()). - * - * @param destination Upon success, this bitmap will be - * configured and have a pixelref installed. - * - * @param factory If not NULL, this object will be used as a - * source of discardable memory when decoding. If NULL, then - * SkDiscardableMemory::Create() will be called. - * - * @return true iff successful. - */ - static bool Install(SkStreamRewindable* stream, SkBitmap* destination, - SkDiscardableMemory::Factory* factory = NULL); + static SkImageGenerator* Create(SkData* data, const Options& opt); private: - SkData* fData; - SkStreamRewindable* fStream; - SkImageInfo fInfo; - bool fHasInfo; - bool fDoCopyTo; + SkData* fData; + SkStreamRewindable* fStream; + const SkImageInfo fInfo; + const int fSampleSize; + const bool fDitherImage; + const SkBitmap::Config fRequestedConfig; + SkDecodingImageGenerator(SkData* data, + SkStreamRewindable* stream, + const SkImageInfo& info, + int sampleSize, + bool ditherImage, + SkBitmap::Config requestedConfig); + static SkImageGenerator* Create(SkData*, SkStreamRewindable*, + const Options&); + typedef SkImageGenerator INHERITED; }; + +// // Example of most basic use case: +// +// bool install_data(SkData* data, SkBitmap* dst) { +// return SkInstallDiscardablePixelRef( +// SkDecodingImageGenerator::Create( +// data, SkDecodingImageGenerator::Options()), dst, NULL); +// } +// bool install_stream(SkStreamRewindable* stream, SkBitmap* dst) { +// return SkInstallDiscardablePixelRef( +// SkDecodingImageGenerator::Create( +// stream, SkDecodingImageGenerator::Options()), dst, NULL); +// } + #endif // SkDecodingImageGenerator_DEFINED diff --git a/src/lazy/SkCachingPixelRef.cpp b/src/lazy/SkCachingPixelRef.cpp index 668f57ef30..fb30d051e1 100644 --- a/src/lazy/SkCachingPixelRef.cpp +++ b/src/lazy/SkCachingPixelRef.cpp @@ -12,7 +12,6 @@ bool SkCachingPixelRef::Install(SkImageGenerator* generator, SkBitmap* dst) { SkImageInfo info; - SkASSERT(generator != NULL); SkASSERT(dst != NULL); if ((NULL == generator) || !(generator->getInfo(&info)) diff --git a/src/lazy/SkDiscardablePixelRef.cpp b/src/lazy/SkDiscardablePixelRef.cpp index 160ca5b4fb..2886156102 100644 --- a/src/lazy/SkDiscardablePixelRef.cpp +++ b/src/lazy/SkDiscardablePixelRef.cpp @@ -72,20 +72,19 @@ bool SkInstallDiscardablePixelRef(SkImageGenerator* generator, SkBitmap* dst, SkDiscardableMemory::Factory* factory) { SkImageInfo info; - SkASSERT(generator != NULL); - if ((NULL == generator) - || (!generator->getInfo(&info)) + SkAutoTDelete autoGenerator(generator); + if ((NULL == autoGenerator.get()) + || (!autoGenerator->getInfo(&info)) || (!dst->setConfig(info, 0))) { - SkDELETE(generator); return false; } SkASSERT(dst->config() != SkBitmap::kNo_Config); - if (dst->empty()) { // Use a normal pixelref. - SkDELETE(generator); // Do not need this anymore. + if (dst->empty()) { // Use a normal pixelref. return dst->allocPixels(NULL, NULL); } - SkAutoTUnref ref(SkNEW_ARGS(SkDiscardablePixelRef, - (info, generator, dst->rowBytes(), factory))); + SkAutoTUnref ref( + SkNEW_ARGS(SkDiscardablePixelRef, + (info, autoGenerator.detach(), dst->rowBytes(), factory))); dst->setPixelRef(ref); return true; } diff --git a/src/lazy/SkDiscardablePixelRef.h b/src/lazy/SkDiscardablePixelRef.h index 4b6693837d..3367096c26 100644 --- a/src/lazy/SkDiscardablePixelRef.h +++ b/src/lazy/SkDiscardablePixelRef.h @@ -13,6 +13,15 @@ #include "SkImageInfo.h" #include "SkPixelRef.h" +/** + * A PixelRef backed by SkDiscardableMemory, with the ability to + * re-generate the pixels (via a SkImageGenerator) if the DM is + * purged. + * + * Since SkColorTable is reference-counted, we do not support indexed + * color with this class; there would be no way for the discardable + * memory system to unref the color table. + */ class SkDiscardablePixelRef : public SkPixelRef { public: SK_DECLARE_UNFLATTENABLE_OBJECT() diff --git a/tests/CachedDecodingPixelRefTest.cpp b/tests/CachedDecodingPixelRefTest.cpp index 04eddd9070..434724d0cf 100644 --- a/tests/CachedDecodingPixelRefTest.cpp +++ b/tests/CachedDecodingPixelRefTest.cpp @@ -146,11 +146,14 @@ static void test_three_encodings(skiatest::Reporter* reporter, //////////////////////////////////////////////////////////////////////////////// static bool install_skCachingPixelRef(SkData* encoded, SkBitmap* dst) { return SkCachingPixelRef::Install( - SkNEW_ARGS(SkDecodingImageGenerator, (encoded)), dst); + SkDecodingImageGenerator::Create( + encoded, SkDecodingImageGenerator::Options()), dst); } static bool install_skDiscardablePixelRef(SkData* encoded, SkBitmap* dst) { // Use system-default discardable memory. - return SkDecodingImageGenerator::Install(encoded, dst, NULL); + return SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create( + encoded, SkDecodingImageGenerator::Options()), dst, NULL); } //////////////////////////////////////////////////////////////////////////////// @@ -213,10 +216,12 @@ public: } return true; } + private: const TestType fType; skiatest::Reporter* const fReporter; }; + void CheckTestImageGeneratorBitmap(skiatest::Reporter* reporter, const SkBitmap& bm) { REPORTER_ASSERT(reporter, TestImageGenerator::Width() == bm.width()); diff --git a/tests/ImageDecodingTest.cpp b/tests/ImageDecodingTest.cpp index f840129b1c..307f055905 100644 --- a/tests/ImageDecodingTest.cpp +++ b/tests/ImageDecodingTest.cpp @@ -12,10 +12,14 @@ #include "SkColor.h" #include "SkColorPriv.h" #include "SkData.h" +#include "SkDecodingImageGenerator.h" +#include "SkDiscardableMemoryPool.h" #include "SkForceLinking.h" #include "SkGradientShader.h" #include "SkImageDecoder.h" #include "SkImageEncoder.h" +#include "SkImageGenerator.h" +#include "SkImagePriv.h" #include "SkOSFile.h" #include "SkPoint.h" #include "SkShader.h" @@ -152,7 +156,7 @@ static void test_unpremul(skiatest::Reporter* reporter) { if (iter.next(&basename)) { do { SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str()); - //SkDebugf("about to decode \"%s\"\n", filename.c_str()); + // SkDebugf("about to decode \"%s\"\n", filename.c_str()); compare_unpremul(reporter, filename); } while (iter.next(&basename)); } else { @@ -200,7 +204,7 @@ static void test_stream_life() { SkImageEncoder::kWEBP_Type, }; for (size_t i = 0; i < SK_ARRAY_COUNT(gTypes); ++i) { - //SkDebugf("encoding to %i\n", i); + // SkDebugf("encoding to %i\n", i); SkAutoTUnref stream(create_image_stream(gTypes[i])); if (NULL == stream.get()) { SkDebugf("no stream\n"); @@ -227,7 +231,7 @@ static void test_stream_life() { // Test inside SkScaledBitmapSampler.cpp extern void test_row_proc_choice(); -#endif // SK_DEBUG +#endif // SK_DEBUG DEF_TEST(ImageDecoding, reporter) { test_unpremul(reporter); @@ -236,3 +240,228 @@ DEF_TEST(ImageDecoding, reporter) { test_row_proc_choice(); #endif } + +//////////////////////////////////////////////////////////////////////////////// + +// example of how Android will do this inside their BitmapFactory +static SkPixelRef* install_pixel_ref(SkBitmap* bitmap, + SkStreamRewindable* stream, + int sampleSize, bool ditherImage) { + SkASSERT(bitmap != NULL); + SkASSERT(stream != NULL); + SkASSERT(stream->rewind()); + SkASSERT(stream->unique()); + SkColorType colorType; + if (!SkBitmapConfigToColorType(bitmap->config(), &colorType)) { + return NULL; + } + SkDecodingImageGenerator::Options opts(sampleSize, ditherImage, colorType); + SkAutoTDelete gen( + SkDecodingImageGenerator::Create(stream, opts)); + SkImageInfo info; + if ((NULL == gen.get()) || !gen->getInfo(&info)) { + return NULL; + } + SkDiscardableMemory::Factory* factory = NULL; + if (info.getSafeSize(info.minRowBytes()) < (32 * 1024)) { + // only use ashmem for large images, since mmaps come at a price + factory = SkGetGlobalDiscardableMemoryPool(); + } + if (SkInstallDiscardablePixelRef(gen.detach(), bitmap, factory)) { + return bitmap->pixelRef(); + } + return NULL; +} +/** + * A test for the SkDecodingImageGenerator::Create and + * SkInstallDiscardablePixelRef functions. + */ +DEF_TEST(ImprovedBitmapFactory, reporter) { + SkString resourcePath = skiatest::Test::GetResourcePath(); + SkString directory = SkOSPath::SkPathJoin(resourcePath.c_str(), "encoding"); + SkString path = SkOSPath::SkPathJoin(directory.c_str(), "randPixels.png"); + SkAutoTUnref stream( + SkStream::NewFromFile(path.c_str())); + if (sk_exists(path.c_str())) { + SkBitmap bm; + SkAssertResult(bm.setConfig(SkBitmap::kARGB_8888_Config, 1, 1)); + REPORTER_ASSERT(reporter, + NULL != install_pixel_ref(&bm, stream.detach(), 1, true)); + SkAutoLockPixels alp(bm); + REPORTER_ASSERT(reporter, NULL != bm.getPixels()); + } +} + + +//////////////////////////////////////////////////////////////////////////////// + +#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) +static inline bool check_rounding(int value, int dividend, int divisor) { + // returns true if (dividend/divisor) rounds up OR down to value + return (((divisor * value) > (dividend - divisor)) + && ((divisor * value) < (dividend + divisor))); +} +#endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX +namespace { +// expected output for 8x8 bitmap +const int kExpectedWidth = 8; +const int kExpectedHeight = 8; +const SkColor kExpectedPixels[] = { + 0xffbba570, 0xff395f5d, 0xffe25c39, 0xff197666, + 0xff3cba27, 0xffdefcb0, 0xffc13874, 0xfffa0093, + 0xffbda60e, 0xffc01db6, 0xff2bd688, 0xff9362d4, + 0xffc641b2, 0xffa5cede, 0xff606eba, 0xff8f4bf3, + 0xff3bf742, 0xff8f02a8, 0xff5509df, 0xffc7027e, + 0xff24aa8a, 0xff886c96, 0xff625481, 0xff403689, + 0xffc52152, 0xff78ccd6, 0xffdcb4ab, 0xff09d27d, + 0xffca00f3, 0xff605d47, 0xff446fb2, 0xff576e46, + 0xff273df9, 0xffb41a83, 0xfff812c3, 0xffccab67, + 0xff034218, 0xff7db9a7, 0xff821048, 0xfffe4ab4, + 0xff6fac98, 0xff941d27, 0xff5fe411, 0xfffbb283, + 0xffd86e99, 0xff169162, 0xff71128c, 0xff39cab4, + 0xffa7fe63, 0xff4c956b, 0xffbc22e0, 0xffb272e4, + 0xff129f4a, 0xffe34513, 0xff3d3742, 0xffbd190a, + 0xffb07222, 0xff2e23f8, 0xfff089d9, 0xffb35738, + 0xffa86022, 0xff3340fe, 0xff95fe71, 0xff6a71df +}; +SK_COMPILE_ASSERT((kExpectedWidth * kExpectedHeight) + == SK_ARRAY_COUNT(kExpectedPixels), array_size_mismatch); +} // namespace + +/** + * Given either a SkStream or a SkData, try to decode the encoded + * image using the specified options and report errors. + */ +static void test_options(skiatest::Reporter* reporter, + const SkDecodingImageGenerator::Options& opts, + SkStreamRewindable* encodedStream, + SkData* encodedData, + bool useData, + const SkString& path) { + SkBitmap bm; + bool success = false; + if (useData) { + if (NULL == encodedData) { + return; + } + success = SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create(encodedData, opts), &bm, NULL); + } else { + if (NULL == encodedStream) { + return; + } + success = SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts), + &bm, NULL); + } + + REPORTER_ASSERT(reporter, success + || (kARGB_4444_SkColorType == opts.fRequestedColorType)); + if (!success) { + return; + } + #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) + // Android is the only system that use Skia's image decoders in + // production. For now, we'll only verify that samplesize works + // on systems where it already is known to work. + REPORTER_ASSERT(reporter, check_rounding(bm.height(), kExpectedHeight, + opts.fSampleSize)); + REPORTER_ASSERT(reporter, check_rounding(bm.width(), kExpectedWidth, + opts.fSampleSize)); + #endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX + SkAutoLockPixels alp(bm); + REPORTER_ASSERT(reporter, bm.getPixels() != NULL); + + SkBitmap::Config requestedConfig + = SkColorTypeToBitmapConfig(opts.fRequestedColorType); + REPORTER_ASSERT(reporter, + (!opts.fUseRequestedColorType) + || (bm.config() == requestedConfig)); + + // Condition under which we should check the decoding results: + if ((SkBitmap::kARGB_8888_Config == bm.config()) + && (NULL != bm.getPixels()) + && (!path.endsWith(".jpg")) // lossy + && (!path.endsWith(".webp")) // decoder error + && (opts.fSampleSize == 1)) { // scaled + bool pixelError = false; + const SkColor* correctPixels = kExpectedPixels; + SkASSERT(bm.height() == kExpectedHeight); + SkASSERT(bm.width() == kExpectedWidth); + for (int y = 0; y < bm.height(); ++y) { + for (int x = 0; x < bm.width(); ++x) { + pixelError |= (*correctPixels != bm.getColor(x, y)); + ++correctPixels; + } + } + REPORTER_ASSERT(reporter, !pixelError); + } +} + +/** + * SkDecodingImageGenerator has an Options struct which lets the + * client of the generator set sample size, dithering, and bitmap + * config. This test loops through many possible options and tries + * them on a set of 5 small encoded images (each in a different + * format). We test both SkData and SkStreamRewindable decoding. + */ +DEF_TEST(ImageDecoderOptions, reporter) { + const char* files[] = { + "randPixels.bmp", + "randPixels.jpg", + "randPixels.png", + "randPixels.webp", + "randPixels.gif" + }; + + SkString resourceDir = skiatest::Test::GetResourcePath(); + SkString directory = SkOSPath::SkPathJoin(resourceDir.c_str(), "encoding"); + if (!sk_exists(directory.c_str())) { + return; + } + + int scaleList[] = {1, 2, 3, 4}; + bool ditherList[] = {true, false}; + SkColorType colorList[] = { + kAlpha_8_SkColorType, + kRGB_565_SkColorType, + kARGB_4444_SkColorType, // Most decoders will fail on 4444. + kPMColor_SkColorType + // Note that indexed color is left out of the list. Lazy + // decoding doesn't do indexed color. + }; + const bool useDataList[] = {true, false}; + + for (size_t fidx = 0; fidx < SK_ARRAY_COUNT(files); ++fidx) { + SkString path = SkOSPath::SkPathJoin(directory.c_str(), files[fidx]); + if (!sk_exists(path.c_str())) { + continue; + } + + SkAutoDataUnref encodedData(SkData::NewFromFileName(path.c_str())); + REPORTER_ASSERT(reporter, encodedData.get() != NULL); + SkAutoTUnref encodedStream( + SkStream::NewFromFile(path.c_str())); + REPORTER_ASSERT(reporter, encodedStream.get() != NULL); + + for (size_t i = 0; i < SK_ARRAY_COUNT(scaleList); ++i) { + for (size_t j = 0; j < SK_ARRAY_COUNT(ditherList); ++j) { + for (size_t m = 0; m < SK_ARRAY_COUNT(useDataList); ++m) { + for (size_t k = 0; k < SK_ARRAY_COUNT(colorList); ++k) { + SkDecodingImageGenerator::Options opts(scaleList[i], + ditherList[j], + colorList[k]); + test_options(reporter, opts, encodedStream, encodedData, + useDataList[m], path); + + } + SkDecodingImageGenerator::Options options(scaleList[i], + ditherList[j]); + test_options(reporter, options, encodedStream, encodedData, + useDataList[m], path); + } + } + } + } +} + diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp index 447ce4eb4d..b90cc030d7 100644 --- a/tests/PictureTest.cpp +++ b/tests/PictureTest.cpp @@ -13,6 +13,8 @@ #include "SkData.h" #include "SkDecodingImageGenerator.h" #include "SkError.h" +#include "SkImageEncoder.h" +#include "SkImageGenerator.h" #include "SkPaint.h" #include "SkPicture.h" #include "SkPictureUtils.h" @@ -338,8 +340,6 @@ static void test_bad_bitmap() { } #endif -#include "SkImageEncoder.h" - static SkData* encode_bitmap_to_data(size_t* offset, const SkBitmap& bm) { *offset = 0; return SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, 100); @@ -381,7 +381,8 @@ static void test_bitmap_with_encoded_data(skiatest::Reporter* reporter) { SkAutoDataUnref data(wStream.copyToData()); SkBitmap bm; - bool installSuccess = SkDecodingImageGenerator::Install(data, &bm); + bool installSuccess = SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create(data, SkDecodingImageGenerator::Options()), &bm, NULL); REPORTER_ASSERT(reporter, installSuccess); // Write both bitmaps to pictures, and ensure that the resulting data streams are the same. diff --git a/tools/LazyDecodeBitmap.cpp b/tools/LazyDecodeBitmap.cpp index 9a4a36ffe8..83fca5f3b2 100644 --- a/tools/LazyDecodeBitmap.cpp +++ b/tools/LazyDecodeBitmap.cpp @@ -31,10 +31,11 @@ bool sk_tools::LazyDecodeBitmap(const void* src, return false; } - SkAutoTDelete gen(SkNEW_ARGS(SkDecodingImageGenerator, - (data))); + SkAutoTDelete gen( + SkDecodingImageGenerator::Create( + data, SkDecodingImageGenerator::Options())); SkImageInfo info; - if (!gen->getInfo(&info)) { + if ((NULL == gen.get()) || !gen->getInfo(&info)) { return false; } SkDiscardableMemory::Factory* pool = NULL;