aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--bench/ImageDecodeBench.cpp91
-rw-r--r--gm/gm.h4
-rw-r--r--gyp/SampleApp.gyp1
-rw-r--r--gyp/bench.gypi1
-rw-r--r--gyp/tests.gyp1
-rw-r--r--include/core/SkImageDecoder.h16
-rw-r--r--samplecode/SampleColorFilter.cpp3
-rw-r--r--samplecode/SampleUnpremul.cpp204
-rw-r--r--src/images/SkImageDecoder.cpp3
-rw-r--r--src/images/SkImageDecoder_libpng.cpp47
-rw-r--r--src/images/SkImageDecoder_libwebp.cpp35
-rw-r--r--src/images/SkScaledBitmapSampler.cpp105
-rw-r--r--src/images/SkScaledBitmapSampler.h2
-rw-r--r--src/ports/SkImageDecoder_WIC.cpp9
-rw-r--r--tests/ImageDecodingTest.cpp157
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")); )
diff --git a/gm/gm.h b/gm/gm.h
index 6e34d3dede..bb263c7741 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -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)