diff options
-rw-r--r-- | bench/ImageDecodeBench.cpp | 91 | ||||
-rw-r--r-- | gm/gm.h | 4 | ||||
-rw-r--r-- | gyp/SampleApp.gyp | 1 | ||||
-rw-r--r-- | gyp/bench.gypi | 1 | ||||
-rw-r--r-- | gyp/tests.gyp | 1 | ||||
-rw-r--r-- | include/core/SkImageDecoder.h | 16 | ||||
-rw-r--r-- | samplecode/SampleColorFilter.cpp | 3 | ||||
-rw-r--r-- | samplecode/SampleUnpremul.cpp | 204 | ||||
-rw-r--r-- | src/images/SkImageDecoder.cpp | 3 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libpng.cpp | 47 | ||||
-rw-r--r-- | src/images/SkImageDecoder_libwebp.cpp | 35 | ||||
-rw-r--r-- | src/images/SkScaledBitmapSampler.cpp | 105 | ||||
-rw-r--r-- | src/images/SkScaledBitmapSampler.h | 2 | ||||
-rw-r--r-- | src/ports/SkImageDecoder_WIC.cpp | 9 | ||||
-rw-r--r-- | tests/ImageDecodingTest.cpp | 157 |
15 files changed, 627 insertions, 52 deletions
diff --git a/bench/ImageDecodeBench.cpp b/bench/ImageDecodeBench.cpp new file mode 100644 index 0000000000..1cb29b1f1e --- /dev/null +++ b/bench/ImageDecodeBench.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBenchmark.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkForceLinking.h" +#include "SkImageDecoder.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "SkString.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +class SkCanvas; + +class ImageDecodeBench : public SkBenchmark { +public: + ImageDecodeBench(void* p, const char* filename) + : INHERITED(p) + , fName("image_decode_") + , fFilename(filename) + , fStream() + , fValid(false) { + fName.append(SkOSPath::SkBasename(filename)); + fIsRendering = false; + } + +protected: + virtual const char* onGetName() SK_OVERRIDE { + return fName.c_str(); + } + + virtual void onPreDraw() SK_OVERRIDE { + SkFILEStream fileStream(fFilename.c_str()); + fValid = fileStream.isValid() && fileStream.getLength() > 0; + if (fValid) { + const size_t size = fileStream.getLength(); + void* data = sk_malloc_throw(size); + if (fileStream.read(data, size) < size) { + fValid = false; + } else { + SkAutoTUnref<SkData> skdata(SkData::NewFromMalloc(data, size)); + fStream.setData(skdata.get()); + } + } + } + + virtual void onDraw(SkCanvas*) SK_OVERRIDE { +#ifdef SK_DEBUG + if (!fValid) { + SkDebugf("stream was invalid: %s\n", fName.c_str()); + return; + } +#endif + // Decode a bunch of times + SkBitmap bm; + for (int i = 0; i < SkBENCHLOOP(1000); ++i) { + SkDEBUGCODE(bool success =) SkImageDecoder::DecodeStream(&fStream, &bm); +#ifdef SK_DEBUG + if (!success) { + SkDebugf("failed to decode %s\n", fName.c_str()); + return; + } +#endif + SkDEBUGCODE(success =) fStream.rewind(); +#ifdef SK_DEBUG + if (!success) { + SkDebugf("failed to rewind %s\n", fName.c_str()); + return; + } +#endif + } + } + +private: + SkString fName; + const SkString fFilename; + SkMemoryStream fStream; + bool fValid; + + typedef SkBenchmark INHERITED; +}; + +// These are files which call decodePalette +//DEF_BENCH( return SkNEW_ARGS(ImageDecodeBench, (p, "/usr/local/google/home/scroggo/Downloads/images/hal_163x90.png")); ) +//DEF_BENCH( return SkNEW_ARGS(ImageDecodeBench, (p, "/usr/local/google/home/scroggo/Downloads/images/box_19_top-left.png")); ) @@ -80,6 +80,10 @@ namespace skiagm { gResourcePath = resourcePath; } + static SkString& GetResourcePath() { + return gResourcePath; + } + bool isCanvasDeferred() const { return fCanvasIsDeferred; } void setCanvasIsDeferred(bool isDeferred) { fCanvasIsDeferred = isDeferred; diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp index 8b64cbdf6b..644121f967 100644 --- a/gyp/SampleApp.gyp +++ b/gyp/SampleApp.gyp @@ -109,6 +109,7 @@ '../samplecode/SampleTiling.cpp', '../samplecode/SampleTinyBitmap.cpp', '../samplecode/SampleUnitMapper.cpp', + '../samplecode/SampleUnpremul.cpp', '../samplecode/SampleVertices.cpp', '../samplecode/SampleXfermodesBlur.cpp', '../samplecode/TransitionView.cpp', diff --git a/gyp/bench.gypi b/gyp/bench.gypi index c7f0c11cb7..2be3f22d24 100644 --- a/gyp/bench.gypi +++ b/gyp/bench.gypi @@ -28,6 +28,7 @@ '../bench/GameBench.cpp', '../bench/GradientBench.cpp', '../bench/GrMemoryPoolBench.cpp', + '../bench/ImageDecodeBench.cpp', '../bench/InterpBench.cpp', '../bench/LineBench.cpp', '../bench/LightingBench.cpp', diff --git a/gyp/tests.gyp b/gyp/tests.gyp index 557fea5a28..3bdf18d4f8 100644 --- a/gyp/tests.gyp +++ b/gyp/tests.gyp @@ -66,6 +66,7 @@ '../tests/GrMemoryPoolTest.cpp', '../tests/GrSurfaceTest.cpp', '../tests/HashCacheTest.cpp', + '../tests/ImageDecodingTest.cpp', '../tests/InfRectTest.cpp', '../tests/LListTest.cpp', '../tests/LayerDrawLooperTest.cpp', diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h index 5ef569047b..360f03acb6 100644 --- a/include/core/SkImageDecoder.h +++ b/include/core/SkImageDecoder.h @@ -82,6 +82,21 @@ public: fPreferQualityOverSpeed = qualityOverSpeed; } + /** Set to true to require the decoder to return a bitmap with unpremultiplied + colors. The default is false, meaning the resulting bitmap will have its + colors premultiplied. + NOTE: Passing true to this function may result in a bitmap which cannot + be properly used by Skia. + */ + void setRequireUnpremultipliedColors(bool request) { + fRequireUnpremultipliedColors = request; + } + + /** Returns true if the decoder will only return bitmaps with unpremultiplied + colors. + */ + bool getRequireUnpremultipliedColors() const { return fRequireUnpremultipliedColors; } + /** \class Peeker Base class for optional callbacks to retrieve meta/chunk data out of @@ -433,6 +448,7 @@ private: bool fUsePrefTable; mutable bool fShouldCancelDecode; bool fPreferQualityOverSpeed; + bool fRequireUnpremultipliedColors; }; /** Calling newDecoder with a stream returns a new matching imagedecoder diff --git a/samplecode/SampleColorFilter.cpp b/samplecode/SampleColorFilter.cpp index 41392ac248..126ee4736d 100644 --- a/samplecode/SampleColorFilter.cpp +++ b/samplecode/SampleColorFilter.cpp @@ -84,7 +84,8 @@ static void test_5bits() { SkDebugf("--- trunc: %d %d round: %d %d new: %d %d\n", e0, ae0, e1, ae1, e2, ae2); } -static SkShader* createChecker() { +// No longer marked static, since it is externed in SampleUnpremul. +SkShader* createChecker() { SkBitmap bm; bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); bm.allocPixels(); diff --git a/samplecode/SampleUnpremul.cpp b/samplecode/SampleUnpremul.cpp new file mode 100644 index 0000000000..dfdd2accdb --- /dev/null +++ b/samplecode/SampleUnpremul.cpp @@ -0,0 +1,204 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "gm.h" +#include "SampleCode.h" +#include "SkBlurDrawLooper.h" +#include "SkCanvas.h" +#include "SkColorPriv.h" +#include "SkForceLinking.h" +#include "SkImageDecoder.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "SkString.h" +#include "SkSystemEventTypes.h" +#include "SkTypes.h" +#include "SkUtils.h" +#include "SkView.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +// Defined in SampleColorFilter.cpp +extern SkShader* createChecker(); + +/** + * Interprets c as an unpremultiplied color, and returns the + * premultiplied equivalent. + */ +static SkPMColor premultiply_unpmcolor(SkPMColor c) { + U8CPU a = SkGetPackedA32(c); + U8CPU r = SkGetPackedR32(c); + U8CPU g = SkGetPackedG32(c); + U8CPU b = SkGetPackedB32(c); + return SkPreMultiplyARGB(a, r, g, b); +} + +class UnpremulView : public SampleView { +public: + UnpremulView(SkString res) + : fResPath(res) + , fPremul(true) + , fDecodeSucceeded(false) { + this->nextImage(); + } + +protected: + // overrides from SkEventSink + virtual bool onQuery(SkEvent* evt) SK_OVERRIDE { + if (SampleCode::TitleQ(*evt)) { + SampleCode::TitleR(evt, "unpremul"); + return true; + } + SkUnichar uni; + if (SampleCode::CharQ(*evt, &uni)) { + char utf8[kMaxBytesInUTF8Sequence]; + size_t size = SkUTF8_FromUnichar(uni, utf8); + // Only consider events for single char keys + if (1 == size) { + switch (utf8[0]) { + case fNextImageChar: + this->nextImage(); + return true; + case fTogglePremulChar: + this->togglePremul(); + return true; + default: + break; + } + } + } + return this->INHERITED::onQuery(evt); + } + + virtual void onDrawBackground(SkCanvas* canvas) SK_OVERRIDE { + SkPaint paint; + SkAutoTUnref<SkShader> shader(createChecker()); + paint.setShader(shader.get()); + canvas->drawPaint(paint); + } + + virtual void onDrawContent(SkCanvas* canvas) SK_OVERRIDE { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(SkIntToScalar(24)); + SkAutoTUnref<SkBlurDrawLooper> looper(SkNEW_ARGS(SkBlurDrawLooper, + (SkIntToScalar(2), 0, 0, SK_ColorBLUE))); + paint.setLooper(looper); + SkScalar height = paint.getFontMetrics(NULL); + if (!fDecodeSucceeded) { + SkString failure; + if (fResPath.size() == 0) { + failure.printf("resource path is required!"); + } else { + failure.printf("Failed to decode %s", fCurrFile.c_str()); + } + canvas->drawText(failure.c_str(), failure.size(), 0, height, paint); + return; + } + + // Name, size of the file, and whether or not it is premultiplied. + SkString header(SkOSPath::SkBasename(fCurrFile.c_str())); + header.appendf(" [%dx%d] %s", fBitmap.width(), fBitmap.height(), + (fPremul ? "premultiplied" : "unpremultiplied")); + canvas->drawText(header.c_str(), header.size(), 0, height, paint); + canvas->translate(0, height); + + // Help messages + header.printf("Press '%c' to move to the next image.'", fNextImageChar); + canvas->drawText(header.c_str(), header.size(), 0, height, paint); + canvas->translate(0, height); + + header.printf("Press '%c' to toggle premultiplied decode.", fTogglePremulChar); + canvas->drawText(header.c_str(), header.size(), 0, height, paint); + + // Now draw the image itself. + canvas->translate(height * 2, height * 2); + if (!fPremul) { + // A premultiplied bitmap cannot currently be drawn. + SkAutoLockPixels alp(fBitmap); + // Copy it to a bitmap which can be drawn, converting + // to premultiplied: + SkBitmap bm; + bm.setConfig(SkBitmap::kARGB_8888_Config, fBitmap.width(), + fBitmap.height()); + SkASSERT(fBitmap.config() == SkBitmap::kARGB_8888_Config); + if (!bm.allocPixels()) { + SkString errMsg("allocPixels failed"); + canvas->drawText(errMsg.c_str(), errMsg.size(), 0, height, paint); + return; + } + for (int i = 0; i < fBitmap.width(); ++i) { + for (int j = 0; j < fBitmap.height(); ++j) { + *bm.getAddr32(i, j) = premultiply_unpmcolor(*fBitmap.getAddr32(i, j)); + } + } + canvas->drawBitmap(bm, 0, 0); + } else { + canvas->drawBitmap(fBitmap, 0, 0); + } + } + +private: + const SkString fResPath; + SkString fCurrFile; + bool fPremul; + bool fDecodeSucceeded; + SkBitmap fBitmap; + SkOSFile::Iter fFileIter; + + static const char fNextImageChar = 'j'; + static const char fTogglePremulChar = 'h'; + + void nextImage() { + if (fResPath.size() == 0) { + return; + } + SkString basename; + if (!fFileIter.next(&basename)) { + fFileIter.reset(fResPath.c_str()); + if (!fFileIter.next(&basename)) { + // Perhaps this should draw some error message? + return; + } + } + fCurrFile = SkOSPath::SkPathJoin(fResPath.c_str(), basename.c_str()); + this->decodeCurrFile(); + } + + void decodeCurrFile() { + if (fCurrFile.size() == 0) { + fDecodeSucceeded = false; + return; + } + SkFILEStream stream(fCurrFile.c_str()); + SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream)); + if (NULL == decoder.get()) { + fDecodeSucceeded = false; + return; + } + if (!fPremul) { + decoder->setRequireUnpremultipliedColors(true); + } + fDecodeSucceeded = decoder->decode(&stream, &fBitmap, + SkBitmap::kARGB_8888_Config, + SkImageDecoder::kDecodePixels_Mode); + this->inval(NULL); + } + + void togglePremul() { + fPremul = !fPremul; + this->decodeCurrFile(); + } + + typedef SampleView INHERITED; +}; + +////////////////////////////////////////////////////////////////////////////// + +static SkView* MyFactory() { + return new UnpremulView(skiagm::GM::GetResourcePath()); +} +static SkViewRegister reg(MyFactory); diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp index 5c078ce630..38aa7fda0d 100644 --- a/src/images/SkImageDecoder.cpp +++ b/src/images/SkImageDecoder.cpp @@ -40,7 +40,8 @@ SkImageDecoder::SkImageDecoder() , fDefaultPref(SkBitmap::kNo_Config) , fDitherImage(true) , fUsePrefTable(false) - , fPreferQualityOverSpeed(false) { + , fPreferQualityOverSpeed(false) + , fRequireUnpremultipliedColors(false) { } SkImageDecoder::~SkImageDecoder() { diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp index d967a69e84..729f87169a 100644 --- a/src/images/SkImageDecoder_libpng.cpp +++ b/src/images/SkImageDecoder_libpng.cpp @@ -80,8 +80,9 @@ private: SkPNGImageIndex* fImageIndex; bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp); - bool decodePalette(png_structp png_ptr, png_infop info_ptr, bool *hasAlphap, - bool *reallyHasAlphap, SkColorTable **colorTablep); + bool decodePalette(png_structp png_ptr, png_infop info_ptr, + bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap, + SkColorTable **colorTablep); bool getBitmapConfig(png_structp png_ptr, png_infop info_ptr, SkBitmap::Config *config, bool *hasAlpha, bool *doDither, SkPMColor *theTranspColor); @@ -311,7 +312,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, if (!reuseBitmap) { decodedBitmap->setConfig(config, sampler.scaledWidth(), - sampler.scaledHeight(), 0); + sampler.scaledHeight()); } if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; @@ -383,7 +384,8 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, upscale png's palette to a direct model */ SkAutoLockColors ctLock(colorTable); - if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors())) { + if (!sampler.begin(decodedBitmap, sc, doDither, ctLock.colors(), + this->getRequireUnpremultipliedColors())) { return false; } const int height = decodedBitmap->height(); @@ -435,6 +437,13 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, if (0 != theTranspColor) { reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor); } + if (reallyHasAlpha && this->getRequireUnpremultipliedColors() && + SkBitmap::kARGB_8888_Config != decodedBitmap->config()) { + // If the caller wants an unpremultiplied bitmap, and we let them get + // away with a config other than 8888, and it has alpha after all, + // return false, since the result will have premultiplied colors. + return false; + } decodedBitmap->setIsOpaque(!reallyHasAlpha); if (reuseBitmap) { decodedBitmap->notifyPixelsChanged(); @@ -445,7 +454,7 @@ bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap, bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr, - SkBitmap::Config *configp, bool *hasAlphap, + SkBitmap::Config *configp, bool * SK_RESTRICT hasAlphap, bool *doDitherp, SkPMColor *theTranspColorp) { png_uint_32 origWidth, origHeight; int bitDepth, colorType; @@ -546,9 +555,20 @@ bool SkPNGImageDecoder::getBitmapConfig(png_structp png_ptr, png_infop info_ptr, } } - return this->chooseFromOneChoice(*configp, origWidth, origHeight); + if (!this->chooseFromOneChoice(*configp, origWidth, origHeight)) { + return false; + } + + // If the image has alpha and the decoder wants unpremultiplied + // colors, the only supported config is 8888. + if (this->getRequireUnpremultipliedColors() && *hasAlphap) { + *configp = SkBitmap::kARGB_8888_Config; + } + return true; } +typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); + bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr, bool *hasAlphap, bool *reallyHasAlphap, SkColorTable **colorTablep) { @@ -587,9 +607,17 @@ bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr, int index = 0; int transLessThanFF = 0; + // Choose which function to use to create the color table. If the final destination's + // config is unpremultiplied, the color table will store unpremultiplied colors. + PackColorProc proc; + if (this->getRequireUnpremultipliedColors()) { + proc = &SkPackARGB32NoCheck; + } else { + proc = &SkPreMultiplyARGB; + } for (; index < numTrans; index++) { transLessThanFF |= (int)*trans - 0xFF; - *colorPtr++ = SkPreMultiplyARGB(*trans++, palette->red, palette->green, palette->blue); + *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue); palette++; } reallyHasAlpha |= (transLessThanFF < 0); @@ -679,7 +707,7 @@ bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { SkScaledBitmapSampler sampler(origWidth, rect.height(), sampleSize); SkBitmap decodedBitmap; - decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight(), 0); + decodedBitmap.setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); // from here down we are concerned with colortables and pixels @@ -773,7 +801,8 @@ bool SkPNGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) { upscale png's palette to a direct model */ SkAutoLockColors ctLock(colorTable); - if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors())) { + if (!sampler.begin(&decodedBitmap, sc, doDither, ctLock.colors(), + this->getRequireUnpremultipliedColors())) { return false; } const int height = decodedBitmap.height(); diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp index 95b9a97878..9cf84493ad 100644 --- a/src/images/SkImageDecoder_libwebp.cpp +++ b/src/images/SkImageDecoder_libwebp.cpp @@ -114,7 +114,17 @@ protected: virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; private: + /** + * Called when determining the output config to request to webp. + * If the image does not have alpha, there is no need to premultiply. + * If the caller wants unpremultiplied colors, that is respected. + */ + bool shouldPremultiply() const { + return SkToBool(fHasAlpha) && !this->getRequireUnpremultipliedColors(); + } + bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height); + SkStream* fInputStream; int fOrigWidth; int fOrigHeight; @@ -157,18 +167,16 @@ static bool return_false(const SkBitmap& bm, const char msg[]) { return false; // must always return false } -static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, int hasAlpha) { +static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, bool premultiply) { WEBP_CSP_MODE mode = MODE_LAST; SkBitmap::Config config = decodedBitmap->config(); - // For images that have alpha, choose appropriate color mode (MODE_rgbA, - // MODE_rgbA_4444) that pre-multiplies RGB pixel values with transparency - // factor (alpha). + if (config == SkBitmap::kARGB_8888_Config) { - mode = hasAlpha ? MODE_rgbA : MODE_RGBA; + mode = premultiply ? MODE_rgbA : MODE_RGBA; } else if (config == SkBitmap::kARGB_4444_Config) { - mode = hasAlpha ? MODE_rgbA_4444 : MODE_RGBA_4444; + mode = premultiply ? MODE_rgbA_4444 : MODE_RGBA_4444; } else if (config == SkBitmap::kRGB_565_Config) { - mode = MODE_RGB_565; + mode = MODE_RGB_565; } SkASSERT(MODE_LAST != mode); return mode; @@ -224,8 +232,8 @@ static bool webp_idecode(SkStream* stream, WebPDecoderConfig* config) { static bool webp_get_config_resize(WebPDecoderConfig* config, SkBitmap* decodedBitmap, - int width, int height, int hasAlpha) { - WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, hasAlpha); + int width, int height, bool premultiply) { + WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, premultiply); if (MODE_LAST == mode) { return false; } @@ -251,10 +259,10 @@ static bool webp_get_config_resize(WebPDecoderConfig* config, static bool webp_get_config_resize_crop(WebPDecoderConfig* config, SkBitmap* decodedBitmap, - const SkIRect& region, int hasAlpha) { + const SkIRect& region, bool premultiply) { if (!webp_get_config_resize(config, decodedBitmap, region.width(), - region.height(), hasAlpha)) { + region.height(), premultiply)) { return false; } @@ -372,7 +380,8 @@ bool SkWEBPImageDecoder::onDecodeSubset(SkBitmap* decodedBitmap, SkAutoLockPixels alp(*bitmap); WebPDecoderConfig config; - if (!webp_get_config_resize_crop(&config, bitmap, rect, fHasAlpha)) { + if (!webp_get_config_resize_crop(&config, bitmap, rect, + this->shouldPremultiply())) { return false; } @@ -430,7 +439,7 @@ bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, WebPDecoderConfig config; if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight, - hasAlpha)) { + this->shouldPremultiply())) { return false; } diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp index 25b32fd9b8..ca41de9396 100644 --- a/src/images/SkScaledBitmapSampler.cpp +++ b/src/images/SkScaledBitmapSampler.cpp @@ -1,4 +1,3 @@ - /* * Copyright 2007 The Android Open Source Project * @@ -11,6 +10,7 @@ #include "SkBitmap.h" #include "SkColorPriv.h" #include "SkDither.h" +#include "SkTypes.h" // 8888 @@ -289,6 +289,41 @@ static bool Sample_Index_DI(void* SK_RESTRICT dstRow, return false; } +// 8888 Unpremul + +static bool Sample_Gray_D8888_Unpremul(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int, + const SkPMColor[]) { + uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow); + for (int x = 0; x < width; x++) { + dst[x] = SkPackARGB32NoCheck(0xFF, src[0], src[0], src[0]); + src += deltaSrc; + } + return false; +} + +// Sample_RGBx_D8888_Unpremul is no different from Sample_RGBx_D8888, since alpha +// is 0xFF + +static bool Sample_RGBA_D8888_Unpremul(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int width, int deltaSrc, int, + const SkPMColor[]) { + uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow); + unsigned alphaMask = 0xFF; + for (int x = 0; x < width; x++) { + unsigned alpha = src[3]; + dst[x] = SkPackARGB32NoCheck(alpha, src[0], src[1], src[2]); + src += deltaSrc; + alphaMask &= alpha; + } + return alphaMask != 0xFF; +} + +// Sample_Index_D8888_Unpremul is the same as Sample_Index_D8888, since the +// color table has its colors inserted unpremultiplied. + /////////////////////////////////////////////////////////////////////////////// #include "SkScaledBitmapSampler.h" @@ -334,33 +369,44 @@ SkScaledBitmapSampler::SkScaledBitmapSampler(int width, int height, } bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc, bool dither, - const SkPMColor ctable[]) { + const SkPMColor ctable[], + bool requireUnpremul) { static const RowProc gProcs[] = { // 8888 (no dither distinction) - Sample_Gray_D8888, Sample_Gray_D8888, - Sample_RGBx_D8888, Sample_RGBx_D8888, - Sample_RGBA_D8888, Sample_RGBA_D8888, - Sample_Index_D8888, Sample_Index_D8888, - NULL, NULL, + Sample_Gray_D8888, Sample_Gray_D8888, + Sample_RGBx_D8888, Sample_RGBx_D8888, + Sample_RGBA_D8888, Sample_RGBA_D8888, + Sample_Index_D8888, Sample_Index_D8888, + NULL, NULL, // 565 (no alpha distinction) - Sample_Gray_D565, Sample_Gray_D565_D, - Sample_RGBx_D565, Sample_RGBx_D565_D, - Sample_RGBx_D565, Sample_RGBx_D565_D, - Sample_Index_D565, Sample_Index_D565_D, - Sample_D565_D565, Sample_D565_D565, + Sample_Gray_D565, Sample_Gray_D565_D, + Sample_RGBx_D565, Sample_RGBx_D565_D, + Sample_RGBx_D565, Sample_RGBx_D565_D, + Sample_Index_D565, Sample_Index_D565_D, + Sample_D565_D565, Sample_D565_D565, // 4444 - Sample_Gray_D4444, Sample_Gray_D4444_D, - Sample_RGBx_D4444, Sample_RGBx_D4444_D, - Sample_RGBA_D4444, Sample_RGBA_D4444_D, - Sample_Index_D4444, Sample_Index_D4444_D, - NULL, NULL, + Sample_Gray_D4444, Sample_Gray_D4444_D, + Sample_RGBx_D4444, Sample_RGBx_D4444_D, + Sample_RGBA_D4444, Sample_RGBA_D4444_D, + Sample_Index_D4444, Sample_Index_D4444_D, + NULL, NULL, // Index8 - NULL, NULL, - NULL, NULL, - NULL, NULL, - Sample_Index_DI, Sample_Index_DI, - NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + Sample_Index_DI, Sample_Index_DI, + NULL, NULL, + // 8888 Unpremul (no dither distinction) + Sample_Gray_D8888_Unpremul, Sample_Gray_D8888_Unpremul, + Sample_RGBx_D8888, Sample_RGBx_D8888, + Sample_RGBA_D8888_Unpremul, Sample_RGBA_D8888_Unpremul, + Sample_Index_D8888, Sample_Index_D8888, + NULL, NULL, }; + // The jump between dst configs in the table + static const int gProcDstConfigSpan = 10; + SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gProcs) == 5 * gProcDstConfigSpan, + gProcs_has_the_wrong_number_of_entries); fCTable = ctable; @@ -399,21 +445,28 @@ bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc, bool dither, switch (dst->config()) { case SkBitmap::kARGB_8888_Config: - index += 0; + index += 0 * gProcDstConfigSpan; break; case SkBitmap::kRGB_565_Config: - index += 10; + index += 1 * gProcDstConfigSpan; break; case SkBitmap::kARGB_4444_Config: - index += 20; + index += 2 * gProcDstConfigSpan; break; case SkBitmap::kIndex8_Config: - index += 30; + index += 3 * gProcDstConfigSpan; break; default: return false; } + if (requireUnpremul) { + if (dst->config() != SkBitmap::kARGB_8888_Config) { + return false; + } + index += 4 * gProcDstConfigSpan; + } + fRowProc = gProcs[index]; fDstRow = (char*)dst->getPixels(); fDstRowBytes = dst->rowBytes(); diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h index 1466309a77..6477db2178 100644 --- a/src/images/SkScaledBitmapSampler.h +++ b/src/images/SkScaledBitmapSampler.h @@ -36,7 +36,7 @@ public: // prepares iterator to process the src colors and write them into dst. // Returns false if the request cannot be fulfulled. bool begin(SkBitmap* dst, SrcConfig sc, bool doDither, - const SkPMColor* = NULL); + const SkPMColor* = NULL, bool requireUnPremul = false); // call with row of src pixels, for y = 0...scaledHeight-1. // returns true if the row had non-opaque alpha in it bool next(const uint8_t* SK_RESTRICT src); diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp index cd7f29fae4..e02ac316d5 100644 --- a/src/ports/SkImageDecoder_WIC.cpp +++ b/src/ports/SkImageDecoder_WIC.cpp @@ -183,10 +183,17 @@ bool SkImageDecoder_WIC::decodeStream(SkStream* stream, SkBitmap* bm, WICModes w hr = piImagingFactory->CreateFormatConverter(&piFormatConverter); } + GUID destinationPixelFormat; + if (this->getRequireUnpremultipliedColors()) { + destinationPixelFormat = GUID_WICPixelFormat32bppBGRA; + } else { + destinationPixelFormat = GUID_WICPixelFormat32bppPBGRA; + } + if (SUCCEEDED(hr)) { hr = piFormatConverter->Initialize( piBitmapSourceOriginal.get() //Input bitmap to convert - , GUID_WICPixelFormat32bppPBGRA //Destination pixel format + , destinationPixelFormat //Destination pixel format , WICBitmapDitherTypeNone //Specified dither patterm , NULL //Specify a particular palette , 0.f //Alpha threshold diff --git a/tests/ImageDecodingTest.cpp b/tests/ImageDecodingTest.cpp new file mode 100644 index 0000000000..f30a0b25b7 --- /dev/null +++ b/tests/ImageDecodingTest.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2013 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 "SkColorPriv.h" +#include "SkForceLinking.h" +#include "SkImageDecoder.h" +#include "SkOSFile.h" +#include "SkStream.h" +#include "SkString.h" +#include "Test.h" + +__SK_FORCE_IMAGE_DECODER_LINKING; + +/** + * Interprets c as an unpremultiplied color, and returns the + * premultiplied equivalent. + */ +static SkPMColor premultiply_unpmcolor(SkPMColor c) { + U8CPU a = SkGetPackedA32(c); + U8CPU r = SkGetPackedR32(c); + U8CPU g = SkGetPackedG32(c); + U8CPU b = SkGetPackedB32(c); + return SkPreMultiplyARGB(a, r, g, b); +} + +/** + * Return true if this stream format should be skipped, due + * to do being an opaque format or not a valid format. + */ +static bool skip_image_format(SkImageDecoder::Format format) { + switch (format) { + case SkImageDecoder::kPNG_Format: + case SkImageDecoder::kWEBP_Format: + return false; + // Skip unknown since it will not be decoded anyway. + case SkImageDecoder::kUnknown_Format: + // Technically ICO and BMP supports alpha channels, but our image + // decoders do not, so skip them as well. + case SkImageDecoder::kICO_Format: + case SkImageDecoder::kBMP_Format: + // The rest of these are opaque. + case SkImageDecoder::kWBMP_Format: + case SkImageDecoder::kGIF_Format: + case SkImageDecoder::kJPEG_Format: + return true; + } + SkASSERT(false); + return true; +} + +/** + * Test decoding an image in premultiplied mode and unpremultiplied mode and compare + * them. + */ +static void compare_unpremul(skiatest::Reporter* reporter, const SkString& filename) { + // Decode a resource: + SkBitmap bm8888; + SkBitmap bm8888Unpremul; + + SkFILEStream stream(filename.c_str()); + + SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&stream); + if (skip_image_format(format)) { + return; + } + + SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream)); + if (NULL == decoder.get()) { + SkDebugf("couldn't decode %s\n", filename.c_str()); + return; + } + + bool success = decoder->decode(&stream, &bm8888, SkBitmap::kARGB_8888_Config, + SkImageDecoder::kDecodePixels_Mode); + if (!success) { + return; + } + + success = stream.rewind(); + REPORTER_ASSERT(reporter, success); + if (!success) { + return; + } + + decoder->setRequireUnpremultipliedColors(true); + success = decoder->decode(&stream, &bm8888Unpremul, SkBitmap::kARGB_8888_Config, + SkImageDecoder::kDecodePixels_Mode); + if (!success) { + return; + } + + bool dimensionsMatch = bm8888.width() == bm8888Unpremul.width() + && bm8888.height() == bm8888Unpremul.height(); + REPORTER_ASSERT(reporter, dimensionsMatch); + if (!dimensionsMatch) { + return; + } + + // Only do the comparison if the two bitmaps are both 8888. + if (bm8888.config() != SkBitmap::kARGB_8888_Config + || bm8888Unpremul.config() != SkBitmap::kARGB_8888_Config) { + return; + } + + // Now compare the two bitmaps. + for (int i = 0; i < bm8888.width(); ++i) { + for (int j = 0; j < bm8888.height(); ++j) { + // "c0" is the color of the premultiplied bitmap at (i, j). + const SkPMColor c0 = *bm8888.getAddr32(i, j); + // "c1" is the result of premultiplying the color of the unpremultiplied + // bitmap at (i, j). + const SkPMColor c1 = premultiply_unpmcolor(*bm8888Unpremul.getAddr32(i, j)); + // Compute the difference for each component. + int da = SkAbs32(SkGetPackedA32(c0) - SkGetPackedA32(c1)); + int dr = SkAbs32(SkGetPackedR32(c0) - SkGetPackedR32(c1)); + int dg = SkAbs32(SkGetPackedG32(c0) - SkGetPackedG32(c1)); + int db = SkAbs32(SkGetPackedB32(c0) - SkGetPackedB32(c1)); + + // Alpha component must be exactly the same. + REPORTER_ASSERT(reporter, 0 == da); + // Other components may differ if rounding is done differently, + // but currently that is not the case. If an image fails here + // in the future, we can change these to account for differences. + REPORTER_ASSERT(reporter, 0 == dr); + REPORTER_ASSERT(reporter, 0 == dg); + REPORTER_ASSERT(reporter, 0 == db); + } + } +} + +static void test_imageDecodingTests(skiatest::Reporter* reporter) { + // This test cannot run if there is no resource path. + SkString resourcePath = skiatest::Test::GetResourcePath(); + if (resourcePath.isEmpty()) { + return; + } + SkOSFile::Iter iter(resourcePath.c_str()); + SkString basename; + if (iter.next(&basename)) { + do { + SkString filename = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str()); + //SkDebugf("about to decode \"%s\"\n", filename.c_str()); + compare_unpremul(reporter, filename); + } while (iter.next(&basename)); + } else { + SkDebugf("Failed to find any files :(\n"); + } +} + +#include "TestClassDef.h" +DEFINE_TESTCLASS("ImageDecoding", ImageDecodingTestClass, + test_imageDecodingTests) |