Revert of Delete SkImageDecoder (patchset #9 id:150001 of https://codereview.chromium.org/1820503002/ )

Reason for revert:
Roll still failing

Original issue's description:
> Delete SkImageDecoder
>
> This image decoding implementation has been replaced
> by SkCodec in Android.
>
> Additionally, we have replaced uses of SkImageDecoder
> in Skia and Google3 with uses of SkCodec.
>
> Now we can delete SkImageDecoder :).
>
> BUG=skia:
> GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1820503002
> CQ_EXTRA_TRYBOTS=client.skia.compile:Build-Ubuntu-GCC-x86_64-Release-CMake-Trybot,Build-Mac-Clang-x86_64-Release-CMake-Trybot
>
> Committed: https://skia.googlesource.com/skia/+/f799706656f2581c5bf5510d94df3fa17cce1607
>
> Committed: https://skia.googlesource.com/skia/+/5b6e73e0c8282c4d85accbfbcecc6dee84f8a1eb
>
> Committed: https://skia.googlesource.com/skia/+/f037fdebda2a2626e6512d7532063f2cd41a264d

TBR=scroggo@google.com,djsollen@google.com
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:

Review URL: https://codereview.chromium.org/1830943002
diff --git a/src/android/SkBitmapRegionDecoder.cpp b/src/android/SkBitmapRegionDecoder.cpp
index 101efbd..712034b 100644
--- a/src/android/SkBitmapRegionDecoder.cpp
+++ b/src/android/SkBitmapRegionDecoder.cpp
@@ -11,6 +11,7 @@
 #include "SkAndroidCodec.h"
 #include "SkCodec.h"
 #include "SkCodecPriv.h"
+#include "SkImageDecoder.h"
 
 SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create(
         SkData* data, Strategy strategy) {
diff --git a/src/core/SkBigPicture.h b/src/core/SkBigPicture.h
index 0834709..2e42213 100644
--- a/src/core/SkBigPicture.h
+++ b/src/core/SkBigPicture.h
@@ -10,11 +10,9 @@
 
 #include "SkOncePtr.h"
 #include "SkPicture.h"
-#include "SkRect.h"
 #include "SkTemplates.h"
 
 class SkBBoxHierarchy;
-class SkMatrix;
 class SkRecord;
 
 // An implementation of SkPicture supporting an arbitrary number of drawing commands.
diff --git a/src/core/SkLayerInfo.h b/src/core/SkLayerInfo.h
index aa19ecb..04ae179 100644
--- a/src/core/SkLayerInfo.h
+++ b/src/core/SkLayerInfo.h
@@ -9,8 +9,6 @@
 #define SkLayerInfo_DEFINED
 
 #include "SkBigPicture.h"
-#include "SkMatrix.h"
-#include "SkPaint.h"
 #include "SkTArray.h"
 
 // This class stores information about the saveLayer/restore pairs found
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index 4f4317c..a606681 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -16,7 +16,6 @@
 #include "SkChecksum.h"
 #include "SkImageFilter.h"
 #include "SkMessageBus.h"
-#include "SkPaint.h"
 #include "SkPicture.h"
 #include "SkTDynamicHash.h"
 
diff --git a/src/images/SkForceLinking.cpp b/src/images/SkForceLinking.cpp
index 05fc7e0..55b7021 100644
--- a/src/images/SkForceLinking.cpp
+++ b/src/images/SkForceLinking.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#include "SkImageEncoder.h"
 #include "SkForceLinking.h"
+#include "SkImageDecoder.h"
 
 // This method is required to fool the linker into not discarding the pre-main
 // initialization and registration of the decoder classes. Passing true will
@@ -14,22 +14,26 @@
 int SkForceLinking(bool doNotPassTrue) {
     if (doNotPassTrue) {
         SkASSERT(false);
-        CreateJPEGImageEncoder();
-        CreateWEBPImageEncoder();
-
+        CreateJPEGImageDecoder();
+        CreateWEBPImageDecoder();
+        CreateBMPImageDecoder();
+        CreateICOImageDecoder();
+        CreateWBMPImageDecoder();
         // Only link hardware texture codecs on platforms that build them. See images.gyp
 #ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
-        CreateKTXImageEncoder();
+        CreatePKMImageDecoder();
+        CreateKTXImageDecoder();
+        CreateASTCImageDecoder();
 #endif
-
+        // Only link GIF and PNG on platforms that build them. See images.gyp
 #if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_IOS)
-        CreatePNGImageEncoder();
+        CreateGIFImageDecoder();
 #endif
-#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
-        CreatePNGImageEncoder_CG();
+#if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_IOS)
+        CreatePNGImageDecoder();
 #endif
-#if defined(SK_BUILD_FOR_WIN)
-        CreateImageEncoder_WIC();
+#if defined(SK_BUILD_FOR_IOS)
+        CreatePNGImageEncoder_IOS();
 #endif
         return -1;
     }
diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp
new file mode 100644
index 0000000..221faf7
--- /dev/null
+++ b/src/images/SkImageDecoder.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkImageDecoder.h"
+#include "SkBitmap.h"
+#include "SkImagePriv.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkCanvas.h"
+
+SkImageDecoder::SkImageDecoder()
+    : fPeeker(nullptr)
+    , fAllocator(nullptr)
+    , fSampleSize(1)
+    , fDefaultPref(kUnknown_SkColorType)
+    , fPreserveSrcDepth(false)
+    , fDitherImage(true)
+    , fSkipWritingZeroes(false)
+    , fPreferQualityOverSpeed(false)
+    , fRequireUnpremultipliedColors(false) {
+}
+
+SkImageDecoder::~SkImageDecoder() {
+    SkSafeUnref(fPeeker);
+    SkSafeUnref(fAllocator);
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* other) {
+    if (nullptr == other) {
+        return;
+    }
+    other->setPeeker(fPeeker);
+    other->setAllocator(fAllocator);
+    other->setSampleSize(fSampleSize);
+    other->setPreserveSrcDepth(fPreserveSrcDepth);
+    other->setDitherImage(fDitherImage);
+    other->setSkipWritingZeroes(fSkipWritingZeroes);
+    other->setPreferQualityOverSpeed(fPreferQualityOverSpeed);
+    other->setRequireUnpremultipliedColors(fRequireUnpremultipliedColors);
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+    return kUnknown_Format;
+}
+
+const char* SkImageDecoder::getFormatName() const {
+    return GetFormatName(this->getFormat());
+}
+
+const char* SkImageDecoder::GetFormatName(Format format) {
+    switch (format) {
+        case kUnknown_Format:
+            return "Unknown Format";
+        case kBMP_Format:
+            return "BMP";
+        case kGIF_Format:
+            return "GIF";
+        case kICO_Format:
+            return "ICO";
+        case kPKM_Format:
+            return "PKM";
+        case kKTX_Format:
+            return "KTX";
+        case kASTC_Format:
+            return "ASTC";
+        case kJPEG_Format:
+            return "JPEG";
+        case kPNG_Format:
+            return "PNG";
+        case kWBMP_Format:
+            return "WBMP";
+        case kWEBP_Format:
+            return "WEBP";
+        default:
+            SkDEBUGFAIL("Invalid format type!");
+    }
+    return "Unknown Format";
+}
+
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader* peeker) {
+    SkRefCnt_SafeAssign(fPeeker, peeker);
+    return peeker;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator* alloc) {
+    SkRefCnt_SafeAssign(fAllocator, alloc);
+    return alloc;
+}
+
+void SkImageDecoder::setSampleSize(int size) {
+    if (size < 1) {
+        size = 1;
+    }
+    fSampleSize = size;
+}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap* bitmap,
+                                   SkColorTable* ctable) const {
+    return bitmap->tryAllocPixels(fAllocator, ctable);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorType SkImageDecoder::getPrefColorType(SrcDepth srcDepth, bool srcHasAlpha) const {
+    SkColorType ct = fDefaultPref;
+    if (fPreserveSrcDepth) {
+        switch (srcDepth) {
+            case kIndex_SrcDepth:
+                ct = kIndex_8_SkColorType;
+                break;
+            case k8BitGray_SrcDepth:
+                ct = kN32_SkColorType;
+                break;
+            case k32Bit_SrcDepth:
+                ct = kN32_SkColorType;
+                break;
+        }
+    }
+    return ct;
+}
+
+SkImageDecoder::Result SkImageDecoder::decode(SkStream* stream, SkBitmap* bm, SkColorType pref,
+                                              Mode mode) {
+    // we reset this to false before calling onDecode
+    fShouldCancelDecode = false;
+    // assign this, for use by getPrefColorType(), in case fUsePrefTable is false
+    fDefaultPref = pref;
+
+    // pass a temporary bitmap, so that if we return false, we are assured of
+    // leaving the caller's bitmap untouched.
+    SkBitmap tmp;
+    const Result result = this->onDecode(stream, &tmp, mode);
+    if (kFailure != result) {
+        bm->swap(tmp);
+    }
+    return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm, SkColorType pref,  Mode mode,
+                                Format* format) {
+    SkASSERT(file);
+    SkASSERT(bm);
+
+    SkAutoTDelete<SkStreamRewindable> stream(SkStream::NewFromFile(file));
+    if (stream.get()) {
+        if (SkImageDecoder::DecodeStream(stream, bm, pref, mode, format)) {
+            if (SkPixelRef* pr = bm->pixelRef()) {
+                pr->setURI(file);
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void* buffer, size_t size, SkBitmap* bm, SkColorType pref,
+                                  Mode mode, Format* format) {
+    if (0 == size) {
+        return false;
+    }
+    SkASSERT(buffer);
+
+    SkMemoryStream  stream(buffer, size);
+    return SkImageDecoder::DecodeStream(&stream, bm, pref, mode, format);
+}
+
+bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkColorType pref,
+                                  Mode mode, Format* format) {
+    SkASSERT(stream);
+    SkASSERT(bm);
+
+    bool success = false;
+    SkImageDecoder* codec = SkImageDecoder::Factory(stream);
+
+    if (codec) {
+        success = codec->decode(stream, bm, pref, mode) != kFailure;
+        if (success && format) {
+            *format = codec->getFormat();
+            if (kUnknown_Format == *format) {
+                if (stream->rewind()) {
+                    *format = GetStreamFormat(stream);
+                }
+            }
+        }
+        delete codec;
+    }
+    return success;
+}
+
+bool SkImageDecoder::decodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3],
+                                      size_t rowBytes[3], SkYUVColorSpace* colorSpace) {
+    // we reset this to false before calling onDecodeYUV8Planes
+    fShouldCancelDecode = false;
+
+    return this->onDecodeYUV8Planes(stream, componentSizes, planes, rowBytes, colorSpace);
+}
diff --git a/src/images/SkImageDecoder_FactoryDefault.cpp b/src/images/SkImageDecoder_FactoryDefault.cpp
index ef8ddda..77c0a0a 100644
--- a/src/images/SkImageDecoder_FactoryDefault.cpp
+++ b/src/images/SkImageDecoder_FactoryDefault.cpp
@@ -6,9 +6,18 @@
  * found in the LICENSE file.
  */
 
+#include "SkImageDecoder.h"
 #include "SkMovie.h"
 #include "SkStream.h"
 
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+    return image_decoder_from_stream(stream);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
 typedef SkTRegistry<SkMovie*(*)(SkStreamRewindable*)> MovieReg;
 
 SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
diff --git a/src/images/SkImageDecoder_FactoryRegistrar.cpp b/src/images/SkImageDecoder_FactoryRegistrar.cpp
new file mode 100644
index 0000000..36034d2
--- /dev/null
+++ b/src/images/SkImageDecoder_FactoryRegistrar.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkErrorInternals.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkTRegistry.h"
+
+// This file is used for registration of SkImageDecoders. It also holds a function
+// for checking all the the registered SkImageDecoders for one that matches an
+// input SkStreamRewindable.
+
+template SkImageDecoder_DecodeReg* SkImageDecoder_DecodeReg::gHead;
+
+SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {
+    SkImageDecoder* codec = nullptr;
+    const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();
+    while (curr) {
+        codec = curr->factory()(stream);
+        // we rewind here, because we promise later when we call "decode", that
+        // the stream will be at its beginning.
+        bool rewindSuceeded = stream->rewind();
+
+        // our image decoder's require that rewind is supported so we fail early
+        // if we are given a stream that does not support rewinding.
+        if (!rewindSuceeded) {
+            SkDEBUGF(("Unable to rewind the image stream."));
+            delete codec;
+            return nullptr;
+        }
+
+        if (codec) {
+            return codec;
+        }
+        curr = curr->next();
+    }
+    return nullptr;
+}
+
+template SkImageDecoder_FormatReg* SkImageDecoder_FormatReg::gHead;
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStreamRewindable* stream) {
+    const SkImageDecoder_FormatReg* curr = SkImageDecoder_FormatReg::Head();
+    while (curr != nullptr) {
+        Format format = curr->factory()(stream);
+        if (!stream->rewind()) {
+            SkErrorInternals::SetError(kInvalidOperation_SkError,
+                                       "Unable to rewind the image stream\n");
+            return kUnknown_Format;
+        }
+        if (format != kUnknown_Format) {
+            return format;
+        }
+        curr = curr->next();
+    }
+    return kUnknown_Format;
+}
diff --git a/src/images/SkImageDecoder_astc.cpp b/src/images/SkImageDecoder_astc.cpp
new file mode 100644
index 0000000..30d65f1
--- /dev/null
+++ b/src/images/SkImageDecoder_astc.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkData.h"
+#include "SkEndian.h"
+#include "SkColorPriv.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTypes.h"
+
+#include "SkTextureCompressor.h"
+
+class SkASTCImageDecoder : public SkImageDecoder {
+public:
+    SkASTCImageDecoder() { }
+
+    Format getFormat() const override {
+        return kASTC_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const uint32_t kASTCMagicNumber = 0x5CA1AB13;
+
+static inline int read_24bit(const uint8_t* buf) {
+    // Assume everything is little endian...
+    return
+        static_cast<int>(buf[0]) |
+        (static_cast<int>(buf[1]) << 8) |
+        (static_cast<int>(buf[2]) << 16);
+}
+
+SkImageDecoder::Result SkASTCImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    auto data = SkCopyStreamToData(stream);
+    if (!data || !data->size()) {
+        return kFailure;
+    }
+
+    unsigned char* buf = (unsigned char*) data->data();
+
+    // Make sure that the magic header is there...
+    SkASSERT(SkEndian_SwapLE32(*(reinterpret_cast<uint32_t*>(buf))) == kASTCMagicNumber);
+
+    // Advance past the magic header
+    buf += 4;
+
+    const int blockDimX = buf[0];
+    const int blockDimY = buf[1];
+    const int blockDimZ = buf[2];
+
+    if (1 != blockDimZ) {
+        // We don't support decoding 3D
+        return kFailure;
+    }
+
+    // Choose the proper ASTC format
+    SkTextureCompressor::Format astcFormat;
+    if (4 == blockDimX && 4 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_4x4_Format;
+    } else if (5 == blockDimX && 4 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_5x4_Format;
+    } else if (5 == blockDimX && 5 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_5x5_Format;
+    } else if (6 == blockDimX && 5 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_6x5_Format;
+    } else if (6 == blockDimX && 6 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_6x6_Format;
+    } else if (8 == blockDimX && 5 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_8x5_Format;
+    } else if (8 == blockDimX && 6 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_8x6_Format;
+    } else if (8 == blockDimX && 8 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_8x8_Format;
+    } else if (10 == blockDimX && 5 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_10x5_Format;
+    } else if (10 == blockDimX && 6 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_10x6_Format;
+    } else if (10 == blockDimX && 8 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_10x8_Format;
+    } else if (10 == blockDimX && 10 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_10x10_Format;
+    } else if (12 == blockDimX && 10 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_12x10_Format;
+    } else if (12 == blockDimX && 12 == blockDimY) {
+        astcFormat = SkTextureCompressor::kASTC_12x12_Format;
+    } else {
+        // We don't support any other block dimensions..
+        return kFailure;
+    }
+
+    // Advance buf past the block dimensions
+    buf += 3;
+
+    // Read the width/height/depth from the buffer...
+    const int width = read_24bit(buf);
+    const int height = read_24bit(buf + 3);
+    const int depth = read_24bit(buf + 6);
+
+    if (1 != depth) {
+        // We don't support decoding 3D.
+        return kFailure;
+    }
+
+    // Advance the buffer past the image dimensions
+    buf += 9;
+
+    // Setup the sampler...
+    SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+    // Determine the alpha of the bitmap...
+    SkAlphaType alphaType = kOpaque_SkAlphaType;
+    if (this->getRequireUnpremultipliedColors()) {
+        alphaType = kUnpremul_SkAlphaType;
+    } else {
+        alphaType = kPremul_SkAlphaType;
+    }
+
+    // Set the config...
+    bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(), alphaType));
+
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return kFailure;
+    }
+
+    // Lock the pixels, since we're about to write to them...
+    SkAutoLockPixels alp(*bm);
+
+    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, *this)) {
+        return kFailure;
+    }
+
+    // ASTC Data is encoded as RGBA pixels, so we should extract it as such
+    int nPixels = width * height;
+    SkAutoMalloc outRGBAData(nPixels * 4);
+    uint8_t *outRGBADataPtr = reinterpret_cast<uint8_t *>(outRGBAData.get());
+
+    // Decode ASTC
+    if (!SkTextureCompressor::DecompressBufferFromFormat(
+            outRGBADataPtr, width*4, buf, width, height, astcFormat)) {
+        return kFailure;
+    }
+
+    // Set each of the pixels...
+    const int srcRowBytes = width * 4;
+    const int dstHeight = sampler.scaledHeight();
+    const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBADataPtr);
+    srcRow += sampler.srcY0() * srcRowBytes;
+    for (int y = 0; y < dstHeight; ++y) {
+        sampler.next(srcRow);
+        srcRow += sampler.srcDY() * srcRowBytes;
+    }
+
+    return kSuccess;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(ASTCImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_astc(SkStreamRewindable* stream) {
+    // Read the ASTC header and make sure it's valid.
+    uint32_t magic;
+    if (stream->read((void*)&magic, 4) != 4) {
+        return false;
+    }
+
+    return kASTCMagicNumber == SkEndian_SwapLE32(magic);
+}
+
+static SkImageDecoder* sk_libastc_dfactory(SkStreamRewindable* stream) {
+    if (is_astc(stream)) {
+        return new SkASTCImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libastc_dfactory);
+
+static SkImageDecoder::Format get_format_astc(SkStreamRewindable* stream) {
+    if (is_astc(stream)) {
+        return SkImageDecoder::kASTC_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_astc);
diff --git a/src/images/SkImageDecoder_ktx.cpp b/src/images/SkImageDecoder_ktx.cpp
index 79f0293..1566745 100644
--- a/src/images/SkImageDecoder_ktx.cpp
+++ b/src/images/SkImageDecoder_ktx.cpp
@@ -6,9 +6,10 @@
  */
 
 #include "SkColorPriv.h"
-#include "SkImageEncoder.h"
+#include "SkImageDecoder.h"
 #include "SkImageGenerator.h"
 #include "SkPixelRef.h"
+#include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkStreamPriv.h"
 #include "SkTypes.h"
@@ -16,14 +17,230 @@
 #include "ktx.h"
 #include "etc1.h"
 
-///////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
 
-// KTX Image Encoder
-//
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// KTX Image decoder
+// ---
 // KTX is a general texture data storage file format ratified by the Khronos Group. As an
 // overview, a KTX file contains all of the appropriate values needed to fully specify a
 // texture in an OpenGL application, including the use of compressed data.
 //
+// This decoder is meant to be used with an SkDiscardablePixelRef so that GPU backends
+// can sniff the data before creating a texture. If they encounter a compressed format
+// that they understand, they can then upload the data directly to the GPU. Otherwise,
+// they will decode the data into a format that Skia supports.
+
+class SkKTXImageDecoder : public SkImageDecoder {
+public:
+    SkKTXImageDecoder() { }
+
+    Format getFormat() const override {
+        return kKTX_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+SkImageDecoder::Result SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    // TODO: Implement SkStream::copyToData() that's cheap for memory and file streams
+    auto data = SkCopyStreamToData(stream);
+    if (nullptr == data) {
+        return kFailure;
+    }
+
+    SkKTXFile ktxFile(data.get());
+    if (!ktxFile.valid()) {
+        return kFailure;
+    }
+
+    const unsigned short width = ktxFile.width();
+    const unsigned short height = ktxFile.height();
+
+    // Set a flag if our source is premultiplied alpha
+    const SkString premulKey("KTXPremultipliedAlpha");
+    const bool bSrcIsPremul = ktxFile.getValueForKey(premulKey) == SkString("True");
+
+    // Setup the sampler...
+    SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+    // Determine the alpha of the bitmap...
+    SkAlphaType alphaType = kOpaque_SkAlphaType;
+    if (ktxFile.isRGBA8()) {
+        if (this->getRequireUnpremultipliedColors()) {
+            alphaType = kUnpremul_SkAlphaType;
+            // If the client wants unpremul colors and we only have
+            // premul, then we cannot honor their wish.
+            if (bSrcIsPremul) {
+                return kFailure;
+            }
+        } else {
+            alphaType = kPremul_SkAlphaType;
+        }
+    }
+
+    // Search through the compressed formats to see if the KTX file is holding
+    // compressed data
+    bool ktxIsCompressed = false;
+    SkTextureCompressor::Format ktxCompressedFormat;
+    for (int i = 0; i < SkTextureCompressor::kFormatCnt; ++i) {
+        SkTextureCompressor::Format fmt = static_cast<SkTextureCompressor::Format>(i);
+        if (ktxFile.isCompressedFormat(fmt)) {
+            ktxIsCompressed = true;
+            ktxCompressedFormat = fmt;
+            break;
+        }
+    }
+
+    // If the compressed format is a grayscale image, then setup the bitmap properly...
+    bool isCompressedAlpha = ktxIsCompressed &&
+        ((SkTextureCompressor::kLATC_Format == ktxCompressedFormat) ||
+         (SkTextureCompressor::kR11_EAC_Format == ktxCompressedFormat));
+
+    // Set the image dimensions and underlying pixel type.
+    if (isCompressedAlpha) {
+        const int w = sampler.scaledWidth();
+        const int h = sampler.scaledHeight();
+        bm->setInfo(SkImageInfo::MakeA8(w, h));
+    } else {
+        const int w = sampler.scaledWidth();
+        const int h = sampler.scaledHeight();
+        bm->setInfo(SkImageInfo::MakeN32(w, h, alphaType));
+    }
+    
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    // If we've made it this far, then we know how to grok the data.
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return kFailure;
+    }
+
+    // Lock the pixels, since we're about to write to them...
+    SkAutoLockPixels alp(*bm);
+
+    if (isCompressedAlpha) {
+        if (!sampler.begin(bm, SkScaledBitmapSampler::kGray, *this)) {
+            return kFailure;
+        }
+
+        // Alpha data is only a single byte per pixel.
+        int nPixels = width * height;
+        SkAutoMalloc outRGBData(nPixels);
+        uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+        // Decode the compressed format
+        const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+        if (!SkTextureCompressor::DecompressBufferFromFormat(
+                outRGBDataPtr, width, buf, width, height, ktxCompressedFormat)) {
+            return kFailure;
+        }
+
+        // Set each of the pixels...
+        const int srcRowBytes = width;
+        const int dstHeight = sampler.scaledHeight();
+        const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+        srcRow += sampler.srcY0() * srcRowBytes;
+        for (int y = 0; y < dstHeight; ++y) {
+            sampler.next(srcRow);
+            srcRow += sampler.srcDY() * srcRowBytes;
+        }
+
+        return kSuccess;
+
+    } else if (ktxFile.isCompressedFormat(SkTextureCompressor::kETC1_Format)) {
+        if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+            return kFailure;
+        }
+
+        // ETC1 Data is encoded as RGB pixels, so we should extract it as such
+        int nPixels = width * height;
+        SkAutoMalloc outRGBData(nPixels * 3);
+        uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+        // Decode ETC1
+        const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+        if (!SkTextureCompressor::DecompressBufferFromFormat(
+                outRGBDataPtr, width*3, buf, width, height, SkTextureCompressor::kETC1_Format)) {
+            return kFailure;
+        }
+
+        // Set each of the pixels...
+        const int srcRowBytes = width * 3;
+        const int dstHeight = sampler.scaledHeight();
+        const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+        srcRow += sampler.srcY0() * srcRowBytes;
+        for (int y = 0; y < dstHeight; ++y) {
+            sampler.next(srcRow);
+            srcRow += sampler.srcDY() * srcRowBytes;
+        }
+
+        return kSuccess;
+
+    } else if (ktxFile.isRGB8()) {
+
+        // Uncompressed RGB data (without alpha)
+        if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+            return kFailure;
+        }
+
+        // Just need to read RGB pixels
+        const int srcRowBytes = width * 3;
+        const int dstHeight = sampler.scaledHeight();
+        const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+        srcRow += sampler.srcY0() * srcRowBytes;
+        for (int y = 0; y < dstHeight; ++y) {
+            sampler.next(srcRow);
+            srcRow += sampler.srcDY() * srcRowBytes;
+        }
+
+        return kSuccess;
+
+    } else if (ktxFile.isRGBA8()) {
+
+        // Uncompressed RGBA data
+
+        // If we know that the image contains premultiplied alpha, then
+        // we need to turn off the premultiplier
+        SkScaledBitmapSampler::Options opts (*this);
+        if (bSrcIsPremul) {
+            SkASSERT(bm->alphaType() == kPremul_SkAlphaType);
+            SkASSERT(!this->getRequireUnpremultipliedColors());
+
+            opts.fPremultiplyAlpha = false;
+        } 
+
+        if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, opts)) {
+            return kFailure;
+        }
+
+        // Just need to read RGBA pixels
+        const int srcRowBytes = width * 4;
+        const int dstHeight = sampler.scaledHeight();
+        const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+        srcRow += sampler.srcY0() * srcRowBytes;
+        for (int y = 0; y < dstHeight; ++y) {
+            sampler.next(srcRow);
+            srcRow += sampler.srcDY() * srcRowBytes;
+        }
+
+        return kSuccess;
+    }
+
+    return kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// KTX Image Encoder
+//
 // This encoder takes a best guess at how to encode the bitmap passed to it. If
 // there is an installed discardable pixel ref with existing PKM data, then we
 // will repurpose the existing ETC1 data into a KTX file. If the data contains
@@ -87,11 +304,28 @@
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(KTXImageDecoder);
 DEFINE_ENCODER_CREATOR(KTXImageEncoder);
 /////////////////////////////////////////////////////////////////////////////////////////
 
+static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) {
+    if (SkKTXFile::is_ktx(stream)) {
+        return new SkKTXImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) {
+    if (SkKTXFile::is_ktx(stream)) {
+        return SkImageDecoder::kKTX_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
 SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) {
     return (SkImageEncoder::kKTX_Type == t) ? new SkKTXImageEncoder : nullptr;
 }
 
+static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_ktx);
 static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory);
diff --git a/src/images/SkImageDecoder_libbmp.cpp b/src/images/SkImageDecoder_libbmp.cpp
new file mode 100644
index 0000000..b9359be
--- /dev/null
+++ b/src/images/SkImageDecoder_libbmp.cpp
@@ -0,0 +1,166 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "bmpdecoderhelper.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTDArray.h"
+
+class SkBMPImageDecoder : public SkImageDecoder {
+public:
+    SkBMPImageDecoder() {}
+
+    Format getFormat() const override {
+        return kBMP_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(BMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_bmp(SkStreamRewindable* stream) {
+    static const char kBmpMagic[] = { 'B', 'M' };
+
+
+    char buffer[sizeof(kBmpMagic)];
+
+    return stream->read(buffer, sizeof(kBmpMagic)) == sizeof(kBmpMagic) &&
+        !memcmp(buffer, kBmpMagic, sizeof(kBmpMagic));
+}
+
+static SkImageDecoder* sk_libbmp_dfactory(SkStreamRewindable* stream) {
+    if (is_bmp(stream)) {
+        return new SkBMPImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libbmp_dfactory);
+
+static SkImageDecoder::Format get_format_bmp(SkStreamRewindable* stream) {
+    if (is_bmp(stream)) {
+        return SkImageDecoder::kBMP_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_bmp);
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkBmpDecoderCallback : public image_codec::BmpDecoderCallback {
+public:
+    // we don't copy the bitmap, just remember the pointer
+    SkBmpDecoderCallback(bool justBounds) : fJustBounds(justBounds) {}
+
+    // override from BmpDecoderCallback
+    virtual uint8* SetSize(int width, int height) {
+        fWidth = width;
+        fHeight = height;
+        if (fJustBounds) {
+            return nullptr;
+        }
+
+        fRGB.setCount(width * height * 3);  // 3 == r, g, b
+        return fRGB.begin();
+    }
+
+    int width() const { return fWidth; }
+    int height() const { return fHeight; }
+    const uint8_t* rgb() const { return fRGB.begin(); }
+
+private:
+    SkTDArray<uint8_t> fRGB;
+    int fWidth;
+    int fHeight;
+    bool fJustBounds;
+};
+
+SkImageDecoder::Result SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    // First read the entire stream, so that all of the data can be passed to
+    // the BmpDecoderHelper.
+
+    auto data = SkCopyStreamToData(stream);
+    if (!data) {
+        return kFailure;
+    }
+
+    // Byte length of all of the data.
+    const size_t length = data->size();
+    if (0 == length) {
+        return kFailure;
+    }
+
+    const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode;
+    SkBmpDecoderCallback callback(justBounds);
+
+    // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...]
+    {
+        image_codec::BmpDecoderHelper helper;
+        const int max_pixels = 16383*16383; // max width*height
+        if (!helper.DecodeImage((const char*) data->data(), length,
+                                max_pixels, &callback)) {
+            return kFailure;
+        }
+    }
+
+    // we don't need this anymore, so free it now (before we try to allocate
+    // the bitmap's pixels) rather than waiting for its destructor
+    data.reset(nullptr);
+
+    int width = callback.width();
+    int height = callback.height();
+    SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, false);
+
+    // only accept prefConfig if it makes sense for us
+    if (kARGB_4444_SkColorType != colorType && kRGB_565_SkColorType != colorType) {
+        colorType = kN32_SkColorType;
+    }
+
+    SkScaledBitmapSampler sampler(width, height, getSampleSize());
+
+    bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+                                  colorType, kOpaque_SkAlphaType));
+
+    if (justBounds) {
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return kFailure;
+    }
+
+    SkAutoLockPixels alp(*bm);
+
+    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+        return kFailure;
+    }
+
+    const int srcRowBytes = width * 3;
+    const int dstHeight = sampler.scaledHeight();
+    const uint8_t* srcRow = callback.rgb();
+
+    srcRow += sampler.srcY0() * srcRowBytes;
+    for (int y = 0; y < dstHeight; y++) {
+        sampler.next(srcRow);
+        srcRow += sampler.srcDY() * srcRowBytes;
+    }
+    return kSuccess;
+}
diff --git a/src/images/SkImageDecoder_libgif.cpp b/src/images/SkImageDecoder_libgif.cpp
new file mode 100644
index 0000000..2677b13
--- /dev/null
+++ b/src/images/SkImageDecoder_libgif.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkColorTable.h"
+#include "SkImageDecoder.h"
+#include "SkRTConf.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+#include "gif_lib.h"
+
+class SkGIFImageDecoder : public SkImageDecoder {
+public:
+    Format getFormat() const override {
+        return kGIF_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+static const uint8_t gStartingIterlaceYValue[] = {
+    0, 4, 2, 1
+};
+static const uint8_t gDeltaIterlaceYValue[] = {
+    8, 8, 4, 2
+};
+
+SK_CONF_DECLARE(bool, c_suppressGIFImageDecoderWarnings,
+                "images.gif.suppressDecoderWarnings", true,
+                "Suppress GIF warnings and errors when calling image decode "
+                "functions.");
+
+
+/*  Implement the GIF interlace algorithm in an iterator.
+    1) grab every 8th line beginning at 0
+    2) grab every 8th line beginning at 4
+    3) grab every 4th line beginning at 2
+    4) grab every 2nd line beginning at 1
+*/
+class GifInterlaceIter {
+public:
+    GifInterlaceIter(int height) : fHeight(height) {
+        fStartYPtr = gStartingIterlaceYValue;
+        fDeltaYPtr = gDeltaIterlaceYValue;
+
+        fCurrY = *fStartYPtr++;
+        fDeltaY = *fDeltaYPtr++;
+    }
+
+    int currY() const {
+        SkASSERT(fStartYPtr);
+        SkASSERT(fDeltaYPtr);
+        return fCurrY;
+    }
+
+    void next() {
+        SkASSERT(fStartYPtr);
+        SkASSERT(fDeltaYPtr);
+
+        int y = fCurrY + fDeltaY;
+        // We went from an if statement to a while loop so that we iterate
+        // through fStartYPtr until a valid row is found. This is so that images
+        // that are smaller than 5x5 will not trash memory.
+        while (y >= fHeight) {
+            if (gStartingIterlaceYValue +
+                    SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
+                // we done
+                SkDEBUGCODE(fStartYPtr = nullptr;)
+                SkDEBUGCODE(fDeltaYPtr = nullptr;)
+                y = 0;
+            } else {
+                y = *fStartYPtr++;
+                fDeltaY = *fDeltaYPtr++;
+            }
+        }
+        fCurrY = y;
+    }
+
+private:
+    const int fHeight;
+    int fCurrY;
+    int fDeltaY;
+    const uint8_t* fStartYPtr;
+    const uint8_t* fDeltaYPtr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
+                              int size) {
+    SkStream* stream = (SkStream*) fileType->UserData;
+    return (int) stream->read(out, size);
+}
+
+void CheckFreeExtension(SavedImage* Image) {
+    if (Image->ExtensionBlocks) {
+#if GIFLIB_MAJOR < 5
+        FreeExtension(Image);
+#else
+        GifFreeExtensions(&Image->ExtensionBlockCount, &Image->ExtensionBlocks);
+#endif
+    }
+}
+
+// return nullptr on failure
+static const ColorMapObject* find_colormap(const GifFileType* gif) {
+    const ColorMapObject* cmap = gif->Image.ColorMap;
+    if (nullptr == cmap) {
+        cmap = gif->SColorMap;
+    }
+
+    if (nullptr == cmap) {
+        // no colormap found
+        return nullptr;
+    }
+    // some sanity checks
+    if (cmap && ((unsigned)cmap->ColorCount > 256 ||
+                 cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
+        cmap = nullptr;
+    }
+    return cmap;
+}
+
+// return -1 if not found (i.e. we're completely opaque)
+static int find_transpIndex(const SavedImage& image, int colorCount) {
+    int transpIndex = -1;
+    for (int i = 0; i < image.ExtensionBlockCount; ++i) {
+        const ExtensionBlock* eb = image.ExtensionBlocks + i;
+        if (eb->Function == 0xF9 && eb->ByteCount == 4) {
+            if (eb->Bytes[0] & 1) {
+                transpIndex = (unsigned char)eb->Bytes[3];
+                // check for valid transpIndex
+                if (transpIndex >= colorCount) {
+                    transpIndex = -1;
+                }
+                break;
+            }
+        }
+    }
+    return transpIndex;
+}
+
+static SkImageDecoder::Result error_return(const SkBitmap& bm, const char msg[]) {
+    if (!c_suppressGIFImageDecoderWarnings) {
+        SkDebugf("libgif error [%s] bitmap [%d %d] pixels %p colortable %p\n",
+                 msg, bm.width(), bm.height(), bm.getPixels(),
+                 bm.getColorTable());
+    }
+    return SkImageDecoder::kFailure;
+}
+
+static void gif_warning(const SkBitmap& bm, const char msg[]) {
+    if (!c_suppressGIFImageDecoderWarnings) {
+        SkDebugf("libgif warning [%s] bitmap [%d %d] pixels %p colortable %p\n",
+                 msg, bm.width(), bm.height(), bm.getPixels(),
+                 bm.getColorTable());
+    }
+}
+
+/**
+ *  Skip rows in the source gif image.
+ *  @param gif Source image.
+ *  @param dst Scratch output needed by gif library call. Must be >= width bytes.
+ *  @param width Bytes per row in the source image.
+ *  @param rowsToSkip Number of rows to skip.
+ *  @return True on success, false on GIF_ERROR.
+ */
+static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) {
+    for (int i = 0; i < rowsToSkip; i++) {
+        if (DGifGetLine(gif, dst, width) == GIF_ERROR) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ *  GIFs with fewer then 256 color entries will sometimes index out of
+ *  bounds of the color table (this is malformed, but libgif does not
+ *  check sicne it is rare).  This function checks for this error and
+ *  fixes it.  This makes the output image consistantly deterministic.
+ */
+static void sanitize_indexed_bitmap(SkBitmap* bm) {
+    if ((kIndex_8_SkColorType == bm->colorType()) && !(bm->empty())) {
+        SkAutoLockPixels alp(*bm);
+        if (bm->getPixels()) {
+            SkColorTable* ct = bm->getColorTable();  // Index8 must have it.
+            SkASSERT(ct != nullptr);
+            uint32_t count = ct->count();
+            SkASSERT(count > 0);
+            SkASSERT(count <= 0x100);
+            if (count != 0x100) {  // Full colortables can't go wrong.
+                // Count is a power of 2; asserted elsewhere.
+                uint8_t byteMask = (~(count - 1));
+                bool warning = false;
+                uint8_t* addr = static_cast<uint8_t*>(bm->getPixels());
+                int height = bm->height();
+                int width = bm->width();
+                size_t rowBytes = bm->rowBytes();
+                while (--height >= 0) {
+                    uint8_t* ptr = addr;
+                    int x = width;
+                    while (--x >= 0) {
+                        if (0 != ((*ptr) & byteMask)) {
+                            warning = true;
+                            *ptr = 0;
+                        }
+                        ++ptr;
+                    }
+                    addr += rowBytes;
+                }
+                if (warning) {
+                    gif_warning(*bm, "Index out of bounds.");
+                }
+            }
+        }
+    }
+}
+
+namespace {
+// This function is a template argument, so can't be static.
+int close_gif(GifFileType* gif) {
+#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
+    return DGifCloseFile(gif);
+#else
+    return DGifCloseFile(gif, nullptr);
+#endif
+}
+}//namespace
+
+SkImageDecoder::Result SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
+#if GIFLIB_MAJOR < 5
+    GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
+#else
+    GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, nullptr);
+#endif
+    if (nullptr == gif) {
+        return error_return(*bm, "DGifOpen");
+    }
+
+    SkAutoTCallIProc<GifFileType, close_gif> acp(gif);
+
+    SavedImage temp_save;
+    temp_save.ExtensionBlocks=nullptr;
+    temp_save.ExtensionBlockCount=0;
+    SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
+
+    int width, height;
+    GifRecordType recType;
+    GifByteType *extData;
+#if GIFLIB_MAJOR >= 5
+    int extFunction;
+#endif
+    int transpIndex = -1;   // -1 means we don't have it (yet)
+    int fillIndex = gif->SBackGroundColor;
+
+    do {
+        if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
+            return error_return(*bm, "DGifGetRecordType");
+        }
+
+        switch (recType) {
+        case IMAGE_DESC_RECORD_TYPE: {
+            if (DGifGetImageDesc(gif) == GIF_ERROR) {
+                return error_return(*bm, "IMAGE_DESC_RECORD_TYPE");
+            }
+
+            if (gif->ImageCount < 1) {    // sanity check
+                return error_return(*bm, "ImageCount < 1");
+            }
+
+            width = gif->SWidth;
+            height = gif->SHeight;
+
+            SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
+            const GifImageDesc& desc = image->ImageDesc;
+
+            int imageLeft = desc.Left;
+            int imageTop = desc.Top;
+            const int innerWidth = desc.Width;
+            const int innerHeight = desc.Height;
+            if (innerWidth <= 0 || innerHeight <= 0) {
+                return error_return(*bm, "invalid dimensions");
+            }
+
+            // check for valid descriptor
+            if (innerWidth > width) {
+                gif_warning(*bm, "image too wide, expanding output to size");
+                width = innerWidth;
+                imageLeft = 0;
+            } else if (imageLeft + innerWidth > width) {
+                gif_warning(*bm, "shifting image left to fit");
+                imageLeft = width - innerWidth;
+            } else if (imageLeft < 0) {
+                gif_warning(*bm, "shifting image right to fit");
+                imageLeft = 0;
+            }
+
+
+            if (innerHeight > height) {
+                gif_warning(*bm, "image too tall,  expanding output to size");
+                height = innerHeight;
+                imageTop = 0;
+            } else if (imageTop + innerHeight > height) {
+                gif_warning(*bm, "shifting image up to fit");
+                imageTop = height - innerHeight;
+            } else if (imageTop < 0) {
+                gif_warning(*bm, "shifting image down to fit");
+                imageTop = 0;
+            }
+
+            SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+            bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+                                          kIndex_8_SkColorType, kPremul_SkAlphaType));
+
+            if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+                return kSuccess;
+            }
+
+
+            // now we decode the colortable
+            int colorCount = 0;
+            {
+                // Declare colorPtr here for scope.
+                SkPMColor colorPtr[256]; // storage for worst-case
+                const ColorMapObject* cmap = find_colormap(gif);
+                if (cmap != nullptr) {
+                    SkASSERT(cmap->ColorCount == (1 << (cmap->BitsPerPixel)));
+                    colorCount = cmap->ColorCount;
+                    if (colorCount > 256) {
+                        colorCount = 256;  // our kIndex8 can't support more
+                    }
+                    for (int index = 0; index < colorCount; index++) {
+                        colorPtr[index] = SkPackARGB32(0xFF,
+                                                       cmap->Colors[index].Red,
+                                                       cmap->Colors[index].Green,
+                                                       cmap->Colors[index].Blue);
+                    }
+                } else {
+                    // find_colormap() returned nullptr.  Some (rare, broken)
+                    // GIFs don't have a color table, so we force one.
+                    gif_warning(*bm, "missing colormap");
+                    colorCount = 256;
+                    sk_memset32(colorPtr, SK_ColorWHITE, colorCount);
+                }
+                transpIndex = find_transpIndex(temp_save, colorCount);
+                if (transpIndex >= 0) {
+                    colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
+                    fillIndex = transpIndex;
+                } else if (fillIndex >= colorCount) {
+                    // gif->SBackGroundColor should be less than colorCount.
+                    fillIndex = 0;  // If not, fix it.
+                }
+
+                SkAutoTUnref<SkColorTable> ctable(new SkColorTable(colorPtr, colorCount));
+                if (!this->allocPixelRef(bm, ctable)) {
+                    return error_return(*bm, "allocPixelRef");
+                }
+            }
+
+            // abort if either inner dimension is <= 0
+            if (innerWidth <= 0 || innerHeight <= 0) {
+                return error_return(*bm, "non-pos inner width/height");
+            }
+
+            SkAutoLockPixels alp(*bm);
+
+            SkAutoTMalloc<uint8_t> storage(innerWidth);
+            uint8_t* scanline = storage.get();
+
+            // GIF has an option to store the scanlines of an image, plus a larger background,
+            // filled by a fill color. In this case, we will use a subset of the larger bitmap
+            // for sampling.
+            SkBitmap subset;
+            SkBitmap* workingBitmap;
+            // are we only a subset of the total bounds?
+            if ((imageTop | imageLeft) > 0 ||
+                 innerWidth < width || innerHeight < height) {
+                // Fill the background.
+                memset(bm->getPixels(), fillIndex, bm->getSize());
+
+                // Create a subset of the bitmap.
+                SkIRect subsetRect(SkIRect::MakeXYWH(imageLeft / sampler.srcDX(),
+                                                     imageTop / sampler.srcDY(),
+                                                     innerWidth / sampler.srcDX(),
+                                                     innerHeight / sampler.srcDY()));
+                if (!bm->extractSubset(&subset, subsetRect)) {
+                    return error_return(*bm, "Extract failed.");
+                }
+                // Update the sampler. We'll now be only sampling into the subset.
+                sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize());
+                workingBitmap = &subset;
+            } else {
+                workingBitmap = bm;
+            }
+
+            // bm is already locked, but if we had to take a subset, it must be locked also,
+            // so that getPixels() will point to its pixels.
+            SkAutoLockPixels alpWorking(*workingBitmap);
+
+            if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) {
+                return error_return(*bm, "Sampler failed to begin.");
+            }
+
+            // now decode each scanline
+            if (gif->Image.Interlace) {
+                // Iterate over the height of the source data. The sampler will
+                // take care of skipping unneeded rows.
+                GifInterlaceIter iter(innerHeight);
+                for (int y = 0; y < innerHeight; y++) {
+                    if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
+                        gif_warning(*bm, "interlace DGifGetLine");
+                        memset(scanline, fillIndex, innerWidth);
+                        for (; y < innerHeight; y++) {
+                            sampler.sampleInterlaced(scanline, iter.currY());
+                            iter.next();
+                        }
+                        return kPartialSuccess;
+                    }
+                    sampler.sampleInterlaced(scanline, iter.currY());
+                    iter.next();
+                }
+            } else {
+                // easy, non-interlace case
+                const int outHeight = workingBitmap->height();
+                skip_src_rows(gif, scanline, innerWidth, sampler.srcY0());
+                for (int y = 0; y < outHeight; y++) {
+                    if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
+                        gif_warning(*bm, "DGifGetLine");
+                        memset(scanline, fillIndex, innerWidth);
+                        for (; y < outHeight; y++) {
+                            sampler.next(scanline);
+                        }
+                        return kPartialSuccess;
+                    }
+                    // scanline now contains the raw data. Sample it.
+                    sampler.next(scanline);
+                    if (y < outHeight - 1) {
+                        skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1);
+                    }
+                }
+                // skip the rest of the rows (if any)
+                int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1;
+                SkASSERT(read <= innerHeight);
+                skip_src_rows(gif, scanline, innerWidth, innerHeight - read);
+            }
+            sanitize_indexed_bitmap(bm);
+            return kSuccess;
+            } break;
+
+        case EXTENSION_RECORD_TYPE:
+#if GIFLIB_MAJOR < 5
+            if (DGifGetExtension(gif, &temp_save.Function,
+                                 &extData) == GIF_ERROR) {
+#else
+            if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) {
+#endif
+                return error_return(*bm, "DGifGetExtension");
+            }
+
+            while (extData != nullptr) {
+                /* Create an extension block with our data */
+#if GIFLIB_MAJOR < 5
+                if (AddExtensionBlock(&temp_save, extData[0],
+                                      &extData[1]) == GIF_ERROR) {
+#else
+                if (GifAddExtensionBlock(&temp_save.ExtensionBlockCount,
+                                         &temp_save.ExtensionBlocks,
+                                         extFunction,
+                                         extData[0],
+                                         &extData[1]) == GIF_ERROR) {
+#endif
+                    return error_return(*bm, "AddExtensionBlock");
+                }
+                if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
+                    return error_return(*bm, "DGifGetExtensionNext");
+                }
+#if GIFLIB_MAJOR < 5
+                temp_save.Function = 0;
+#endif
+            }
+            break;
+
+        case TERMINATE_RECORD_TYPE:
+            break;
+
+        default:    /* Should be trapped by DGifGetRecordType */
+            break;
+        }
+    } while (recType != TERMINATE_RECORD_TYPE);
+
+    sanitize_indexed_bitmap(bm);
+    return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(GIFImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_gif(SkStreamRewindable* stream) {
+    char buf[GIF_STAMP_LEN];
+    if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
+        if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
+                memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+                memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) {
+    if (is_gif(stream)) {
+        return new SkGIFImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory);
+
+static SkImageDecoder::Format get_format_gif(SkStreamRewindable* stream) {
+    if (is_gif(stream)) {
+        return SkImageDecoder::kGIF_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_gif);
diff --git a/src/images/SkImageDecoder_libico.cpp b/src/images/SkImageDecoder_libico.cpp
new file mode 100644
index 0000000..ff04d74
--- /dev/null
+++ b/src/images/SkImageDecoder_libico.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTypes.h"
+
+class SkICOImageDecoder : public SkImageDecoder {
+public:
+    SkICOImageDecoder();
+
+    Format getFormat() const override {
+        return kICO_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+//read bytes starting from the begin-th index in the buffer
+//read in Intel order, and return an integer
+
+#define readByte(buffer,begin) buffer[begin]
+#define read2Bytes(buffer,begin) buffer[begin]+SkLeftShift(buffer[begin+1],8)
+#define read4Bytes(buffer,begin) buffer[begin]+SkLeftShift(buffer[begin+1],8)+SkLeftShift(buffer[begin+2],16)+SkLeftShift(buffer[begin+3],24)
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkICOImageDecoder::SkICOImageDecoder()
+{
+}
+
+//helpers - my function pointer will call one of these, depending on the bitCount, each time through the inner loop
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+
+
+static int calculateRowBytesFor8888(int w, int bitCount)
+{
+    //  Default rowBytes is w << 2 for kARGB_8888
+    //  In the case of a 4 bit image with an odd width, we need to add some
+    //  so we can go off the end of the drawn bitmap.
+    //  Add 4 to ensure that it is still a multiple of 4.
+    if (4 == bitCount && (w & 0x1)) {
+        return (w + 1) << 2;
+    }
+    //  Otherwise return 0, which will allow it to be calculated automatically.
+    return 0;
+}
+
+SkImageDecoder::Result SkICOImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    auto data = SkCopyStreamToData(stream);
+    if (!data) {
+        return kFailure;
+    }
+
+    const size_t length = data->size();
+    // Check that the buffer is large enough to read the directory header
+    if (length < 6) {
+        return kFailure;
+    }
+
+    unsigned char* buf = (unsigned char*) data->data();
+
+    //these should always be the same - should i use for error checking? - what about files that have some
+    //incorrect values, but still decode properly?
+    int reserved = read2Bytes(buf, 0);    // 0
+    int type = read2Bytes(buf, 2);        // 1
+    if (reserved != 0 || type != 1) {
+        return kFailure;
+    }
+
+    int count = read2Bytes(buf, 4);
+    // Check that there are directory entries
+    if (count < 1) {
+        return kFailure;
+    }
+
+    // Check that buffer is large enough to read directory entries.
+    // We are guaranteed that count is at least 1.  We might as well assume
+    // count is 1 because this deprecated decoder only looks at the first
+    // directory entry.
+    if (length < (size_t)(6 + count*16)) {
+        return kFailure;
+    }
+
+    //skip ahead to the correct header
+    //commented out lines are not used, but if i switch to other read method, need to know how many to skip
+    //otherwise, they could be used for error checking
+    int w = readByte(buf, 6);
+    int h = readByte(buf, 7);
+    SkASSERT(w >= 0 && h >= 0);
+    int colorCount = readByte(buf, 8);
+    //int reservedToo = readByte(buf, 9 + choice*16);   //0
+    //int planes = read2Bytes(buf, 10 + choice*16);       //1 - but often 0
+    //int fakeBitCount = read2Bytes(buf, 12 + choice*16); //should be real - usually 0
+    const size_t size = read4Bytes(buf, 14);           //matters?
+    const size_t offset = read4Bytes(buf, 18);
+    // promote the sum to 64-bits to avoid overflow
+    // Check that buffer is large enough to read image data
+    if (offset > length || size > length || ((uint64_t)offset + size) > length) {
+        return kFailure;
+    }
+
+    // Check to see if this is a PNG image inside the ICO
+    {
+        SkMemoryStream subStream(buf + offset, size, false);
+        SkAutoTDelete<SkImageDecoder> otherDecoder(SkImageDecoder::Factory(&subStream));
+        if (otherDecoder.get() != nullptr) {
+            // Disallow nesting ICO files within one another
+            // FIXME: Can ICO files contain other formats besides PNG?
+            if (otherDecoder->getFormat() == SkImageDecoder::kICO_Format) {
+                return kFailure;
+            }
+            // Set fields on the other decoder to be the same as this one.
+            this->copyFieldsToOther(otherDecoder.get());
+            const Result result = otherDecoder->decode(&subStream, bm, this->getDefaultPref(),
+                                                       mode);
+            // FIXME: Should we just return result here? Is it possible that data that looked like
+            // a subimage was not, but was actually a valid ICO?
+            if (result != kFailure) {
+                return result;
+            }
+        }
+    }
+
+    //int infoSize = read4Bytes(buf, offset);             //40
+    //int width = read4Bytes(buf, offset+4);              //should == w
+    //int height = read4Bytes(buf, offset+8);             //should == 2*h
+    //int planesToo = read2Bytes(buf, offset+12);         //should == 1 (does it?)
+    
+    // For ico images, only a byte is used to store each dimension
+    // 0 is used to represent 256
+    if (w == 0) {
+        w = 256;
+    }
+    if (h == 0) {
+        h = 256;
+    }
+
+    // Check that buffer is large enough to read the bit depth
+    if (length < offset + 16) {
+        return kFailure;
+    }
+    int bitCount = read2Bytes(buf, offset+14);
+
+    void (*placePixel)(const int pixelNo, const unsigned char* buf,
+        const int xorOffset, int& x, int y, const int w,
+        SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) = nullptr;
+    switch (bitCount)
+    {
+        case 1:
+            placePixel = &editPixelBit1;
+            colorCount = 2;
+            break;
+        case 4:
+            placePixel = &editPixelBit4;
+            colorCount = 16;
+            break;
+        case 8:
+            placePixel = &editPixelBit8;
+            colorCount = 256;
+            break;
+        case 24:
+            placePixel = &editPixelBit24;
+            colorCount = 0;
+            break;
+        case 32:
+            placePixel = &editPixelBit32;
+            colorCount = 0;
+            break;
+        default:
+            SkDEBUGF(("Decoding %ibpp is unimplemented\n", bitCount));
+            return kFailure;
+    }
+
+    //these should all be zero, but perhaps are not - need to check
+    //int compression = read4Bytes(buf, offset+16);       //0
+    //int imageSize = read4Bytes(buf, offset+20);         //0 - sometimes has a value
+    //int xPixels = read4Bytes(buf, offset+24);           //0
+    //int yPixels = read4Bytes(buf, offset+28);           //0
+    //int colorsUsed = read4Bytes(buf, offset+32)         //0 - might have an actual value though
+    //int colorsImportant = read4Bytes(buf, offset+36);   //0
+
+    int begin = SkToInt(offset + 40);
+    // Check that the buffer is large enough to read the color table
+    // For bmp-in-icos, there should be 4 bytes per color
+    if (length < (size_t) (begin + 4*colorCount)) {
+        return kFailure;
+    }
+
+    //this array represents the colortable
+    //if i allow other types of bitmaps, it may actually be used as a part of the bitmap
+    SkPMColor* colors = nullptr;
+    int blue, green, red;
+    if (colorCount)
+    {
+        colors = new SkPMColor[colorCount];
+        for (int j = 0; j < colorCount; j++)
+        {
+            //should this be a function - maybe a #define?
+            blue = readByte(buf, begin + 4*j);
+            green = readByte(buf, begin + 4*j + 1);
+            red = readByte(buf, begin + 4*j + 2);
+            colors[j] = SkPackARGB32(0xFF, red & 0xFF, green & 0xFF, blue & 0xFF);
+        }
+    }
+    int bitWidth = w*bitCount;
+    int test = bitWidth & 0x1F;
+    int mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1);    //either 0xFFFFFFFF or 0
+    int lineBitWidth = (bitWidth & 0xFFFFFFE0) + (0x20 & mask);
+    int lineWidth = lineBitWidth/bitCount;
+
+    int xorOffset = begin + colorCount*4;   //beginning of the color bitmap
+                                            //other read method means we will just be here already
+    int andOffset = xorOffset + ((lineWidth*h*bitCount) >> 3);
+
+    /*int */test = w & 0x1F;   //the low 5 bits - we are rounding up to the next 32 (2^5)
+    /*int */mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1);    //either 0xFFFFFFFF or 0
+    int andLineWidth = (w & 0xFFFFFFE0) + (0x20 & mask);
+    //if we allow different Configs, everything is the same til here
+    //change the config, and use different address getter, and place index vs color, and add the color table
+    //FIXME: what is the tradeoff in size?
+    //if the andbitmap (mask) is all zeroes, then we can easily do an index bitmap
+    //however, with small images with large colortables, maybe it's better to still do argb_8888
+
+    bm->setInfo(SkImageInfo::MakeN32Premul(w, h), calculateRowBytesFor8888(w, bitCount));
+
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        delete[] colors;
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(bm, nullptr))
+    {
+        delete[] colors;
+        return kFailure;
+    }
+
+    // The AND mask is a 1-bit alpha mask for each pixel that comes after the
+    // XOR mask in the bmp.  If we check that the largest AND offset is safe,
+    // it should mean all other buffer accesses will be at smaller indices and
+    // will therefore be safe.
+    size_t maxAndOffset = andOffset + ((andLineWidth*(h-1)+(w-1)) >> 3);
+    if (length <= maxAndOffset) {
+        return kFailure;
+    }
+
+    // Here we assert that all reads from the buffer using the XOR offset are
+    // less than the AND offset.  This should be guaranteed based on the above
+    // calculations.
+#ifdef SK_DEBUG
+    int maxPixelNum = lineWidth*(h-1)+w-1;
+    int maxByte;
+    switch (bitCount) {
+        case 1:
+            maxByte = maxPixelNum >> 3;
+            break;
+        case 4:
+            maxByte = maxPixelNum >> 1;
+            break;
+        case 8:
+            maxByte = maxPixelNum;
+            break;
+        case 24:
+            maxByte = maxPixelNum * 3 + 2;
+            break;
+        case 32:
+            maxByte = maxPixelNum * 4 + 3;
+            break;
+        default:
+            SkASSERT(false);
+            return kFailure;
+    }
+    int maxXOROffset = xorOffset + maxByte;
+    SkASSERT(maxXOROffset < andOffset);
+#endif
+
+    SkAutoLockPixels alp(*bm);
+
+    for (int y = 0; y < h; y++)
+    {
+        for (int x = 0; x < w; x++)
+        {
+            //U32* address = bm->getAddr32(x, y);
+
+            //check the alpha bit first, but pass it along to the function to figure out how to deal with it
+            int andPixelNo = andLineWidth*(h-y-1)+x;
+            //only need to get a new alphaByte when x %8 == 0
+            //but that introduces an if and a mod - probably much slower
+            //that's ok, it's just a read of an array, not a stream
+            int alphaByte = readByte(buf, andOffset + (andPixelNo >> 3));
+            int shift = 7 - (andPixelNo & 0x7);
+            int m = 1 << shift;
+
+            int pixelNo = lineWidth*(h-y-1)+x;
+            placePixel(pixelNo, buf, xorOffset, x, y, w, bm, alphaByte, m, shift, colors);
+
+        }
+    }
+
+    delete [] colors;
+    //ensure we haven't read off the end?
+    //of course this doesn't help us if the andOffset was a lie...
+    //return andOffset + (andLineWidth >> 3) <= length;
+    return kSuccess;
+}   //onDecode
+
+//function to place the pixel, determined by the bitCount
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+    // note that this should be the same as/similar to the AND bitmap
+    SkPMColor* address = bm->getAddr32(x,y);
+    int byte = readByte(buf, xorOffset + (pixelNo >> 3));
+    int colorBit;
+    int alphaBit;
+    // Read all of the bits in this byte.
+    int i = x + 8;
+    // Pin to the width so we do not write outside the bounds of
+    // our color table.
+    i = i > w ? w : i;
+    // While loop to check all 8 bits individually.
+    while (x < i)
+    {
+
+        colorBit = (byte & m) >> shift;
+        alphaBit = (alphaByte & m) >> shift;
+        *address = (alphaBit-1)&(colors[colorBit]);
+        x++;
+        // setup for the next pixel
+        address = address + 1;
+        m = m >> 1;
+        shift -= 1;
+    }
+    x--;
+}
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+    SkPMColor* address = bm->getAddr32(x, y);
+    int byte = readByte(buf, xorOffset + (pixelNo >> 1));
+    int pixel = (byte >> 4) & 0xF;
+    int alphaBit = (alphaByte & m) >> shift;
+    *address = (alphaBit-1)&(colors[pixel]);
+    x++;
+    //if w is odd, x may be the same as w, which means we are writing to an unused portion of the bitmap
+    //but that's okay, since i've added an extra rowByte for just this purpose
+    address = address + 1;
+    pixel = byte & 0xF;
+    m = m >> 1;
+    alphaBit = (alphaByte & m) >> (shift-1);
+    //speed up trick here
+    *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+    SkPMColor* address = bm->getAddr32(x, y);
+    int pixel = readByte(buf, xorOffset + pixelNo);
+    int alphaBit = (alphaByte & m) >> shift;
+    *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+    SkPMColor* address = bm->getAddr32(x, y);
+    int blue = readByte(buf, xorOffset + 3*pixelNo);
+    int green = readByte(buf, xorOffset + 3*pixelNo + 1);
+    int red = readByte(buf, xorOffset + 3*pixelNo + 2);
+    int alphaBit = (alphaByte & m) >> shift;
+    //alphaBit == 1 => alpha = 0
+    int alpha = (alphaBit-1) & 0xFF;
+    *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+            const int xorOffset, int& x, int y, const int w,
+            SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+    SkPMColor* address = bm->getAddr32(x, y);
+    int blue = readByte(buf, xorOffset + 4*pixelNo);
+    int green = readByte(buf, xorOffset + 4*pixelNo + 1);
+    int red = readByte(buf, xorOffset + 4*pixelNo + 2);
+    int alphaBit = (alphaByte & m) >> shift;
+#if 1 // don't trust the alphaBit for 32bit images <mrr>
+    alphaBit = 0;
+#endif
+    int alpha = readByte(buf, xorOffset + 4*pixelNo + 3) & ((alphaBit-1)&0xFF);
+    *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(ICOImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_ico(SkStreamRewindable* stream) {
+    // Check to see if the first four bytes are 0,0,1,0
+    // FIXME: Is that required and sufficient?
+    char buf[4];
+    if (stream->read((void*)buf, 4) != 4) {
+        return false;
+    }
+    int reserved = read2Bytes(buf, 0);
+    int type = read2Bytes(buf, 2);
+    return 0 == reserved && 1 == type;
+}
+
+static SkImageDecoder* sk_libico_dfactory(SkStreamRewindable* stream) {
+    if (is_ico(stream)) {
+        return new SkICOImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libico_dfactory);
+
+static SkImageDecoder::Format get_format_ico(SkStreamRewindable* stream) {
+    if (is_ico(stream)) {
+        return SkImageDecoder::kICO_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_ico);
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index fd10bdb..89bfefc 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -6,10 +6,13 @@
  */
 
 
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkJpegUtility.h"
 #include "SkColorPriv.h"
 #include "SkDither.h"
+#include "SkMSAN.h"
+#include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkTime.h"
@@ -25,12 +28,730 @@
     #include "jerror.h"
 }
 
-// These enable timing code that report milliseconds for an encoding
+// These enable timing code that report milliseconds for an encoding/decoding
 //#define TIME_ENCODE
+//#define TIME_DECODE
 
 // this enables our rgb->yuv code, which is faster than libjpeg on ARM
 #define WE_CONVERT_TO_YUV
 
+// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
+// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
+
+#define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS true
+#define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS true
+SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderWarnings,
+                "images.jpeg.suppressDecoderWarnings",
+                DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS,
+                "Suppress most JPG warnings when calling decode functions.");
+SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderErrors,
+                "images.jpeg.suppressDecoderErrors",
+                DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS,
+                "Suppress most JPG error messages when decode "
+                "function fails.");
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static void do_nothing_emit_message(jpeg_common_struct*, int) {
+    /* do nothing */
+}
+static void do_nothing_output_message(j_common_ptr) {
+    /* do nothing */
+}
+
+static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) {
+    SkASSERT(cinfo != nullptr);
+    SkASSERT(src_mgr != nullptr);
+    jpeg_create_decompress(cinfo);
+    cinfo->src = src_mgr;
+    /* To suppress warnings with a SK_DEBUG binary, set the
+     * environment variable "skia_images_jpeg_suppressDecoderWarnings"
+     * to "true".  Inside a program that links to skia:
+     * SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true); */
+    if (c_suppressJPEGImageDecoderWarnings) {
+        cinfo->err->emit_message = &do_nothing_emit_message;
+    }
+    /* To suppress error messages with a SK_DEBUG binary, set the
+     * environment variable "skia_images_jpeg_suppressDecoderErrors"
+     * to "true".  Inside a program that links to skia:
+     * SK_CONF_SET("images.jpeg.suppressDecoderErrors", true); */
+    if (c_suppressJPEGImageDecoderErrors) {
+        cinfo->err->output_message = &do_nothing_output_message;
+    }
+}
+
+class SkJPEGImageDecoder : public SkImageDecoder {
+public:
+
+    Format getFormat() const override {
+        return kJPEG_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+    bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3],
+                            void* planes[3], size_t rowBytes[3],
+                            SkYUVColorSpace* colorSpace) override;
+
+private:
+
+    /**
+     *  Determine the appropriate bitmap colortype and out_color_space based on
+     *  both the preference of the caller and the jpeg_color_space on the
+     *  jpeg_decompress_struct passed in.
+     *  Must be called after jpeg_read_header.
+     */
+    SkColorType getBitmapColorType(jpeg_decompress_struct*);
+
+    typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+/* Automatically clean up after throwing an exception */
+class JPEGAutoClean {
+public:
+    JPEGAutoClean(): cinfo_ptr(nullptr) {}
+    ~JPEGAutoClean() {
+        if (cinfo_ptr) {
+            jpeg_destroy_decompress(cinfo_ptr);
+        }
+    }
+    void set(jpeg_decompress_struct* info) {
+        cinfo_ptr = info;
+    }
+private:
+    jpeg_decompress_struct* cinfo_ptr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/*  If we need to better match the request, we might examine the image and
+     output dimensions, and determine if the downsampling jpeg provided is
+     not sufficient. If so, we can recompute a modified sampleSize value to
+     make up the difference.
+
+     To skip this additional scaling, just set sampleSize = 1; below.
+ */
+static int recompute_sampleSize(int sampleSize,
+                                const jpeg_decompress_struct& cinfo) {
+    return sampleSize * cinfo.output_width / cinfo.image_width;
+}
+
+static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
+    /* These are initialized to 0, so if they have non-zero values, we assume
+       they are "valid" (i.e. have been computed by libjpeg)
+     */
+    return 0 != cinfo.output_width && 0 != cinfo.output_height;
+}
+
+static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
+    for (int i = 0; i < count; i++) {
+        JSAMPLE* rowptr = (JSAMPLE*)buffer;
+        int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
+        if (1 != row_count) {
+            return false;
+        }
+    }
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This guy exists just to aid in debugging, as it allows debuggers to just
+// set a break-point in one place to see all error exists.
+static void print_jpeg_decoder_errors(const jpeg_decompress_struct& cinfo,
+                         int width, int height, const char caller[]) {
+    if (!(c_suppressJPEGImageDecoderErrors)) {
+        char buffer[JMSG_LENGTH_MAX];
+        cinfo.err->format_message((const j_common_ptr)&cinfo, buffer);
+        SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n",
+                 cinfo.err->msg_code, buffer, caller, width, height);
+    }
+}
+
+static bool return_false(const jpeg_decompress_struct& cinfo,
+                         const char caller[]) {
+    print_jpeg_decoder_errors(cinfo, 0, 0, caller);
+    return false;
+}
+
+static SkImageDecoder::Result return_failure(const jpeg_decompress_struct& cinfo,
+                                             const SkBitmap& bm, const char caller[]) {
+    print_jpeg_decoder_errors(cinfo, bm.width(), bm.height(), caller);
+    return SkImageDecoder::kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Convert a scanline of CMYK samples to RGBX in place. Note that this
+// method moves the "scanline" pointer in its processing
+static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) {
+    // At this point we've received CMYK pixels from libjpeg. We
+    // perform a crude conversion to RGB (based on the formulae
+    // from easyrgb.com):
+    //  CMYK -> CMY
+    //    C = ( C * (1 - K) + K )      // for each CMY component
+    //  CMY -> RGB
+    //    R = ( 1 - C ) * 255          // for each RGB component
+    // Unfortunately we are seeing inverted CMYK so all the original terms
+    // are 1-. This yields:
+    //  CMYK -> CMY
+    //    C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K
+    // The conversion from CMY->RGB remains the same
+    for (unsigned int x = 0; x < width; ++x, scanline += 4) {
+        scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]);
+        scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]);
+        scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]);
+        scanline[3] = 255;
+    }
+}
+
+/**
+ *  Common code for setting the error manager.
+ */
+static void set_error_mgr(jpeg_decompress_struct* cinfo, skjpeg_error_mgr* errorManager) {
+    SkASSERT(cinfo != nullptr);
+    SkASSERT(errorManager != nullptr);
+    cinfo->err = jpeg_std_error(errorManager);
+    errorManager->error_exit = skjpeg_error_exit;
+}
+
+/**
+ * Common code for setting the dct method.
+ */
+static void set_dct_method(const SkImageDecoder& decoder, jpeg_decompress_struct* cinfo) {
+    SkASSERT(cinfo != nullptr);
+    cinfo->dct_method = JDCT_ISLOW;
+}
+
+SkColorType SkJPEGImageDecoder::getBitmapColorType(jpeg_decompress_struct* cinfo) {
+    SkASSERT(cinfo != nullptr);
+
+    SrcDepth srcDepth = k32Bit_SrcDepth;
+    if (JCS_GRAYSCALE == cinfo->jpeg_color_space) {
+        srcDepth = k8BitGray_SrcDepth;
+    }
+
+    SkColorType colorType = this->getPrefColorType(srcDepth, /*hasAlpha*/ false);
+    switch (colorType) {
+        case kAlpha_8_SkColorType:
+            // Only respect A8 colortype if the original is grayscale,
+            // in which case we will treat the grayscale as alpha
+            // values.
+            if (cinfo->jpeg_color_space != JCS_GRAYSCALE) {
+                colorType = kN32_SkColorType;
+            }
+            break;
+        case kN32_SkColorType:
+            // Fall through.
+        case kARGB_4444_SkColorType:
+            // Fall through.
+        case kRGB_565_SkColorType:
+            // These are acceptable destination colortypes.
+            break;
+        default:
+            // Force all other colortypes to 8888.
+            colorType = kN32_SkColorType;
+            break;
+    }
+
+    switch (cinfo->jpeg_color_space) {
+        case JCS_CMYK:
+            // Fall through.
+        case JCS_YCCK:
+            // libjpeg cannot convert from CMYK or YCCK to RGB - here we set up
+            // so libjpeg will give us CMYK samples back and we will later
+            // manually convert them to RGB
+            cinfo->out_color_space = JCS_CMYK;
+            break;
+        case JCS_GRAYSCALE:
+            if (kAlpha_8_SkColorType == colorType) {
+                cinfo->out_color_space = JCS_GRAYSCALE;
+                break;
+            }
+            // The data is JCS_GRAYSCALE, but the caller wants some sort of RGB
+            // colortype. Fall through to set to the default.
+        default:
+            cinfo->out_color_space = JCS_RGB;
+            break;
+    }
+    return colorType;
+}
+
+/**
+ *  Based on the colortype and dither mode, adjust out_color_space and
+ *  dither_mode of cinfo. Only does work in ANDROID_RGB
+ */
+static void adjust_out_color_space_and_dither(jpeg_decompress_struct* cinfo,
+                                              SkColorType colorType,
+                                              const SkImageDecoder& decoder) {
+    SkASSERT(cinfo != nullptr);
+#ifdef ANDROID_RGB
+    cinfo->dither_mode = JDITHER_NONE;
+    if (JCS_CMYK == cinfo->out_color_space) {
+        return;
+    }
+    switch (colorType) {
+        case kN32_SkColorType:
+            cinfo->out_color_space = JCS_RGBA_8888;
+            break;
+        case kRGB_565_SkColorType:
+            cinfo->out_color_space = JCS_RGB_565;
+            if (decoder.getDitherImage()) {
+                cinfo->dither_mode = JDITHER_ORDERED;
+            }
+            break;
+        default:
+            break;
+    }
+#endif
+}
+
+/**
+   Sets all pixels in given bitmap to SK_ColorWHITE for all rows >= y.
+   Used when decoding fails partway through reading scanlines to fill
+   remaining lines. */
+static void fill_below_level(int y, SkBitmap* bitmap) {
+    SkIRect rect = SkIRect::MakeLTRB(0, y, bitmap->width(), bitmap->height());
+    SkCanvas canvas(*bitmap);
+    canvas.clipRect(SkRect::Make(rect));
+    canvas.drawColor(SK_ColorWHITE);
+}
+
+/**
+ *  Get the config and bytes per pixel of the source data. Return
+ *  whether the data is supported.
+ */
+static bool get_src_config(const jpeg_decompress_struct& cinfo,
+                           SkScaledBitmapSampler::SrcConfig* sc,
+                           int* srcBytesPerPixel) {
+    SkASSERT(sc != nullptr && srcBytesPerPixel != nullptr);
+    if (JCS_CMYK == cinfo.out_color_space) {
+        // In this case we will manually convert the CMYK values to RGB
+        *sc = SkScaledBitmapSampler::kRGBX;
+        // The CMYK work-around relies on 4 components per pixel here
+        *srcBytesPerPixel = 4;
+    } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
+        *sc = SkScaledBitmapSampler::kRGB;
+        *srcBytesPerPixel = 3;
+#ifdef ANDROID_RGB
+    } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
+        *sc = SkScaledBitmapSampler::kRGBX;
+        *srcBytesPerPixel = 4;
+    } else if (JCS_RGB_565 == cinfo.out_color_space) {
+        *sc = SkScaledBitmapSampler::kRGB_565;
+        *srcBytesPerPixel = 2;
+#endif
+    } else if (1 == cinfo.out_color_components &&
+               JCS_GRAYSCALE == cinfo.out_color_space) {
+        *sc = SkScaledBitmapSampler::kGray;
+        *srcBytesPerPixel = 1;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+SkImageDecoder::Result SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+#ifdef TIME_DECODE
+    SkAutoTime atm("JPEG Decode");
+#endif
+
+    JPEGAutoClean autoClean;
+
+    jpeg_decompress_struct  cinfo;
+    skjpeg_source_mgr       srcManager(stream, this);
+
+    skjpeg_error_mgr errorManager;
+    set_error_mgr(&cinfo, &errorManager);
+
+    // All objects need to be instantiated before this setjmp call so that
+    // they will be cleaned up properly if an error occurs.
+    if (setjmp(errorManager.fJmpBuf)) {
+        return return_failure(cinfo, *bm, "setjmp");
+    }
+
+    initialize_info(&cinfo, &srcManager);
+    autoClean.set(&cinfo);
+
+    int status = jpeg_read_header(&cinfo, true);
+    if (status != JPEG_HEADER_OK) {
+        return return_failure(cinfo, *bm, "read_header");
+    }
+
+    /*  Try to fulfill the requested sampleSize. Since jpeg can do it (when it
+        can) much faster that we, just use their num/denom api to approximate
+        the size.
+    */
+    int sampleSize = this->getSampleSize();
+
+    set_dct_method(*this, &cinfo);
+
+    SkASSERT(1 == cinfo.scale_num);
+    cinfo.scale_denom = sampleSize;
+
+    const SkColorType colorType = this->getBitmapColorType(&cinfo);
+    const SkAlphaType alphaType = kAlpha_8_SkColorType == colorType ?
+                                      kPremul_SkAlphaType : kOpaque_SkAlphaType;
+
+    adjust_out_color_space_and_dither(&cinfo, colorType, *this);
+
+    if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
+        // Assume an A8 bitmap is not opaque to avoid the check of each
+        // individual pixel. It is very unlikely to be opaque, since
+        // an opaque A8 bitmap would not be very interesting.
+        // Otherwise, a jpeg image is opaque.
+        bool success = bm->setInfo(SkImageInfo::Make(cinfo.image_width, cinfo.image_height,
+                                                     colorType, alphaType));
+        return success ? kSuccess : kFailure;
+    }
+
+    /*  image_width and image_height are the original dimensions, available
+        after jpeg_read_header(). To see the scaled dimensions, we have to call
+        jpeg_start_decompress(), and then read output_width and output_height.
+    */
+    if (!jpeg_start_decompress(&cinfo)) {
+        /*  If we failed here, we may still have enough information to return
+            to the caller if they just wanted (subsampled bounds). If sampleSize
+            was 1, then we would have already returned. Thus we just check if
+            we're in kDecodeBounds_Mode, and that we have valid output sizes.
+
+            One reason to fail here is that we have insufficient stream data
+            to complete the setup. However, output dimensions seem to get
+            computed very early, which is why this special check can pay off.
+         */
+        if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
+            SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
+                                       recompute_sampleSize(sampleSize, cinfo));
+            // Assume an A8 bitmap is not opaque to avoid the check of each
+            // individual pixel. It is very unlikely to be opaque, since
+            // an opaque A8 bitmap would not be very interesting.
+            // Otherwise, a jpeg image is opaque.
+            bool success = bm->setInfo(SkImageInfo::Make(smpl.scaledWidth(), smpl.scaledHeight(),
+                                                         colorType, alphaType));
+            return success ? kSuccess : kFailure;
+        } else {
+            return return_failure(cinfo, *bm, "start_decompress");
+        }
+    }
+    sampleSize = recompute_sampleSize(sampleSize, cinfo);
+
+    SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
+    // Assume an A8 bitmap is not opaque to avoid the check of each
+    // individual pixel. It is very unlikely to be opaque, since
+    // an opaque A8 bitmap would not be very interesting.
+    // Otherwise, a jpeg image is opaque.
+    bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+                                  colorType, alphaType));
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return return_failure(cinfo, *bm, "allocPixelRef");
+    }
+
+    SkAutoLockPixels alp(*bm);
+
+#ifdef ANDROID_RGB
+    /* short-circuit the SkScaledBitmapSampler when possible, as this gives
+       a significant performance boost.
+    */
+    if (sampleSize == 1 &&
+        ((kN32_SkColorType == colorType && cinfo.out_color_space == JCS_RGBA_8888) ||
+         (kRGB_565_SkColorType == colorType && cinfo.out_color_space == JCS_RGB_565)))
+    {
+        JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
+        INT32 const bpr =  bm->rowBytes();
+
+        while (cinfo.output_scanline < cinfo.output_height) {
+            int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+            if (0 == row_count) {
+                // if row_count == 0, then we didn't get a scanline,
+                // so return early.  We will return a partial image.
+                fill_below_level(cinfo.output_scanline, bm);
+                cinfo.output_scanline = cinfo.output_height;
+                jpeg_finish_decompress(&cinfo);
+                return kPartialSuccess;
+            }
+            if (this->shouldCancelDecode()) {
+                return return_failure(cinfo, *bm, "shouldCancelDecode");
+            }
+            rowptr += bpr;
+        }
+        jpeg_finish_decompress(&cinfo);
+        return kSuccess;
+    }
+#endif
+
+    // check for supported formats
+    SkScaledBitmapSampler::SrcConfig sc;
+    int srcBytesPerPixel;
+
+    if (!get_src_config(cinfo, &sc, &srcBytesPerPixel)) {
+        return return_failure(cinfo, *bm, "jpeg colorspace");
+    }
+
+    if (!sampler.begin(bm, sc, *this)) {
+        return return_failure(cinfo, *bm, "sampler.begin");
+    }
+
+    SkAutoTMalloc<uint8_t> srcStorage(cinfo.output_width * srcBytesPerPixel);
+    uint8_t* srcRow = srcStorage.get();
+
+    //  Possibly skip initial rows [sampler.srcY0]
+    if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
+        return return_failure(cinfo, *bm, "skip rows");
+    }
+
+    // now loop through scanlines until y == bm->height() - 1
+    for (int y = 0;; y++) {
+        JSAMPLE* rowptr = (JSAMPLE*)srcRow;
+        int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+        sk_msan_mark_initialized(srcRow, srcRow + cinfo.output_width * srcBytesPerPixel,
+                                 "skbug.com/4550");
+        if (0 == row_count) {
+            // if row_count == 0, then we didn't get a scanline,
+            // so return early.  We will return a partial image.
+            fill_below_level(y, bm);
+            cinfo.output_scanline = cinfo.output_height;
+            jpeg_finish_decompress(&cinfo);
+            return kPartialSuccess;
+        }
+        if (this->shouldCancelDecode()) {
+            return return_failure(cinfo, *bm, "shouldCancelDecode");
+        }
+
+        if (JCS_CMYK == cinfo.out_color_space) {
+            convert_CMYK_to_RGB(srcRow, cinfo.output_width);
+        }
+
+
+        sampler.next(srcRow);
+        if (bm->height() - 1 == y) {
+            // we're done
+            break;
+        }
+
+        if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
+            return return_failure(cinfo, *bm, "skip rows");
+        }
+    }
+
+    // we formally skip the rest, so we don't get a complaint from libjpeg
+    if (!skip_src_rows(&cinfo, srcRow,
+                       cinfo.output_height - cinfo.output_scanline)) {
+        return return_failure(cinfo, *bm, "skip rows");
+    }
+    jpeg_finish_decompress(&cinfo);
+
+    return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enum SizeType {
+    kSizeForMemoryAllocation_SizeType,
+    kActualSize_SizeType
+};
+
+static SkISize compute_yuv_size(const jpeg_decompress_struct& info, int component,
+                                SizeType sizeType) {
+    if (sizeType == kSizeForMemoryAllocation_SizeType) {
+        return SkISize::Make(info.cur_comp_info[component]->width_in_blocks * DCTSIZE,
+                             info.cur_comp_info[component]->height_in_blocks * DCTSIZE);
+    }
+    return SkISize::Make(info.cur_comp_info[component]->downsampled_width,
+                         info.cur_comp_info[component]->downsampled_height);
+}
+
+static bool appears_to_be_yuv(const jpeg_decompress_struct& info) {
+    return (info.jpeg_color_space == JCS_YCbCr)
+        && (DCTSIZE == 8)
+        && (info.num_components == 3)
+        && (info.comps_in_scan >= info.num_components)
+        && (info.scale_denom <= 8)
+        && (info.cur_comp_info[0])
+        && (info.cur_comp_info[1])
+        && (info.cur_comp_info[2])
+        && (info.cur_comp_info[1]->h_samp_factor == 1)
+        && (info.cur_comp_info[1]->v_samp_factor == 1)
+        && (info.cur_comp_info[2]->h_samp_factor == 1)
+        && (info.cur_comp_info[2]->v_samp_factor == 1);
+}
+
+static void update_components_sizes(const jpeg_decompress_struct& cinfo, SkISize componentSizes[3],
+                                    SizeType sizeType) {
+    SkASSERT(appears_to_be_yuv(cinfo));
+    for (int i = 0; i < 3; ++i) {
+        componentSizes[i] = compute_yuv_size(cinfo, i, sizeType);
+    }
+}
+
+static bool output_raw_data(jpeg_decompress_struct& cinfo, void* planes[3], size_t rowBytes[3]) {
+    SkASSERT(appears_to_be_yuv(cinfo));
+    // U size and V size have to be the same if we're calling output_raw_data()
+    SkISize uvSize = compute_yuv_size(cinfo, 1, kSizeForMemoryAllocation_SizeType);
+    SkASSERT(uvSize == compute_yuv_size(cinfo, 2, kSizeForMemoryAllocation_SizeType));
+
+    JSAMPARRAY bufferraw[3];
+    JSAMPROW bufferraw2[32];
+    bufferraw[0] = &bufferraw2[0]; // Y channel rows (8 or 16)
+    bufferraw[1] = &bufferraw2[16]; // U channel rows (8)
+    bufferraw[2] = &bufferraw2[24]; // V channel rows (8)
+    int yWidth = cinfo.output_width;
+    int yHeight = cinfo.output_height;
+    int yMaxH = yHeight - 1;
+    int v = cinfo.cur_comp_info[0]->v_samp_factor;
+    int uvMaxH = uvSize.height() - 1;
+    JSAMPROW outputY = static_cast<JSAMPROW>(planes[0]);
+    JSAMPROW outputU = static_cast<JSAMPROW>(planes[1]);
+    JSAMPROW outputV = static_cast<JSAMPROW>(planes[2]);
+    size_t rowBytesY = rowBytes[0];
+    size_t rowBytesU = rowBytes[1];
+    size_t rowBytesV = rowBytes[2];
+
+    int yScanlinesToRead = DCTSIZE * v;
+    SkAutoMalloc lastRowStorage(rowBytesY * 4);
+    JSAMPROW yLastRow = (JSAMPROW)lastRowStorage.get();
+    JSAMPROW uLastRow = yLastRow + rowBytesY;
+    JSAMPROW vLastRow = uLastRow + rowBytesY;
+    JSAMPROW dummyRow = vLastRow + rowBytesY;
+
+    while (cinfo.output_scanline < cinfo.output_height) {
+        // Request 8 or 16 scanlines: returns 0 or more scanlines.
+        bool hasYLastRow(false), hasUVLastRow(false);
+        // Assign 8 or 16 rows of memory to read the Y channel.
+        for (int i = 0; i < yScanlinesToRead; ++i) {
+            int scanline = (cinfo.output_scanline + i);
+            if (scanline < yMaxH) {
+                bufferraw2[i] = &outputY[scanline * rowBytesY];
+            } else if (scanline == yMaxH) {
+                bufferraw2[i] = yLastRow;
+                hasYLastRow = true;
+            } else {
+                bufferraw2[i] = dummyRow;
+            }
+        }
+        int scaledScanline = cinfo.output_scanline / v;
+        // Assign 8 rows of memory to read the U and V channels.
+        for (int i = 0; i < 8; ++i) {
+            int scanline = (scaledScanline + i);
+            if (scanline < uvMaxH) {
+                bufferraw2[16 + i] = &outputU[scanline * rowBytesU];
+                bufferraw2[24 + i] = &outputV[scanline * rowBytesV];
+            } else if (scanline == uvMaxH) {
+                bufferraw2[16 + i] = uLastRow;
+                bufferraw2[24 + i] = vLastRow;
+                hasUVLastRow = true;
+            } else {
+                bufferraw2[16 + i] = dummyRow;
+                bufferraw2[24 + i] = dummyRow;
+            }
+        }
+        JDIMENSION scanlinesRead = jpeg_read_raw_data(&cinfo, bufferraw, yScanlinesToRead);
+
+        if (scanlinesRead == 0) {
+            return false;
+        }
+
+        if (hasYLastRow) {
+            memcpy(&outputY[yMaxH * rowBytesY], yLastRow, yWidth);
+        }
+        if (hasUVLastRow) {
+            memcpy(&outputU[uvMaxH * rowBytesU], uLastRow, uvSize.width());
+            memcpy(&outputV[uvMaxH * rowBytesV], vLastRow, uvSize.width());
+        }
+    }
+
+    cinfo.output_scanline = SkMin32(cinfo.output_scanline, cinfo.output_height);
+
+    return true;
+}
+
+bool SkJPEGImageDecoder::onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3],
+                                            void* planes[3], size_t rowBytes[3],
+                                            SkYUVColorSpace* colorSpace) {
+#ifdef TIME_DECODE
+    SkAutoTime atm("JPEG YUV8 Decode");
+#endif
+    if (this->getSampleSize() != 1) {
+        return false; // Resizing not supported
+    }
+
+    JPEGAutoClean autoClean;
+
+    jpeg_decompress_struct  cinfo;
+    skjpeg_source_mgr       srcManager(stream, this);
+
+    skjpeg_error_mgr errorManager;
+    set_error_mgr(&cinfo, &errorManager);
+
+    // All objects need to be instantiated before this setjmp call so that
+    // they will be cleaned up properly if an error occurs.
+    if (setjmp(errorManager.fJmpBuf)) {
+        return return_false(cinfo, "setjmp YUV8");
+    }
+
+    initialize_info(&cinfo, &srcManager);
+    autoClean.set(&cinfo);
+
+    int status = jpeg_read_header(&cinfo, true);
+    if (status != JPEG_HEADER_OK) {
+        return return_false(cinfo, "read_header YUV8");
+    }
+
+    if (!appears_to_be_yuv(cinfo)) {
+        // It's not an error to not be encoded in YUV, so no need to use return_false()
+        return false;
+    }
+
+    cinfo.out_color_space = JCS_YCbCr;
+    cinfo.raw_data_out = TRUE;
+
+    if (!planes || !planes[0] || !rowBytes || !rowBytes[0]) { // Compute size only
+        update_components_sizes(cinfo, componentSizes, kSizeForMemoryAllocation_SizeType);
+        return true;
+    }
+
+    set_dct_method(*this, &cinfo);
+
+    SkASSERT(1 == cinfo.scale_num);
+    cinfo.scale_denom = 1;
+
+#ifdef ANDROID_RGB
+    cinfo.dither_mode = JDITHER_NONE;
+#endif
+
+    /*  image_width and image_height are the original dimensions, available
+        after jpeg_read_header(). To see the scaled dimensions, we have to call
+        jpeg_start_decompress(), and then read output_width and output_height.
+    */
+    if (!jpeg_start_decompress(&cinfo)) {
+        return return_false(cinfo, "start_decompress YUV8");
+    }
+
+    // Seems like jpeg_start_decompress is updating our opinion of whether cinfo represents YUV.
+    // Again, not really an error.
+    if (!appears_to_be_yuv(cinfo)) {
+        return false;
+    }
+
+    if (!output_raw_data(cinfo, planes, rowBytes)) {
+        return return_false(cinfo, "output_raw_data");
+    }
+
+    update_components_sizes(cinfo, componentSizes, kActualSize_SizeType);
+    jpeg_finish_decompress(&cinfo);
+
+    if (nullptr != colorSpace) {
+        *colorSpace = kJPEG_SkYUVColorSpace;
+    }
+
+    return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkColorPriv.h"
@@ -272,11 +993,45 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(JPEGImageDecoder);
 DEFINE_ENCODER_CREATOR(JPEGImageEncoder);
 ///////////////////////////////////////////////////////////////////////////////
 
+static bool is_jpeg(SkStreamRewindable* stream) {
+    static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF };
+    static const size_t HEADER_SIZE = sizeof(gHeader);
+
+    char buffer[HEADER_SIZE];
+    size_t len = stream->read(buffer, HEADER_SIZE);
+
+    if (len != HEADER_SIZE) {
+        return false;   // can't read enough
+    }
+    if (memcmp(buffer, gHeader, HEADER_SIZE)) {
+        return false;
+    }
+    return true;
+}
+
+
+static SkImageDecoder* sk_libjpeg_dfactory(SkStreamRewindable* stream) {
+    if (is_jpeg(stream)) {
+        return new SkJPEGImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder::Format get_format_jpeg(SkStreamRewindable* stream) {
+    if (is_jpeg(stream)) {
+        return SkImageDecoder::kJPEG_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
 static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) {
     return (SkImageEncoder::kJPEG_Type == t) ? new SkJPEGImageEncoder : nullptr;
 }
 
+static SkImageDecoder_DecodeReg gDReg(sk_libjpeg_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_jpeg);
 static SkImageEncoder_EncodeReg gEReg(sk_libjpeg_efactory);
diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp
index c3df5d1..cd8152a 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -5,12 +5,14 @@
  * found in the LICENSE file.
  */
 
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkColor.h"
 #include "SkColorPriv.h"
 #include "SkDither.h"
 #include "SkMath.h"
 #include "SkRTConf.h"
+#include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkUtils.h"
@@ -42,10 +44,88 @@
                 "Suppress most PNG warnings when calling image decode "
                 "functions.");
 
-///////////////////////////////////////////////////////////////////////////////
+class SkPNGImageIndex {
+public:
+    // Takes ownership of stream.
+    SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
+        : fStream(stream)
+        , fPng_ptr(png_ptr)
+        , fInfo_ptr(info_ptr)
+        , fColorType(kUnknown_SkColorType) {
+        SkASSERT(stream != nullptr);
+    }
+    ~SkPNGImageIndex() {
+        if (fPng_ptr) {
+            png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
+        }
+    }
 
-#include "SkColorPriv.h"
-#include "SkUnPreMultiply.h"
+    SkAutoTDelete<SkStreamRewindable>   fStream;
+    png_structp                         fPng_ptr;
+    png_infop                           fInfo_ptr;
+    SkColorType                         fColorType;
+};
+
+class SkPNGImageDecoder : public SkImageDecoder {
+public:
+    SkPNGImageDecoder() {
+        fImageIndex = nullptr;
+    }
+    Format getFormat() const override {
+        return kPNG_Format;
+    }
+
+    virtual ~SkPNGImageDecoder() { delete fImageIndex; }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+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, int bitDepth,
+                       bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
+                       SkColorTable **colorTablep);
+    bool getBitmapColorType(png_structp, png_infop, SkColorType*, bool* hasAlpha,
+                            SkPMColor* theTranspColor);
+
+    typedef SkImageDecoder INHERITED;
+};
+
+#ifndef png_jmpbuf
+#  define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
+#endif
+
+#define PNG_BYTES_TO_CHECK 4
+
+/* Automatically clean up after throwing an exception */
+struct PNGAutoClean {
+    PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
+    ~PNGAutoClean() {
+        png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+    }
+private:
+    png_structp png_ptr;
+    png_infop info_ptr;
+};
+
+static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
+    SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
+    size_t bytes = sk_stream->read(data, length);
+    if (bytes != length) {
+        png_error(png_ptr, "Read Error!");
+    }
+}
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
+    SkPngChunkReader* peeker = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
+    // readChunk() returning true means continue decoding
+    return peeker->readChunk((const char*)chunk->name, chunk->data, chunk->size) ?
+            1 : -1;
+}
+#endif
 
 static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
     if (!c_suppressPNGImageDecoderWarnings) {
@@ -54,6 +134,577 @@
     longjmp(png_jmpbuf(png_ptr), 1);
 }
 
+static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
+    for (int i = 0; i < count; i++) {
+        uint8_t* tmp = storage;
+        png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+    }
+}
+
+static bool pos_le(int value, int max) {
+    return value > 0 && value <= max;
+}
+
+static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
+    SkASSERT(bm->colorType() == kN32_SkColorType);
+
+    bool reallyHasAlpha = false;
+
+    for (int y = bm->height() - 1; y >= 0; --y) {
+        SkPMColor* p = bm->getAddr32(0, y);
+        for (int x = bm->width() - 1; x >= 0; --x) {
+            if (match == *p) {
+                *p = 0;
+                reallyHasAlpha = true;
+            }
+            p += 1;
+        }
+    }
+    return reallyHasAlpha;
+}
+
+static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) {
+    switch (dstColorType) {
+        case kN32_SkColorType:
+        case kARGB_4444_SkColorType:
+            return true;
+        case kRGB_565_SkColorType:
+            // only return true if the src is opaque (since 565 is opaque)
+            return !srcHasAlpha;
+        default:
+            return false;
+    }
+}
+
+// call only if color_type is PALETTE. Returns true if the ctable has alpha
+static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
+    png_bytep trans;
+    int num_trans;
+
+    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+        png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, nullptr);
+        return num_trans > 0;
+    }
+    return false;
+}
+
+void do_nothing_warning_fn(png_structp, png_const_charp) {
+    /* do nothing */
+}
+
+bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
+                                     png_infop *info_ptrp) {
+    /* Create and initialize the png_struct with the desired error handler
+    * functions.  If you want to use the default stderr and longjump method,
+    * you can supply nullptr for the last three parameters.  We also supply the
+    * the compiler header file version, so that we know if the application
+    * was compiled with a compatible version of the library.  */
+
+    png_error_ptr user_warning_fn =
+        (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : nullptr;
+    /* nullptr means to leave as default library behavior. */
+    /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
+    /* To suppress warnings with a SK_DEBUG binary, set the
+     * environment variable "skia_images_png_suppressDecoderWarnings"
+     * to "true".  Inside a program that links to skia:
+     * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
+
+    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+        nullptr, sk_error_fn, user_warning_fn);
+    //   png_voidp user_error_ptr, user_error_fn, user_warning_fn);
+    if (png_ptr == nullptr) {
+        return false;
+    }
+
+    *png_ptrp = png_ptr;
+
+    /* Allocate/initialize the memory for image information. */
+    png_infop info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == nullptr) {
+        png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
+        return false;
+    }
+    *info_ptrp = info_ptr;
+
+    /* Set error handling if you are using the setjmp/longjmp method (this is
+    * the normal method of doing things with libpng).  REQUIRED unless you
+    * set up your own error handlers in the png_create_read_struct() earlier.
+    */
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+        return false;
+    }
+
+    /* If you are using replacement read functions, instead of calling
+    * png_init_io() here you would call:
+    */
+    png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
+    /* where user_io_ptr is a structure you want available to the callbacks */
+    /* If we have already read some of the signature */
+//  png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+    // hookup our peeker so we can see any user-chunks the caller may be interested in
+    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
+    if (this->getPeeker()) {
+        png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
+    }
+#endif
+    /* The call to png_read_info() gives us all of the information from the
+    * PNG file before the first IDAT (image data chunk). */
+    png_read_info(png_ptr, info_ptr);
+    png_uint_32 origWidth, origHeight;
+    int bitDepth, colorType;
+    png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+                 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+    /* tell libpng to strip 16 bit/color files down to 8 bits/color */
+    if (bitDepth == 16) {
+        png_set_strip_16(png_ptr);
+    }
+#ifdef PNG_READ_PACK_SUPPORTED
+    /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
+     * byte into separate bytes (useful for paletted and grayscale images). */
+    if (bitDepth < 8) {
+        png_set_packing(png_ptr);
+    }
+#endif
+    /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
+    if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+        png_set_expand_gray_1_2_4_to_8(png_ptr);
+    }
+
+    return true;
+}
+
+SkImageDecoder::Result SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
+                                                   Mode mode) {
+    png_structp png_ptr;
+    png_infop info_ptr;
+
+    if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
+        return kFailure;
+    }
+
+    PNGAutoClean autoClean(png_ptr, info_ptr);
+
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        return kFailure;
+    }
+
+    png_uint_32 origWidth, origHeight;
+    int bitDepth, pngColorType, interlaceType;
+    png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+                 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
+
+    SkColorType         colorType;
+    bool                hasAlpha = false;
+    SkPMColor           theTranspColor = 0; // 0 tells us not to try to match
+
+    if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
+        return kFailure;
+    }
+
+    SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
+                                kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+    const int sampleSize = this->getSampleSize();
+    SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+    decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+                                             colorType, alphaType));
+
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    // from here down we are concerned with colortables and pixels
+
+    // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
+    // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
+    // draw lots faster if we can flag the bitmap has being opaque
+    bool reallyHasAlpha = false;
+    SkColorTable* colorTable = nullptr;
+
+    if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
+        decodePalette(png_ptr, info_ptr, bitDepth, &hasAlpha, &reallyHasAlpha, &colorTable);
+    }
+
+    SkAutoUnref aur(colorTable);
+
+    if (!this->allocPixelRef(decodedBitmap,
+                             kIndex_8_SkColorType == colorType ? colorTable : nullptr)) {
+        return kFailure;
+    }
+
+    SkAutoLockPixels alp(*decodedBitmap);
+
+    // Repeat setjmp, otherwise variables declared since the last call (e.g. alp
+    // and aur) won't get their destructors called in case of a failure.
+    if (setjmp(png_jmpbuf(png_ptr))) {
+        return kFailure;
+    }
+
+    /* Turn on interlace handling.  REQUIRED if you are not using
+    *  png_read_image().  To see how to handle interlacing passes,
+    *  see the png_read_row() method below:
+    */
+    const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
+                              png_set_interlace_handling(png_ptr) : 1;
+
+    /* Optional call to gamma correct and add the background to the palette
+    *  and update info structure.  REQUIRED if you are expecting libpng to
+    *  update the palette for you (ie you selected such a transform above).
+    */
+    png_read_update_info(png_ptr, info_ptr);
+
+    if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
+            1 == sampleSize) {
+        if (kAlpha_8_SkColorType == colorType) {
+            // For an A8 bitmap, we assume there is an alpha for speed. It is
+            // possible the bitmap is opaque, but that is an unlikely use case
+            // since it would not be very interesting.
+            reallyHasAlpha = true;
+            // A8 is only allowed if the original was GRAY.
+            SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
+        }
+        for (int i = 0; i < number_passes; i++) {
+            for (png_uint_32 y = 0; y < origHeight; y++) {
+                uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
+                png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+            }
+        }
+    } else {
+        SkScaledBitmapSampler::SrcConfig sc;
+        int srcBytesPerPixel = 4;
+
+        if (colorTable != nullptr) {
+            sc = SkScaledBitmapSampler::kIndex;
+            srcBytesPerPixel = 1;
+        } else if (kAlpha_8_SkColorType == colorType) {
+            // A8 is only allowed if the original was GRAY.
+            SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
+            sc = SkScaledBitmapSampler::kGray;
+            srcBytesPerPixel = 1;
+        } else if (hasAlpha) {
+            sc = SkScaledBitmapSampler::kRGBA;
+        } else {
+            sc = SkScaledBitmapSampler::kRGBX;
+        }
+
+        /*  We have to pass the colortable explicitly, since we may have one
+            even if our decodedBitmap doesn't, due to the request that we
+            upscale png's palette to a direct model
+         */
+        const SkPMColor* colors = colorTable ? colorTable->readColors() : nullptr;
+        if (!sampler.begin(decodedBitmap, sc, *this, colors)) {
+            return kFailure;
+        }
+        const int height = decodedBitmap->height();
+
+        if (number_passes > 1) {
+            SkAutoTMalloc<uint8_t> storage(origWidth * origHeight * srcBytesPerPixel);
+            uint8_t* base = storage.get();
+            size_t rowBytes = origWidth * srcBytesPerPixel;
+
+            for (int i = 0; i < number_passes; i++) {
+                uint8_t* row = base;
+                for (png_uint_32 y = 0; y < origHeight; y++) {
+                    uint8_t* bmRow = row;
+                    png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+                    row += rowBytes;
+                }
+            }
+            // now sample it
+            base += sampler.srcY0() * rowBytes;
+            for (int y = 0; y < height; y++) {
+                reallyHasAlpha |= sampler.next(base);
+                base += sampler.srcDY() * rowBytes;
+            }
+        } else {
+            SkAutoTMalloc<uint8_t> storage(origWidth * srcBytesPerPixel);
+            uint8_t* srcRow = storage.get();
+            skip_src_rows(png_ptr, srcRow, sampler.srcY0());
+
+            for (int y = 0; y < height; y++) {
+                uint8_t* tmp = srcRow;
+                png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+                reallyHasAlpha |= sampler.next(srcRow);
+                if (y < height - 1) {
+                    skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
+                }
+            }
+
+            // skip the rest of the rows (if any)
+            png_uint_32 read = (height - 1) * sampler.srcDY() +
+                               sampler.srcY0() + 1;
+            SkASSERT(read <= origHeight);
+            skip_src_rows(png_ptr, srcRow, origHeight - read);
+        }
+    }
+
+    /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
+    png_read_end(png_ptr, info_ptr);
+
+    if (0 != theTranspColor) {
+        reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
+    }
+    if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
+        switch (decodedBitmap->colorType()) {
+            case kIndex_8_SkColorType:
+                // Fall through.
+            case kARGB_4444_SkColorType:
+                // We have chosen not to support unpremul for these colortypes.
+                return kFailure;
+            default: {
+                // Fall through to finish the decode. This colortype either
+                // supports unpremul or it is irrelevant because it has no
+                // alpha (or only alpha).
+                // These brackets prevent a warning.
+            }
+        }
+    }
+
+    if (!reallyHasAlpha) {
+        decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
+    }
+    return kSuccess;
+}
+
+
+
+bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
+                                           SkColorType* colorTypep,
+                                           bool* hasAlphap,
+                                           SkPMColor* SK_RESTRICT theTranspColorp) {
+    png_uint_32 origWidth, origHeight;
+    int bitDepth, colorType;
+    png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+                 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+#ifdef PNG_sBIT_SUPPORTED
+    // check for sBIT chunk data, in case we should disable dithering because
+    // our data is not truely 8bits per component
+    png_color_8p sig_bit;
+    if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
+#if 0
+        SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
+                 sig_bit->blue, sig_bit->alpha);
+#endif
+        // 0 seems to indicate no information available
+        if (pos_le(sig_bit->red, SK_R16_BITS) &&
+            pos_le(sig_bit->green, SK_G16_BITS) &&
+            pos_le(sig_bit->blue, SK_B16_BITS)) {
+            this->setDitherImage(false);
+        }
+    }
+#endif
+
+    if (colorType == PNG_COLOR_TYPE_PALETTE) {
+        bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
+        *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
+        // now see if we can upscale to their requested colortype
+        if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
+            *colorTypep = kIndex_8_SkColorType;
+        }
+    } else {
+        png_color_16p transpColor = nullptr;
+        int numTransp = 0;
+
+        png_get_tRNS(png_ptr, info_ptr, nullptr, &numTransp, &transpColor);
+
+        bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
+
+        if (valid && numTransp == 1 && transpColor != nullptr) {
+            /*  Compute our transparent color, which we'll match against later.
+                We don't really handle 16bit components properly here, since we
+                do our compare *after* the values have been knocked down to 8bit
+                which means we will find more matches than we should. The real
+                fix seems to be to see the actual 16bit components, do the
+                compare, and then knock it down to 8bits ourselves.
+            */
+            if (colorType & PNG_COLOR_MASK_COLOR) {
+                if (16 == bitDepth) {
+                    *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
+                                                    transpColor->green >> 8,
+                                                    transpColor->blue >> 8);
+                } else {
+                    /* We apply the mask because in a very small
+                       number of corrupt PNGs, (transpColor->red > 255)
+                       and (bitDepth == 8), for certain versions of libpng. */
+                    *theTranspColorp = SkPackARGB32(0xFF,
+                                                    0xFF & (transpColor->red),
+                                                    0xFF & (transpColor->green),
+                                                    0xFF & (transpColor->blue));
+                }
+            } else {    // gray
+                if (16 == bitDepth) {
+                    *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
+                                                    transpColor->gray >> 8,
+                                                    transpColor->gray >> 8);
+                } else {
+                    /* We apply the mask because in a very small
+                       number of corrupt PNGs, (transpColor->red >
+                       255) and (bitDepth == 8), for certain versions
+                       of libpng.  For safety we assume the same could
+                       happen with a grayscale PNG.  */
+                    *theTranspColorp = SkPackARGB32(0xFF,
+                                                    0xFF & (transpColor->gray),
+                                                    0xFF & (transpColor->gray),
+                                                    0xFF & (transpColor->gray));
+                }
+            }
+        }
+
+        if (valid ||
+            PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
+            PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
+            *hasAlphap = true;
+        }
+
+        SrcDepth srcDepth = k32Bit_SrcDepth;
+        if (PNG_COLOR_TYPE_GRAY == colorType) {
+            srcDepth = k8BitGray_SrcDepth;
+            // Remove this assert, which fails on desk_pokemonwiki.skp
+            //SkASSERT(!*hasAlphap);
+        }
+
+        *colorTypep = this->getPrefColorType(srcDepth, *hasAlphap);
+        // now match the request against our capabilities
+        if (*hasAlphap) {
+            if (*colorTypep != kARGB_4444_SkColorType) {
+                *colorTypep = kN32_SkColorType;
+            }
+        } else {
+            if (kAlpha_8_SkColorType == *colorTypep) {
+                if (k8BitGray_SrcDepth != srcDepth) {
+                    // Converting a non grayscale image to A8 is not currently supported.
+                    *colorTypep = kN32_SkColorType;
+                }
+            } else if (*colorTypep != kRGB_565_SkColorType &&
+                       *colorTypep != kARGB_4444_SkColorType) {
+                *colorTypep = kN32_SkColorType;
+            }
+        }
+    }
+
+    // sanity check for size
+    {
+        int64_t size = sk_64_mul(origWidth, origHeight);
+        // now check that if we are 4-bytes per pixel, we also don't overflow
+        if (size < 0 || size > (0x7FFFFFFF >> 2)) {
+            return false;
+        }
+    }
+
+    // If the image has alpha and the decoder wants unpremultiplied
+    // colors, the only supported colortype is 8888.
+    if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
+        *colorTypep = kN32_SkColorType;
+    }
+
+    if (fImageIndex != nullptr) {
+        if (kUnknown_SkColorType == fImageIndex->fColorType) {
+            // This is the first time for this subset decode. From now on,
+            // all decodes must be in the same colortype.
+            fImageIndex->fColorType = *colorTypep;
+        } else if (fImageIndex->fColorType != *colorTypep) {
+            // Requesting a different colortype for a subsequent decode is not
+            // supported. Report failure before we make changes to png_ptr.
+            return false;
+        }
+    }
+
+    bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
+
+    // Unless the user is requesting A8, convert a grayscale image into RGB.
+    // GRAY_ALPHA will always be converted to RGB
+    if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        png_set_gray_to_rgb(png_ptr);
+    }
+
+    // Add filler (or alpha) byte (after each RGB triplet) if necessary.
+    if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
+        png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
+    }
+
+    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,
+                                      int bitDepth, bool *hasAlphap,
+                                      bool *reallyHasAlphap,
+                                      SkColorTable **colorTablep) {
+    int numPalette;
+    png_colorp palette;
+    png_bytep trans;
+    int numTrans;
+
+    png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
+
+    SkPMColor colorStorage[256];    // worst-case storage
+    SkPMColor* colorPtr = colorStorage;
+
+    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+        png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, nullptr);
+        *hasAlphap = (numTrans > 0);
+    } else {
+        numTrans = 0;
+    }
+
+    // check for bad images that might make us crash
+    if (numTrans > numPalette) {
+        numTrans = numPalette;
+    }
+
+    int index = 0;
+    int transLessThanFF = 0;
+
+    // Choose which function to use to create the color table. If the final destination's
+    // colortype 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++ = proc(*trans++, palette->red, palette->green, palette->blue);
+        palette++;
+    }
+    bool reallyHasAlpha = (transLessThanFF < 0);
+
+    for (; index < numPalette; index++) {
+        *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
+        palette++;
+    }
+
+    /*  BUGGY IMAGE WORKAROUND
+
+        Invalid images could contain pixel values that are greater than the number of palette
+        entries. Since we use pixel values as indices into the palette this could result in reading
+        beyond the end of the palette which could leak the contents of uninitialized memory. To
+        ensure this doesn't happen, we grow the colortable to the maximum size that can be
+        addressed by the bitdepth of the image and fill it with the last palette color or black if
+        the palette is empty (really broken image).
+    */
+    int colorCount = SkTMax(numPalette, 1 << SkTMin(bitDepth, 8));
+    SkPMColor lastColor = index > 0 ? colorPtr[-1] : SkPackARGB32(0xFF, 0, 0, 0);
+    for (; index < colorCount; index++) {
+        *colorPtr++ = lastColor;
+    }
+
+    *colorTablep = new SkColorTable(colorStorage, colorCount);
+    *reallyHasAlphap = reallyHasAlpha;
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorPriv.h"
+#include "SkUnPreMultiply.h"
+
 static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
     SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
     if (!sk_stream->write(data, len)) {
@@ -334,11 +985,37 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(PNGImageDecoder);
 DEFINE_ENCODER_CREATOR(PNGImageEncoder);
 ///////////////////////////////////////////////////////////////////////////////
 
+static bool is_png(SkStreamRewindable* stream) {
+    char buf[PNG_BYTES_TO_CHECK];
+    if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
+        !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
+        return true;
+    }
+    return false;
+}
+
+SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
+    if (is_png(stream)) {
+        return new SkPNGImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
+    if (is_png(stream)) {
+        return SkImageDecoder::kPNG_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
 SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
     return (SkImageEncoder::kPNG_Type == t) ? new SkPNGImageEncoder : nullptr;
 }
 
+static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_png);
 static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);
diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp
index 116608a..2db08ce 100644
--- a/src/images/SkImageDecoder_libwebp.cpp
+++ b/src/images/SkImageDecoder_libwebp.cpp
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#include "SkBitmap.h"
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkColorPriv.h"
+#include "SkScaledBitmapSampler.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkUtils.h"
@@ -31,9 +32,299 @@
 extern "C" {
 // If moving libwebp out of skia source tree, path for webp headers must be
 // updated accordingly. Here, we enforce using local copy in webp sub-directory.
+#include "webp/decode.h"
 #include "webp/encode.h"
 }
 
+// this enables timing code to report milliseconds for a decode
+//#define TIME_DECODE
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+// Define VP8 I/O on top of Skia stream
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static const size_t WEBP_VP8_HEADER_SIZE = 64;
+static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16);
+
+// Parse headers of RIFF container, and check for valid Webp (VP8) content.
+static bool webp_parse_header(SkStream* stream, int* width, int* height, int* alpha) {
+    unsigned char buffer[WEBP_VP8_HEADER_SIZE];
+    size_t bytesToRead = WEBP_VP8_HEADER_SIZE;
+    size_t totalBytesRead = 0;
+    do {
+        unsigned char* dst = buffer + totalBytesRead;
+        const size_t bytesRead = stream->read(dst, bytesToRead);
+        if (0 == bytesRead) {
+            SkASSERT(stream->isAtEnd());
+            break;
+        }
+        bytesToRead -= bytesRead;
+        totalBytesRead += bytesRead;
+        SkASSERT(bytesToRead + totalBytesRead == WEBP_VP8_HEADER_SIZE);
+    } while (!stream->isAtEnd() && bytesToRead > 0);
+
+    WebPBitstreamFeatures features;
+    VP8StatusCode status = WebPGetFeatures(buffer, totalBytesRead, &features);
+    if (VP8_STATUS_OK != status) {
+        return false; // Invalid WebP file.
+    }
+    *width = features.width;
+    *height = features.height;
+    *alpha = features.has_alpha;
+
+    // sanity check for image size that's about to be decoded.
+    {
+        int64_t size = sk_64_mul(*width, *height);
+        if (!sk_64_isS32(size)) {
+            return false;
+        }
+        // now check that if we are 4-bytes per pixel, we also don't overflow
+        if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+class SkWEBPImageDecoder: public SkImageDecoder {
+public:
+    SkWEBPImageDecoder() {
+        fOrigWidth = 0;
+        fOrigHeight = 0;
+        fHasAlpha = 0;
+    }
+
+    Format getFormat() const override {
+        return kWEBP_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) 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);
+
+    SkAutoTDelete<SkStream> fInputStream;
+    int fOrigWidth;
+    int fOrigHeight;
+    int fHasAlpha;
+
+    typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef TIME_DECODE
+
+#include "SkTime.h"
+
+class AutoTimeMillis {
+public:
+    AutoTimeMillis(const char label[]) :
+        fLabel(label) {
+        if (nullptr == fLabel) {
+            fLabel = "";
+        }
+        fNow = SkTime::GetMSecs();
+    }
+    ~AutoTimeMillis() {
+        SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
+    }
+private:
+    const char* fLabel;
+    SkMSec fNow;
+};
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This guy exists just to aid in debugging, as it allows debuggers to just
+// set a break-point in one place to see all error exists.
+static void print_webp_error(const SkBitmap& bm, const char msg[]) {
+    SkDEBUGF(("libwebp error %s [%d %d]", msg, bm.width(), bm.height()));
+}
+
+static SkImageDecoder::Result return_failure(const SkBitmap& bm, const char msg[]) {
+    print_webp_error(bm, msg);
+    return SkImageDecoder::kFailure; // must always return kFailure
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, bool premultiply) {
+    WEBP_CSP_MODE mode = MODE_LAST;
+    const SkColorType ct = decodedBitmap->colorType();
+
+    if (ct == kN32_SkColorType) {
+        #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
+            mode = premultiply ? MODE_bgrA : MODE_BGRA;
+        #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
+            mode = premultiply ? MODE_rgbA : MODE_RGBA;
+        #else
+            #error "Skia uses BGRA or RGBA byte order"
+        #endif
+    } else if (ct == kARGB_4444_SkColorType) {
+        mode = premultiply ? MODE_rgbA_4444 : MODE_RGBA_4444;
+    } else if (ct == kRGB_565_SkColorType) {
+        mode = MODE_RGB_565;
+    }
+    SkASSERT(MODE_LAST != mode);
+    return mode;
+}
+
+// Incremental WebP image decoding. Reads input buffer of 64K size iteratively
+// and decodes this block to appropriate color-space as per config object.
+static bool webp_idecode(SkStream* stream, WebPDecoderConfig* config) {
+    WebPIDecoder* idec = WebPIDecode(nullptr, 0, config);
+    if (nullptr == idec) {
+        WebPFreeDecBuffer(&config->output);
+        return false;
+    }
+
+    if (!stream->rewind()) {
+        SkDebugf("Failed to rewind webp stream!");
+        return false;
+    }
+    const size_t readBufferSize = stream->hasLength() ?
+            SkTMin(stream->getLength(), WEBP_IDECODE_BUFFER_SZ) : WEBP_IDECODE_BUFFER_SZ;
+    SkAutoTMalloc<unsigned char> srcStorage(readBufferSize);
+    unsigned char* input = srcStorage.get();
+    if (nullptr == input) {
+        WebPIDelete(idec);
+        WebPFreeDecBuffer(&config->output);
+        return false;
+    }
+
+    bool success = true;
+    VP8StatusCode status = VP8_STATUS_SUSPENDED;
+    do {
+        const size_t bytesRead = stream->read(input, readBufferSize);
+        if (0 == bytesRead) {
+            success = false;
+            break;
+        }
+
+        status = WebPIAppend(idec, input, bytesRead);
+        if (VP8_STATUS_OK != status && VP8_STATUS_SUSPENDED != status) {
+            success = false;
+            break;
+        }
+    } while (VP8_STATUS_OK != status);
+    srcStorage.reset();
+    WebPIDelete(idec);
+    WebPFreeDecBuffer(&config->output);
+
+    return success;
+}
+
+static bool webp_get_config_resize(WebPDecoderConfig* config,
+                                   SkBitmap* decodedBitmap,
+                                   int width, int height, bool premultiply) {
+    WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, premultiply);
+    if (MODE_LAST == mode) {
+        return false;
+    }
+
+    if (0 == WebPInitDecoderConfig(config)) {
+        return false;
+    }
+
+    config->output.colorspace = mode;
+    config->output.u.RGBA.rgba = (uint8_t*)decodedBitmap->getPixels();
+    config->output.u.RGBA.stride = (int) decodedBitmap->rowBytes();
+    config->output.u.RGBA.size = decodedBitmap->getSize();
+    config->output.is_external_memory = 1;
+
+    if (width != decodedBitmap->width() || height != decodedBitmap->height()) {
+        config->options.use_scaling = 1;
+        config->options.scaled_width = decodedBitmap->width();
+        config->options.scaled_height = decodedBitmap->height();
+    }
+
+    return true;
+}
+
+bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap, int width, int height) {
+    SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, SkToBool(fHasAlpha));
+
+    // YUV converter supports output in RGB565, RGBA4444 and RGBA8888 formats.
+    if (fHasAlpha) {
+        if (colorType != kARGB_4444_SkColorType) {
+            colorType = kN32_SkColorType;
+        }
+    } else {
+        if (colorType != kRGB_565_SkColorType && colorType != kARGB_4444_SkColorType) {
+            colorType = kN32_SkColorType;
+        }
+    }
+
+    SkAlphaType alphaType = kOpaque_SkAlphaType;
+    if (SkToBool(fHasAlpha)) {
+        if (this->getRequireUnpremultipliedColors()) {
+            alphaType = kUnpremul_SkAlphaType;
+        } else {
+            alphaType = kPremul_SkAlphaType;
+        }
+    }
+    return decodedBitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType));
+}
+
+SkImageDecoder::Result SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+                                                    Mode mode) {
+#ifdef TIME_DECODE
+    AutoTimeMillis atm("WEBP Decode");
+#endif
+
+    int origWidth, origHeight, hasAlpha;
+    if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
+        return kFailure;
+    }
+    this->fHasAlpha = hasAlpha;
+
+    const int sampleSize = this->getSampleSize();
+    SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+    if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(),
+                         sampler.scaledHeight())) {
+        return kFailure;
+    }
+
+    // If only bounds are requested, done
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(decodedBitmap, nullptr)) {
+        return return_failure(*decodedBitmap, "allocPixelRef");
+    }
+
+    SkAutoLockPixels alp(*decodedBitmap);
+
+    WebPDecoderConfig config;
+    if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight,
+                                this->shouldPremultiply())) {
+        return kFailure;
+    }
+
+    // Decode the WebP image data stream using WebP incremental decoding.
+    return webp_idecode(stream, &config) ? kSuccess : kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 #include "SkUnPreMultiply.h"
 
 typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* out, int width,
@@ -237,11 +528,32 @@
 
 
 ///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WEBPImageDecoder);
 DEFINE_ENCODER_CREATOR(WEBPImageEncoder);
 ///////////////////////////////////////////////////////////////////////////////
 
+static SkImageDecoder* sk_libwebp_dfactory(SkStreamRewindable* stream) {
+    int width, height, hasAlpha;
+    if (!webp_parse_header(stream, &width, &height, &hasAlpha)) {
+        return nullptr;
+    }
+
+    // Magic matches, call decoder
+    return new SkWEBPImageDecoder;
+}
+
+static SkImageDecoder::Format get_format_webp(SkStreamRewindable* stream) {
+    int width, height, hasAlpha;
+    if (webp_parse_header(stream, &width, &height, &hasAlpha)) {
+        return SkImageDecoder::kWEBP_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
 static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) {
     return (SkImageEncoder::kWEBP_Type == t) ? new SkWEBPImageEncoder : nullptr;
 }
 
+static SkImageDecoder_DecodeReg gDReg(sk_libwebp_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_webp);
 static SkImageEncoder_EncodeReg gEReg(sk_libwebp_efactory);
diff --git a/src/images/SkImageDecoder_pkm.cpp b/src/images/SkImageDecoder_pkm.cpp
new file mode 100644
index 0000000..af68f20
--- /dev/null
+++ b/src/images/SkImageDecoder_pkm.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTextureCompressor.h"
+#include "SkTypes.h"
+
+#include "etc1.h"
+
+class SkPKMImageDecoder : public SkImageDecoder {
+public:
+    SkPKMImageDecoder() { }
+
+    Format getFormat() const override {
+        return kPKM_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkImageDecoder::Result SkPKMImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    sk_sp<SkData> data(SkCopyStreamToData(stream));
+    if (!data || !data->size()) {
+        return kFailure;
+    }
+
+    unsigned char* buf = (unsigned char*) data->data();
+
+    // Make sure original PKM header is there...
+    SkASSERT(etc1_pkm_is_valid(buf));
+
+    const unsigned short width = etc1_pkm_get_width(buf);
+    const unsigned short height = etc1_pkm_get_height(buf);
+
+    // Setup the sampler...
+    SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+    // Set the config...
+    bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(),
+                                     kOpaque_SkAlphaType));
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return kFailure;
+    }
+
+    // Lock the pixels, since we're about to write to them...
+    SkAutoLockPixels alp(*bm);
+
+    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+        return kFailure;
+    }
+
+    // Advance buffer past the header
+    buf += ETC_PKM_HEADER_SIZE;
+
+    // ETC1 Data is encoded as RGB pixels, so we should extract it as such
+    int nPixels = width * height;
+    SkAutoMalloc outRGBData(nPixels * 3);
+    uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+    // Decode ETC1
+    if (!SkTextureCompressor::DecompressBufferFromFormat(
+            outRGBDataPtr, width*3, buf, width, height, SkTextureCompressor::kETC1_Format)) {
+        return kFailure;
+    }
+
+    // Set each of the pixels...
+    const int srcRowBytes = width * 3;
+    const int dstHeight = sampler.scaledHeight();
+    const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+    srcRow += sampler.srcY0() * srcRowBytes;
+    for (int y = 0; y < dstHeight; ++y) {
+        sampler.next(srcRow);
+        srcRow += sampler.srcDY() * srcRowBytes;
+    }
+
+    return kSuccess;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(PKMImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_pkm(SkStreamRewindable* stream) {
+    // Read the PKM header and make sure it's valid.
+    unsigned char buf[ETC_PKM_HEADER_SIZE];
+    if (stream->read((void*)buf, ETC_PKM_HEADER_SIZE) != ETC_PKM_HEADER_SIZE) {
+        return false;
+    }
+
+    return SkToBool(etc1_pkm_is_valid(buf));
+}
+
+static SkImageDecoder* sk_libpkm_dfactory(SkStreamRewindable* stream) {
+    if (is_pkm(stream)) {
+        return new SkPKMImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libpkm_dfactory);
+
+static SkImageDecoder::Format get_format_pkm(SkStreamRewindable* stream) {
+    if (is_pkm(stream)) {
+        return SkImageDecoder::kPKM_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_pkm);
diff --git a/src/images/SkImageDecoder_wbmp.cpp b/src/images/SkImageDecoder_wbmp.cpp
new file mode 100644
index 0000000..335966b
--- /dev/null
+++ b/src/images/SkImageDecoder_wbmp.cpp
@@ -0,0 +1,173 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkImageDecoder.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkMath.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+class SkWBMPImageDecoder : public SkImageDecoder {
+public:
+    Format getFormat() const override {
+        return kWBMP_Format;
+    }
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+    typedef SkImageDecoder INHERITED;
+};
+
+static bool read_byte(SkStream* stream, uint8_t* data)
+{
+    return stream->read(data, 1) == 1;
+}
+
+static bool read_mbf(SkStream* stream, int* value)
+{
+    int n = 0;
+    uint8_t data;
+    do {
+        if (!read_byte(stream, &data)) {
+            return false;
+        }
+        n = (n << 7) | (data & 0x7F);
+    } while (data & 0x80);
+
+    *value = n;
+    return true;
+}
+
+struct wbmp_head {
+    int fWidth;
+    int fHeight;
+
+    bool init(SkStream* stream)
+    {
+        uint8_t data;
+
+        if (!read_byte(stream, &data) || data != 0) { // unknown type
+            return false;
+        }
+        if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header
+            return false;
+        }
+        if (!read_mbf(stream, &fWidth) || (unsigned)fWidth > 0xFFFF) {
+            return false;
+        }
+        if (!read_mbf(stream, &fHeight) || (unsigned)fHeight > 0xFFFF) {
+            return false;
+        }
+        return fWidth != 0 && fHeight != 0;
+    }
+};
+
+static void expand_bits_to_bytes(uint8_t dst[], const uint8_t src[], int bits)
+{
+    int bytes = bits >> 3;
+
+    for (int i = 0; i < bytes; i++) {
+        unsigned mask = *src++;
+        dst[0] = (mask >> 7) & 1;
+        dst[1] = (mask >> 6) & 1;
+        dst[2] = (mask >> 5) & 1;
+        dst[3] = (mask >> 4) & 1;
+        dst[4] = (mask >> 3) & 1;
+        dst[5] = (mask >> 2) & 1;
+        dst[6] = (mask >> 1) & 1;
+        dst[7] = (mask >> 0) & 1;
+        dst += 8;
+    }
+
+    bits &= 7;
+    if (bits > 0) {
+        unsigned mask = *src;
+        do {
+            *dst++ = (mask >> 7) & 1;
+            mask <<= 1;
+        } while (--bits != 0);
+    }
+}
+
+SkImageDecoder::Result SkWBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+                                                    Mode mode)
+{
+    wbmp_head   head;
+
+    if (!head.init(stream)) {
+        return kFailure;
+    }
+
+    int width = head.fWidth;
+    int height = head.fHeight;
+
+    decodedBitmap->setInfo(SkImageInfo::Make(width, height,
+                                             kIndex_8_SkColorType, kOpaque_SkAlphaType));
+
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    const SkPMColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
+    SkColorTable* ct = new SkColorTable(colors, 2);
+    SkAutoUnref   aur(ct);
+
+    if (!this->allocPixelRef(decodedBitmap, ct)) {
+        return kFailure;
+    }
+
+    SkAutoLockPixels alp(*decodedBitmap);
+
+    uint8_t* dst = decodedBitmap->getAddr8(0, 0);
+    // store the 1-bit valuess at the end of our pixels, so we won't stomp
+    // on them before we're read them. Just trying to avoid a temp allocation
+    size_t srcRB = SkAlign8(width) >> 3;
+    size_t srcSize = height * srcRB;
+    uint8_t* src = dst + decodedBitmap->getSize() - srcSize;
+    if (stream->read(src, srcSize) != srcSize) {
+        return kFailure;
+    }
+
+    for (int y = 0; y < height; y++)
+    {
+        expand_bits_to_bytes(dst, src, width);
+        dst += decodedBitmap->rowBytes();
+        src += srcRB;
+    }
+
+    return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WBMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static SkImageDecoder* sk_wbmp_dfactory(SkStreamRewindable* stream) {
+    wbmp_head   head;
+
+    if (head.init(stream)) {
+        return new SkWBMPImageDecoder;
+    }
+    return nullptr;
+}
+
+static SkImageDecoder::Format get_format_wbmp(SkStreamRewindable* stream) {
+    wbmp_head head;
+    if (head.init(stream)) {
+        return SkImageDecoder::kWBMP_Format;
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_DecodeReg gDReg(sk_wbmp_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_wbmp);
diff --git a/src/images/SkJpegUtility.cpp b/src/images/SkJpegUtility.cpp
index ab8486b..f1a32ca 100644
--- a/src/images/SkJpegUtility.cpp
+++ b/src/images/SkJpegUtility.cpp
@@ -8,6 +8,108 @@
 
 #include "SkJpegUtility.h"
 
+/////////////////////////////////////////////////////////////////////
+static void sk_init_source(j_decompress_ptr cinfo) {
+    skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
+    src->next_input_byte = (const JOCTET*)src->fBuffer;
+    src->bytes_in_buffer = 0;
+#ifdef SK_JPEG_INDEX_SUPPORTED
+    src->current_offset = 0;
+#endif
+    if (!src->fStream->rewind()) {
+        SkDebugf("xxxxxxxxxxxxxx failure to rewind\n");
+        cinfo->err->error_exit((j_common_ptr)cinfo);
+    }
+}
+
+#ifdef SK_JPEG_INDEX_SUPPORTED
+static boolean sk_seek_input_data(j_decompress_ptr cinfo, long byte_offset) {
+    skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+    size_t bo = (size_t) byte_offset;
+
+    if (bo > src->current_offset) {
+        (void)src->fStream->skip(bo - src->current_offset);
+    } else {
+        if (!src->fStream->rewind()) {
+            SkDebugf("xxxxxxxxxxxxxx failure to rewind\n");
+            cinfo->err->error_exit((j_common_ptr)cinfo);
+            return false;
+        }
+        (void)src->fStream->skip(bo);
+    }
+
+    src->current_offset = bo;
+    src->next_input_byte = (const JOCTET*)src->fBuffer;
+    src->bytes_in_buffer = 0;
+    return true;
+}
+#endif
+
+static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
+    skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+    if (src->fDecoder != nullptr && src->fDecoder->shouldCancelDecode()) {
+        return FALSE;
+    }
+    size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize);
+    // note that JPEG is happy with less than the full read,
+    // as long as the result is non-zero
+    if (bytes == 0) {
+        return FALSE;
+    }
+
+#ifdef SK_JPEG_INDEX_SUPPORTED
+    src->current_offset += bytes;
+#endif
+    src->next_input_byte = (const JOCTET*)src->fBuffer;
+    src->bytes_in_buffer = bytes;
+    return TRUE;
+}
+
+static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+    skjpeg_source_mgr*  src = (skjpeg_source_mgr*)cinfo->src;
+
+    if (num_bytes > (long)src->bytes_in_buffer) {
+        size_t bytesToSkip = num_bytes - src->bytes_in_buffer;
+        while (bytesToSkip > 0) {
+            size_t bytes = src->fStream->skip(bytesToSkip);
+            if (bytes <= 0 || bytes > bytesToSkip) {
+//              SkDebugf("xxxxxxxxxxxxxx failure to skip request %d returned %d\n", bytesToSkip, bytes);
+                cinfo->err->error_exit((j_common_ptr)cinfo);
+                return;
+            }
+#ifdef SK_JPEG_INDEX_SUPPORTED
+            src->current_offset += bytes;
+#endif
+            bytesToSkip -= bytes;
+        }
+        src->next_input_byte = (const JOCTET*)src->fBuffer;
+        src->bytes_in_buffer = 0;
+    } else {
+        src->next_input_byte += num_bytes;
+        src->bytes_in_buffer -= num_bytes;
+    }
+}
+
+static void sk_term_source(j_decompress_ptr /*cinfo*/) {}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder)
+    : fStream(stream)
+    , fDecoder(decoder) {
+
+    init_source = sk_init_source;
+    fill_input_buffer = sk_fill_input_buffer;
+    skip_input_data = sk_skip_input_data;
+    resync_to_restart = jpeg_resync_to_restart;
+    term_source = sk_term_source;
+#ifdef SK_JPEG_INDEX_SUPPORTED
+    seek_input_data = sk_seek_input_data;
+#endif
+//    SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static void sk_init_destination(j_compress_ptr cinfo) {
diff --git a/src/images/SkJpegUtility.h b/src/images/SkJpegUtility.h
index c844652..1a763f8 100644
--- a/src/images/SkJpegUtility.h
+++ b/src/images/SkJpegUtility.h
@@ -10,6 +10,7 @@
 #ifndef SkJpegUtility_DEFINED
 #define SkJpegUtility_DEFINED
 
+#include "SkImageDecoder.h"
 #include "SkStream.h"
 
 extern "C" {
@@ -29,6 +30,23 @@
 
 void skjpeg_error_exit(j_common_ptr cinfo);
 
+///////////////////////////////////////////////////////////////////////////
+/* Our source struct for directing jpeg to our stream object.
+*/
+struct skjpeg_source_mgr : jpeg_source_mgr {
+    skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder);
+
+    // Unowned.
+    SkStream*       fStream;
+    // Unowned pointer to the decoder, used to check if the decoding process
+    // has been cancelled.
+    SkImageDecoder* fDecoder;
+    enum {
+        kBufferSize = 1024
+    };
+    char    fBuffer[kBufferSize];
+};
+
 /////////////////////////////////////////////////////////////////////////////
 /* Our destination struct for directing decompressed pixels to our stream
  * object.
diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp
new file mode 100644
index 0000000..5ffd648
--- /dev/null
+++ b/src/images/SkScaledBitmapSampler.cpp
@@ -0,0 +1,877 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkScaledBitmapSampler.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkTypes.h"
+
+// 8888
+
+static bool Sample_Gray_D8888(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPackARGB32(0xFF, src[0], src[0], src[0]);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Dither, unpremul, and skipZeroes have no effect
+    return Sample_Gray_D8888;
+}
+
+static bool Sample_RGBx_D8888(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPackARGB32(0xFF, src[0], src[1], src[2]);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Dither, unpremul, and skipZeroes have no effect
+    return Sample_RGBx_D8888;
+}
+
+static bool Sample_RGBA_D8888(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    unsigned alphaMask = 0xFF;
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 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;
+}
+
+static bool Sample_RGBA_D8888_SkipZ(void* SK_RESTRICT dstRow,
+                                    const uint8_t* SK_RESTRICT src,
+                                    int width, int deltaSrc, int,
+                                    const SkPMColor[]) {
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    unsigned alphaMask = 0xFF;
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (0 != alpha) {
+            dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBA_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Dither has no effect.
+    if (!opts.fPremultiplyAlpha) {
+        // We could check each component for a zero, at the expense of extra checks.
+        // For now, just return unpremul.
+        return Sample_RGBA_D8888_Unpremul;
+    }
+    // Supply the versions that premultiply the colors
+    if (opts.fSkipZeros) {
+        return Sample_RGBA_D8888_SkipZ;
+    }
+    return Sample_RGBA_D8888;
+}
+
+// 565
+
+static bool Sample_Gray_D565(void* SK_RESTRICT dstRow,
+                             const uint8_t* SK_RESTRICT src,
+                             int width, int deltaSrc, int, const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static bool Sample_Gray_D565_D(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src,
+                           int width, int deltaSrc, int y, const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    DITHER_565_SCAN(y);
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkDitherRGBTo565(src[0], src[0], src[0], DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremul and skip zeroes make no difference
+    if (opts.fDither) {
+        return Sample_Gray_D565_D;
+    }
+    return Sample_Gray_D565;
+}
+
+static bool Sample_RGBx_D565(void* SK_RESTRICT dstRow,
+                             const uint8_t* SK_RESTRICT src,
+                             int width, int deltaSrc, int, const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src,
+                               int width, int deltaSrc, int y,
+                               const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    DITHER_565_SCAN(y);
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremul and skip zeroes make no difference
+    if (opts.fDither) {
+        return Sample_RGBx_D565_D;
+    }
+    return Sample_RGBx_D565;
+}
+
+
+static bool Sample_D565_D565(void* SK_RESTRICT dstRow,
+                             const uint8_t* SK_RESTRICT src,
+                             int width, int deltaSrc, int, const SkPMColor[]) {
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    uint16_t* SK_RESTRICT castedSrc = (uint16_t*) src;
+    for (int x = 0; x < width; x++) {
+        dst[x] = castedSrc[0];
+        castedSrc += deltaSrc >> 1;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_565_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremul, dither, and skip zeroes have no effect
+    return Sample_D565_D565;
+}
+
+// 4444
+
+static bool Sample_Gray_D4444(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    for (int x = 0; x < width; x++) {
+        unsigned gray = src[0] >> 4;
+        dst[x] = SkPackARGB4444(0xF, gray, gray, gray);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static bool Sample_Gray_D4444_D(void* SK_RESTRICT dstRow,
+                                const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int y, const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    DITHER_4444_SCAN(y);
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[0], src[0],
+                                      DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Skip zeroes and unpremul make no difference
+    if (opts.fDither) {
+        return Sample_Gray_D4444_D;
+    }
+    return Sample_Gray_D4444;
+}
+
+static bool Sample_RGBx_D4444(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPackARGB4444(0xF, src[0] >> 4, src[1] >> 4, src[2] >> 4);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static bool Sample_RGBx_D4444_D(void* SK_RESTRICT dstRow,
+                                const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int y, const SkPMColor[]) {
+    SkPMColor16* dst = (SkPMColor16*)dstRow;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[1], src[2],
+                                      DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Skip zeroes and unpremul make no difference
+    if (opts.fDither) {
+        return Sample_RGBx_D4444_D;
+    }
+    return Sample_RGBx_D4444;
+}
+
+static bool Sample_RGBA_D4444(void* SK_RESTRICT dstRow,
+                              const uint8_t* SK_RESTRICT src,
+                              int width, int deltaSrc, int, const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+        dst[x] = SkPixel32ToPixel4444(c);
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D4444_SkipZ(void* SK_RESTRICT dstRow,
+                                    const uint8_t* SK_RESTRICT src,
+                                    int width, int deltaSrc, int,
+                                    const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (alpha != 0) {
+            SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+            dst[x] = SkPixel32ToPixel4444(c);
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+
+static bool Sample_RGBA_D4444_D(void* SK_RESTRICT dstRow,
+                                const uint8_t* SK_RESTRICT src,
+                                int width, int deltaSrc, int y,
+                                const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+        dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+                                      const uint8_t* SK_RESTRICT src,
+                                      int width, int deltaSrc, int y,
+                                      const SkPMColor[]) {
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    unsigned alphaMask = 0xFF;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        unsigned alpha = src[3];
+        if (alpha != 0) {
+            SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+            dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        }
+        src += deltaSrc;
+        alphaMask &= alpha;
+    }
+    return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBA_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+    if (!opts.fPremultiplyAlpha) {
+        // Unpremultiplied is not supported for 4444
+        return nullptr;
+    }
+    if (opts.fSkipZeros) {
+        if (opts.fDither) {
+            return Sample_RGBA_D4444_D_SkipZ;
+        }
+        return Sample_RGBA_D4444_SkipZ;
+    }
+    if (opts.fDither) {
+        return Sample_RGBA_D4444_D;
+    }
+    return Sample_RGBA_D4444;
+}
+
+// Index
+
+#define A32_MASK_IN_PLACE   (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT)
+
+static bool Sample_Index_D8888(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src,
+                       int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        dst[x] = c;
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D8888_SkipZ(void* SK_RESTRICT dstRow,
+                                     const uint8_t* SK_RESTRICT src,
+                                     int width, int deltaSrc, int,
+                                     const SkPMColor ctable[]) {
+
+    SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = c;
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+    // The caller is expected to have created the source colortable
+    // properly with respect to opts.fPremultiplyAlpha, so premul makes
+    // no difference here.
+    // Dither makes no difference
+    if (opts.fSkipZeros) {
+        return Sample_Index_D8888_SkipZ;
+    }
+    return Sample_Index_D8888;
+}
+
+static bool Sample_Index_D565(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src,
+                       int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    for (int x = 0; x < width; x++) {
+        dst[x] = SkPixel32ToPixel16(ctable[*src]);
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static bool Sample_Index_D565_D(void* SK_RESTRICT dstRow,
+                                const uint8_t* SK_RESTRICT src, int width,
+                                int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+    DITHER_565_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        dst[x] = SkDitherRGBTo565(SkGetPackedR32(c), SkGetPackedG32(c),
+                                  SkGetPackedB32(c), DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremultiplied and skip zeroes make no difference
+    if (opts.fDither) {
+        return Sample_Index_D565_D;
+    }
+    return Sample_Index_D565;
+}
+
+static bool Sample_Index_D4444(void* SK_RESTRICT dstRow,
+                               const uint8_t* SK_RESTRICT src, int width,
+                               int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        dst[x] = SkPixel32ToPixel4444(c);
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D(void* SK_RESTRICT dstRow,
+                                 const uint8_t* SK_RESTRICT src, int width,
+                                int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_SkipZ(void* SK_RESTRICT dstRow,
+                                     const uint8_t* SK_RESTRICT src, int width,
+                                     int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = SkPixel32ToPixel4444(c);
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+                                       const uint8_t* SK_RESTRICT src, int width,
+                                       int deltaSrc, int y, const SkPMColor ctable[]) {
+
+    SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+    SkPMColor cc = A32_MASK_IN_PLACE;
+    DITHER_4444_SCAN(y);
+
+    for (int x = 0; x < width; x++) {
+        SkPMColor c = ctable[*src];
+        cc &= c;
+        if (c != 0) {
+            dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+        }
+        src += deltaSrc;
+    }
+    return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremul not allowed
+    if (!opts.fPremultiplyAlpha) {
+        return nullptr;
+    }
+    if (opts.fSkipZeros) {
+        if (opts.fDither) {
+            return Sample_Index_D4444_D_SkipZ;
+        }
+        return Sample_Index_D4444_SkipZ;
+    }
+    if (opts.fDither) {
+        return Sample_Index_D4444_D;
+    }
+    return Sample_Index_D4444;
+}
+
+static bool Sample_Index_DI(void* SK_RESTRICT dstRow,
+                            const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int, const SkPMColor[]) {
+    if (1 == deltaSrc) {
+        memcpy(dstRow, src, width);
+    } else {
+        uint8_t* SK_RESTRICT dst = (uint8_t*)dstRow;
+        for (int x = 0; x < width; x++) {
+            dst[x] = src[0];
+            src += deltaSrc;
+        }
+    }
+    return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_index_proc(const SkScaledBitmapSampler::Options& opts) {
+    // Unpremul not allowed
+    if (!opts.fPremultiplyAlpha) {
+        return nullptr;
+    }
+    // Ignore dither and skip zeroes
+    return Sample_Index_DI;
+}
+
+// A8
+static bool Sample_Gray_DA8(void* SK_RESTRICT dstRow,
+                            const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int,
+                            const SkPMColor[]) {
+    // Sampling Gray to A8 uses the same function as Index to Index8,
+    // except we assume that there is alpha for speed, since an A8
+    // bitmap with no alpha is not interesting.
+    (void) Sample_Index_DI(dstRow, src, width, deltaSrc, /* y unused */ 0,
+                           /* ctable unused */ nullptr);
+    return true;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_A8_proc(const SkScaledBitmapSampler::Options& opts) {
+    if (!opts.fPremultiplyAlpha) {
+        return nullptr;
+    }
+    // Ignore skip and dither.
+    return Sample_Gray_DA8;
+}
+
+typedef SkScaledBitmapSampler::RowProc (*RowProcChooser)(const SkScaledBitmapSampler::Options&);
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkScaledBitmapSampler.h"
+
+SkScaledBitmapSampler::SkScaledBitmapSampler(int width, int height,
+                                             int sampleSize) {
+    fCTable = nullptr;
+    fDstRow = nullptr;
+    fRowProc = nullptr;
+
+    if (width <= 0 || height <= 0) {
+        sk_throw();
+    }
+
+    SkDEBUGCODE(fSampleMode = kUninitialized_SampleMode);
+
+    if (sampleSize <= 1) {
+        fScaledWidth = width;
+        fScaledHeight = height;
+        fX0 = fY0 = 0;
+        fDX = fDY = 1;
+        return;
+    }
+
+    int dx = SkMin32(sampleSize, width);
+    int dy = SkMin32(sampleSize, height);
+
+    fScaledWidth = width / dx;
+    fScaledHeight = height / dy;
+
+    SkASSERT(fScaledWidth > 0);
+    SkASSERT(fScaledHeight > 0);
+
+    fX0 = dx >> 1;
+    fY0 = dy >> 1;
+
+    SkASSERT(fX0 >= 0 && fX0 < width);
+    SkASSERT(fY0 >= 0 && fY0 < height);
+
+    fDX = dx;
+    fDY = dy;
+
+    SkASSERT(fDX > 0 && (fX0 + fDX * (fScaledWidth - 1)) < width);
+    SkASSERT(fDY > 0 && (fY0 + fDY * (fScaledHeight - 1)) < height);
+}
+
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc,
+                                  const Options& opts,
+                                  const SkPMColor ctable[]) {
+    static const RowProcChooser gProcChoosers[] = {
+        get_gray_to_8888_proc,
+        get_RGBx_to_8888_proc,
+        get_RGBA_to_8888_proc,
+        get_index_to_8888_proc,
+        nullptr, // 565 to 8888
+
+        get_gray_to_565_proc,
+        get_RGBx_to_565_proc,
+        get_RGBx_to_565_proc, // The source alpha will be ignored.
+        get_index_to_565_proc,
+        get_565_to_565_proc,
+
+        get_gray_to_4444_proc,
+        get_RGBx_to_4444_proc,
+        get_RGBA_to_4444_proc,
+        get_index_to_4444_proc,
+        nullptr, // 565 to 4444
+
+        nullptr, // gray to index
+        nullptr, // rgbx to index
+        nullptr, // rgba to index
+        get_index_to_index_proc,
+        nullptr, // 565 to index
+
+        get_gray_to_A8_proc,
+        nullptr, // rgbx to a8
+        nullptr, // rgba to a8
+        nullptr, // index to a8
+        nullptr, // 565 to a8
+    };
+
+    // The jump between dst configs in the table
+    static const int gProcDstConfigSpan = 5;
+    static_assert(SK_ARRAY_COUNT(gProcChoosers) == 5 * gProcDstConfigSpan,
+                  "gProcs_has_the_wrong_number_of_entries");
+
+    fCTable = ctable;
+
+    int index = 0;
+    switch (sc) {
+        case SkScaledBitmapSampler::kGray:
+            fSrcPixelSize = 1;
+            index += 0;
+            break;
+        case SkScaledBitmapSampler::kRGB:
+            fSrcPixelSize = 3;
+            index += 1;
+            break;
+        case SkScaledBitmapSampler::kRGBX:
+            fSrcPixelSize = 4;
+            index += 1;
+            break;
+        case SkScaledBitmapSampler::kRGBA:
+            fSrcPixelSize = 4;
+            index += 2;
+            break;
+        case SkScaledBitmapSampler::kIndex:
+            fSrcPixelSize = 1;
+            index += 3;
+            break;
+        case SkScaledBitmapSampler::kRGB_565:
+            fSrcPixelSize = 2;
+            index += 4;
+            break;
+        default:
+            return false;
+    }
+
+    switch (dst->colorType()) {
+        case kN32_SkColorType:
+            index += 0 * gProcDstConfigSpan;
+            break;
+        case kRGB_565_SkColorType:
+            index += 1 * gProcDstConfigSpan;
+            break;
+        case kARGB_4444_SkColorType:
+            index += 2 * gProcDstConfigSpan;
+            break;
+        case kIndex_8_SkColorType:
+            index += 3 * gProcDstConfigSpan;
+            break;
+        case kAlpha_8_SkColorType:
+            index += 4 * gProcDstConfigSpan;
+            break;
+        default:
+            return false;
+    }
+
+    RowProcChooser chooser = gProcChoosers[index];
+    if (nullptr == chooser) {
+        fRowProc = nullptr;
+    } else {
+        fRowProc = chooser(opts);
+    }
+    fDstRow = (char*)dst->getPixels();
+    fDstRowBytes = dst->rowBytes();
+    fCurrY = 0;
+    return fRowProc != nullptr;
+}
+
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc,
+                                  const SkImageDecoder& decoder,
+                                  const SkPMColor ctable[]) {
+    return this->begin(dst, sc, Options(decoder), ctable);
+}
+
+bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) {
+    SkASSERT(kInterlaced_SampleMode != fSampleMode);
+    SkDEBUGCODE(fSampleMode = kConsecutive_SampleMode);
+    SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight);
+
+    bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+                             fDX * fSrcPixelSize, fCurrY, fCTable);
+    fDstRow += fDstRowBytes;
+    fCurrY += 1;
+    return hadAlpha;
+}
+
+bool SkScaledBitmapSampler::sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY) {
+    SkASSERT(kConsecutive_SampleMode != fSampleMode);
+    SkDEBUGCODE(fSampleMode = kInterlaced_SampleMode);
+    // Any line that should be a part of the destination can be created by the formula:
+    // fY0 + (some multiplier) * fDY
+    // so if srcY - fY0 is not an integer multiple of fDY that srcY will be skipped.
+    const int srcYMinusY0 = srcY - fY0;
+    if (srcYMinusY0 % fDY != 0) {
+        // This line is not part of the output, so return false for alpha, since we have
+        // not added an alpha to the output.
+        return false;
+    }
+    // Unlike in next(), where the data is used sequentially, this function skips around,
+    // so fDstRow and fCurrY are never updated. fDstRow must always be the starting point
+    // of the destination bitmap's pixels, which is used to calculate the destination row
+    // each time this function is called.
+    const int dstY = srcYMinusY0 / fDY;
+    if (dstY >= fScaledHeight) {
+        return false;
+    }
+    char* dstRow = fDstRow + dstY * fDstRowBytes;
+    return fRowProc(dstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+                    fDX * fSrcPixelSize, dstY, fCTable);
+}
+
+#ifdef SK_DEBUG
+// The following code is for a test to ensure that changing the method to get the right row proc
+// did not change the row proc unintentionally. Tested by ImageDecodingTest.cpp
+
+// friend of SkScaledBitmapSampler solely for the purpose of accessing fRowProc.
+class RowProcTester {
+public:
+    static SkScaledBitmapSampler::RowProc getRowProc(const SkScaledBitmapSampler& sampler) {
+        return sampler.fRowProc;
+    }
+};
+
+
+// Table showing the expected RowProc for each combination of inputs.
+// Table formated as follows:
+// Each group of 5 consecutive rows represents sampling from a single
+// SkScaledBitmapSampler::SrcConfig.
+// Within each set, each row represents a different destination SkBitmap::Config
+// Each column represents a different combination of dither and unpremul.
+// D = dither   ~D = no dither
+// U = unpremul ~U = no unpremul
+//  ~D~U                D~U                     ~DU                         DU
+SkScaledBitmapSampler::RowProc gTestProcs[] = {
+    // Gray
+    Sample_Gray_DA8,    Sample_Gray_DA8,        nullptr,                       nullptr,                       // to A8
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to Index8
+    Sample_Gray_D565,   Sample_Gray_D565_D,     Sample_Gray_D565,           Sample_Gray_D565_D,         // to 565
+    Sample_Gray_D4444,  Sample_Gray_D4444_D,    Sample_Gray_D4444,          Sample_Gray_D4444_D,        // to 4444
+    Sample_Gray_D8888,  Sample_Gray_D8888,      Sample_Gray_D8888,          Sample_Gray_D8888,          // to 8888
+    // Index
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to A8
+    Sample_Index_DI,    Sample_Index_DI,        nullptr,                       nullptr,                       // to Index8
+    Sample_Index_D565,  Sample_Index_D565_D,    Sample_Index_D565,          Sample_Index_D565_D,        // to 565
+    Sample_Index_D4444, Sample_Index_D4444_D,   nullptr,                       nullptr,                       // to 4444
+    Sample_Index_D8888, Sample_Index_D8888,     Sample_Index_D8888,         Sample_Index_D8888,         // to 8888
+    // RGB
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to A8
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBx_D4444,  Sample_RGBx_D4444_D,    Sample_RGBx_D4444,          Sample_RGBx_D4444_D,        // to 4444
+    Sample_RGBx_D8888,  Sample_RGBx_D8888,      Sample_RGBx_D8888,          Sample_RGBx_D8888,          // to 8888
+    // RGBx is the same as RGB
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to A8
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBx_D4444,  Sample_RGBx_D4444_D,    Sample_RGBx_D4444,          Sample_RGBx_D4444_D,        // to 4444
+    Sample_RGBx_D8888,  Sample_RGBx_D8888,      Sample_RGBx_D8888,          Sample_RGBx_D8888,          // to 8888
+    // RGBA
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to A8
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to Index8
+    Sample_RGBx_D565,   Sample_RGBx_D565_D,     Sample_RGBx_D565,           Sample_RGBx_D565_D,         // to 565
+    Sample_RGBA_D4444,  Sample_RGBA_D4444_D,    nullptr,                       nullptr,                       // to 4444
+    Sample_RGBA_D8888,  Sample_RGBA_D8888,      Sample_RGBA_D8888_Unpremul, Sample_RGBA_D8888_Unpremul, // to 8888
+    // RGB_565
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to A8
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to Index8
+    Sample_D565_D565,   Sample_D565_D565,       Sample_D565_D565,           Sample_D565_D565,           // to 565
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to 4444
+    nullptr,               nullptr,                   nullptr,                       nullptr,                       // to 8888
+};
+
+// Dummy class that allows instantiation of an ImageDecoder, so begin can query its fields.
+class DummyDecoder : public SkImageDecoder {
+public:
+    DummyDecoder() {}
+protected:
+    Result onDecode(SkStream*, SkBitmap*, SkImageDecoder::Mode) override {
+        return kFailure;
+    }
+};
+
+void test_row_proc_choice();
+void test_row_proc_choice() {
+    const SkColorType colorTypes[] = {
+        kAlpha_8_SkColorType, kIndex_8_SkColorType, kRGB_565_SkColorType, kARGB_4444_SkColorType,
+        kN32_SkColorType
+    };
+
+    SkBitmap dummyBitmap;
+    DummyDecoder dummyDecoder;
+    size_t procCounter = 0;
+    for (int sc = SkScaledBitmapSampler::kGray; sc <= SkScaledBitmapSampler::kRGB_565; ++sc) {
+        for (size_t c = 0; c < SK_ARRAY_COUNT(colorTypes); ++c) {
+            for (int unpremul = 0; unpremul <= 1; ++unpremul) {
+                for (int dither = 0; dither <= 1; ++dither) {
+                    // Arbitrary width/height/sampleSize to allow SkScaledBitmapSampler to
+                    // be considered valid.
+                    SkScaledBitmapSampler sampler(10, 10, 1);
+                    dummyBitmap.setInfo(SkImageInfo::Make(10, 10,
+                                                          colorTypes[c], kPremul_SkAlphaType));
+                    dummyDecoder.setDitherImage(SkToBool(dither));
+                    dummyDecoder.setRequireUnpremultipliedColors(SkToBool(unpremul));
+                    sampler.begin(&dummyBitmap, (SkScaledBitmapSampler::SrcConfig) sc,
+                                  dummyDecoder);
+                    SkScaledBitmapSampler::RowProc expected = gTestProcs[procCounter];
+                    SkScaledBitmapSampler::RowProc actual = RowProcTester::getRowProc(sampler);
+                    SkASSERT(expected == actual);
+                    procCounter++;
+                }
+            }
+        }
+    }
+    SkASSERT(SK_ARRAY_COUNT(gTestProcs) == procCounter);
+}
+#endif // SK_DEBUG
diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h
new file mode 100644
index 0000000..198dc07
--- /dev/null
+++ b/src/images/SkScaledBitmapSampler.h
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkScaledBitmapSampler_DEFINED
+#define SkScaledBitmapSampler_DEFINED
+
+#include "SkTypes.h"
+#include "SkColor.h"
+#include "SkImageDecoder.h"
+
+class SkBitmap;
+
+class SkScaledBitmapSampler {
+public:
+    SkScaledBitmapSampler(int origWidth, int origHeight, int cellSize);
+
+    int scaledWidth() const { return fScaledWidth; }
+    int scaledHeight() const { return fScaledHeight; }
+
+    int srcY0() const { return fY0; }
+    int srcDX() const { return fDX; }
+    int srcDY() const { return fDY; }
+
+    enum SrcConfig {
+        kGray,  // 1 byte per pixel
+        kIndex, // 1 byte per pixel
+        kRGB,   // 3 bytes per pixel
+        kRGBX,  // 4 byes per pixel (ignore 4th)
+        kRGBA,  // 4 bytes per pixel
+        kRGB_565 // 2 bytes per pixel
+    };
+
+    struct Options {
+        bool fDither;
+        bool fPremultiplyAlpha;
+        bool fSkipZeros;
+        explicit Options(const SkImageDecoder &dec)
+            : fDither(dec.getDitherImage())
+            , fPremultiplyAlpha(!dec.getRequireUnpremultipliedColors())
+            , fSkipZeros(dec.getSkipWritingZeroes())
+            { }
+    };
+
+    // Given a dst bitmap (with pixels already allocated) and a src-config,
+    // 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, const SkImageDecoder& decoder,
+               const SkPMColor* = nullptr);
+    bool begin(SkBitmap* dst, SrcConfig sc, const Options& opts,
+               const SkPMColor* = nullptr);
+    // 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);
+
+    // Like next(), but specifies the y value of the source row, so the
+    // rows can come in any order. If the row is not part of the output
+    // sample, it will be skipped. Only sampleInterlaced OR next should
+    // be called for one SkScaledBitmapSampler.
+    bool sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY);
+
+    typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
+                            const uint8_t* SK_RESTRICT src,
+                            int width, int deltaSrc, int y,
+                            const SkPMColor[]);
+
+private:
+    int fScaledWidth;
+    int fScaledHeight;
+
+    int fX0;    // first X coord to sample
+    int fY0;    // first Y coord (scanline) to sample
+    int fDX;    // step between X samples
+    int fDY;    // step between Y samples
+
+#ifdef SK_DEBUG
+    // Keep track of whether the caller is using next or sampleInterlaced.
+    // Only one can be used per sampler.
+    enum SampleMode {
+        kUninitialized_SampleMode,
+        kConsecutive_SampleMode,
+        kInterlaced_SampleMode,
+    };
+
+    SampleMode fSampleMode;
+#endif
+
+    // setup state
+    char*   fDstRow; // points into bitmap's pixels
+    size_t  fDstRowBytes;
+    int     fCurrY; // used for dithering
+    int     fSrcPixelSize;  // 1, 3, 4
+    RowProc fRowProc;
+
+    // optional reference to the src colors if the src is a palette model
+    const SkPMColor* fCTable;
+
+#ifdef SK_DEBUG
+    // Helper class allowing a test to have access to fRowProc.
+    friend class RowProcTester;
+#endif
+};
+
+#endif
diff --git a/src/images/bmpdecoderhelper.cpp b/src/images/bmpdecoderhelper.cpp
new file mode 100644
index 0000000..9171b5d
--- /dev/null
+++ b/src/images/bmpdecoderhelper.cpp
@@ -0,0 +1,369 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Author: cevans@google.com (Chris Evans)
+
+#include "bmpdecoderhelper.h"
+
+namespace image_codec {
+
+static const int kBmpHeaderSize = 14;
+static const int kBmpInfoSize = 40;
+static const int kBmpOS2InfoSize = 12;
+static const int kMaxDim = SHRT_MAX / 2;
+
+bool BmpDecoderHelper::DecodeImage(const char* p,
+                                   size_t len,
+                                   int max_pixels,
+                                   BmpDecoderCallback* callback) {
+  data_ = reinterpret_cast<const uint8*>(p);
+  pos_ = 0;
+  len_ = len;
+  inverted_ = true;
+  // Parse the header structure.
+  if (len < kBmpHeaderSize + 4) {
+    return false;
+  }
+  GetShort();  // Signature.
+  GetInt();  // Size.
+  GetInt();  // Reserved.
+  int offset = GetInt();
+  // Parse the info structure.
+  int infoSize = GetInt();
+  if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) {
+    return false;
+  }
+  int cols = 0;
+  int comp = 0;
+  int colLen = 4;
+  if (infoSize >= kBmpInfoSize) {
+    if (len < kBmpHeaderSize + kBmpInfoSize) {
+      return false;
+    }
+    width_ = GetInt();
+    height_ = GetInt();
+    GetShort();  // Planes.
+    bpp_ = GetShort();
+    comp = GetInt();
+    GetInt();  // Size.
+    GetInt();  // XPPM.
+    GetInt();  // YPPM.
+    cols = GetInt();
+    GetInt();  // Important colours.
+  } else {
+    if (len < kBmpHeaderSize + kBmpOS2InfoSize) {
+      return false;
+    }
+    colLen = 3;
+    width_ = GetShort();
+    height_ = GetShort();
+    GetShort();  // Planes.
+    bpp_ = GetShort();
+  }
+  if (height_ < 0) {
+    height_ = -height_;
+    inverted_ = false;
+  }
+  if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) {
+    return false;
+  }
+  if (width_ * height_ > max_pixels) {
+    return false;
+  }
+  if (cols < 0 || cols > 256) {
+    return false;
+  }
+  // Allocate then read in the colour map.
+  if (cols == 0 && bpp_ <= 8) {
+    cols = 1 << bpp_;
+  }
+  if (bpp_ <= 8 || cols > 0) {
+    uint8* colBuf = new uint8[256 * 3];
+    memset(colBuf, '\0', 256 * 3);
+    colTab_.reset(colBuf);
+  }
+  if (cols > 0) {
+    if (pos_ + (cols * colLen) > len_) {
+      return false;
+    }
+    for (int i = 0; i < cols; ++i) {
+      int base = i * 3;
+      colTab_[base + 2] = GetByte();
+      colTab_[base + 1] = GetByte();
+      colTab_[base] = GetByte();
+      if (colLen == 4) {
+        GetByte();
+      }
+    }
+  }
+  // Read in the compression data if necessary.
+  redBits_ = 0x7c00;
+  greenBits_ = 0x03e0;
+  blueBits_ = 0x001f;
+  bool rle = false;
+  if (comp == 1 || comp == 2) {
+    rle = true;
+  } else if (comp == 3) {
+    if (pos_ + 12 > len_) {
+      return false;
+    }
+    redBits_ = GetInt() & 0xffff;
+    greenBits_ = GetInt() & 0xffff;
+    blueBits_ = GetInt() & 0xffff;
+  }
+  redShiftRight_ = CalcShiftRight(redBits_);
+  greenShiftRight_ = CalcShiftRight(greenBits_);
+  blueShiftRight_ = CalcShiftRight(blueBits_);
+  redShiftLeft_ = CalcShiftLeft(redBits_);
+  greenShiftLeft_ = CalcShiftLeft(greenBits_);
+  blueShiftLeft_ = CalcShiftLeft(blueBits_);
+  rowPad_ = 0;
+  pixelPad_ = 0;
+  int rowLen;
+  if (bpp_ == 32) {
+    rowLen = width_ * 4;
+    pixelPad_ = 1;
+  } else if (bpp_ == 24) {
+    rowLen = width_ * 3;
+  } else if (bpp_ == 16) {
+    rowLen = width_ * 2;
+  } else if (bpp_ == 8) {
+    rowLen = width_;
+  } else if (bpp_ == 4) {
+    rowLen = width_ / 2;
+    if (width_ & 1) {
+      rowLen++;
+    }
+  } else if (bpp_ == 1) {
+    rowLen = width_ / 8;
+    if (width_ & 7) {
+      rowLen++;
+    }
+  } else {
+    return false;
+  }
+  // Round the rowLen up to a multiple of 4.
+  if (rowLen % 4 != 0) {
+    rowPad_ = 4 - (rowLen % 4);
+    rowLen += rowPad_;
+  }
+
+  if (offset > 0 && (size_t)offset > pos_ && (size_t)offset < len_) {
+    pos_ = offset;
+  }
+  // Deliberately off-by-one; a load of BMPs seem to have their last byte
+  // missing.
+  if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) {
+    return false;
+  }
+
+  output_ = callback->SetSize(width_, height_);
+  if (nullptr == output_) {
+    return true;  // meaning we succeeded, but they want us to stop now
+  }
+
+  if (rle && (bpp_ == 4 || bpp_ == 8)) {
+    DoRLEDecode();
+  } else {
+    DoStandardDecode();
+  }
+  return true;
+}
+
+void BmpDecoderHelper::DoRLEDecode() {
+  static const uint8 RLE_ESCAPE = 0;
+  static const uint8 RLE_EOL = 0;
+  static const uint8 RLE_EOF = 1;
+  static const uint8 RLE_DELTA = 2;
+  int x = 0;
+  int y = height_ - 1;
+  while (pos_ + 1 < len_) {
+    uint8 cmd = GetByte();
+    if (cmd != RLE_ESCAPE) {
+      uint8 pixels = GetByte();
+      int num = 0;
+      uint8 col = pixels;
+      while (cmd-- && x < width_) {
+        if (bpp_ == 4) {
+          if (num & 1) {
+            col = pixels & 0xf;
+          } else {
+            col = pixels >> 4;
+          }
+        }
+        PutPixel(x++, y, col);
+        num++;
+      }
+    } else {
+      cmd = GetByte();
+      if (cmd == RLE_EOF) {
+        return;
+      } else if (cmd == RLE_EOL) {
+        x = 0;
+        y--;
+        if (y < 0) {
+          return;
+        }
+      } else if (cmd == RLE_DELTA) {
+        if (pos_ + 1 < len_) {
+          uint8 dx = GetByte();
+          uint8 dy = GetByte();
+          x += dx;
+          if (x > width_) {
+            x = width_;
+          }
+          y -= dy;
+          if (y < 0) {
+            return;
+          }
+        }
+      } else {
+        int num = 0;
+        int bytesRead = 0;
+        uint8 val = 0;
+        while (cmd-- && pos_ < len_) {
+          if (bpp_ == 8 || !(num & 1)) {
+            val = GetByte();
+            bytesRead++;
+          }
+          uint8 col = val;
+          if (bpp_ == 4) {
+            if (num & 1) {
+              col = col & 0xf;
+            } else {
+              col >>= 4;
+            }
+          }
+          if (x < width_) {
+            PutPixel(x++, y, col);
+          }
+          num++;
+        }
+        // All pixel runs must be an even number of bytes - skip a byte if we
+        // read an odd number.
+        if ((bytesRead & 1) && pos_ < len_) {
+          GetByte();
+        }
+      }
+    }
+  }
+}
+
+void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) {
+  CHECK(x >= 0 && x < width_);
+  CHECK(y >= 0 && y < height_);
+  if (!inverted_) {
+    y = height_ - (y + 1);
+  }
+
+  int base = ((y * width_) + x) * 3;
+  int colBase = col * 3;
+  output_[base] = colTab_[colBase];
+  output_[base + 1] = colTab_[colBase + 1];
+  output_[base + 2] = colTab_[colBase + 2];
+}
+
+void BmpDecoderHelper::DoStandardDecode() {
+  int row = 0;
+  uint8 currVal = 0;
+  for (int h = height_ - 1; h >= 0; h--, row++) {
+    int realH = h;
+    if (!inverted_) {
+      realH = height_ - (h + 1);
+    }
+    uint8* line = output_ + (3 * width_ * realH);
+    for (int w = 0; w < width_; w++) {
+      if (bpp_ >= 24) {
+        line[2] = GetByte();
+        line[1] = GetByte();
+        line[0] = GetByte();
+      } else if (bpp_ == 16) {
+        uint32 val = GetShort();
+        line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_;
+        line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_;
+        line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_;
+      } else if (bpp_ <= 8) {
+        uint8 col;
+        if (bpp_ == 8) {
+          col = GetByte();
+        } else if (bpp_ == 4) {
+          if ((w % 2) == 0) {
+            currVal = GetByte();
+            col = currVal >> 4;
+          } else {
+            col = currVal & 0xf;
+          }
+        } else {
+          if ((w % 8) == 0) {
+            currVal = GetByte();
+          }
+          int bit = w & 7;
+          col = ((currVal >> (7 - bit)) & 1);
+        }
+        int base = col * 3;
+        line[0] = colTab_[base];
+        line[1] = colTab_[base + 1];
+        line[2] = colTab_[base + 2];
+      }
+      line += 3;
+      for (int i = 0; i < pixelPad_; ++i) {
+        GetByte();
+      }
+    }
+    for (int i = 0; i < rowPad_; ++i) {
+      GetByte();
+    }
+  }
+}
+
+int BmpDecoderHelper::GetInt() {
+  uint8 b1 = GetByte();
+  uint8 b2 = GetByte();
+  uint8 b3 = GetByte();
+  uint8 b4 = GetByte();
+  return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
+}
+
+int BmpDecoderHelper::GetShort() {
+  uint8 b1 = GetByte();
+  uint8 b2 = GetByte();
+  return b1 | (b2 << 8);
+}
+
+uint8 BmpDecoderHelper::GetByte() {
+  CHECK(pos_ <= len_);
+  // We deliberately allow this off-by-one access to cater for BMPs with their
+  // last byte missing.
+  if (pos_ == len_) {
+    return 0;
+  }
+  return data_[pos_++];
+}
+
+int BmpDecoderHelper::CalcShiftRight(uint32 mask) {
+  int ret = 0;
+  while (mask != 0 && !(mask & 1)) {
+    mask >>= 1;
+    ret++;
+  }
+  return ret;
+}
+
+int BmpDecoderHelper::CalcShiftLeft(uint32 mask) {
+  int ret = 0;
+  while (mask != 0 && !(mask & 1)) {
+    mask >>= 1;
+  }
+  while (mask != 0 && !(mask & 0x80)) {
+    mask <<= 1;
+    ret++;
+  }
+  return ret;
+}
+
+}  // namespace image_codec
diff --git a/src/images/bmpdecoderhelper.h b/src/images/bmpdecoderhelper.h
new file mode 100644
index 0000000..b448734
--- /dev/null
+++ b/src/images/bmpdecoderhelper.h
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef IMAGE_CODEC_BMPDECODERHELPER_H__
+#define IMAGE_CODEC_BMPDECODERHELPER_H__
+
+///////////////////////////////////////////////////////////////////////////////
+// this section is my current "glue" between google3 code and android.
+// will be fixed soon
+
+#include "SkTypes.h"
+#include <limits.h>
+#define DISALLOW_EVIL_CONSTRUCTORS(name)
+#define CHECK(predicate)  SkASSERT(predicate)
+typedef uint8_t uint8;
+typedef uint32_t uint32;
+
+template <typename T> class scoped_array {
+private:
+  T* ptr_;
+  scoped_array(scoped_array const&);
+  scoped_array& operator=(const scoped_array&);
+
+public:
+  explicit scoped_array(T* p = 0) : ptr_(p) {}
+  ~scoped_array() {
+    delete[] ptr_;
+  }
+
+  void reset(T* p = 0) {
+    if (p != ptr_) {
+      delete[] ptr_;
+      ptr_ = p;
+    }
+  }
+
+  T& operator[](int i) const {
+    return ptr_[i];
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace image_codec {
+
+class BmpDecoderCallback {
+ public:
+  BmpDecoderCallback() { }
+  virtual ~BmpDecoderCallback() {}
+
+  /**
+   * This is called once for an image. It is passed the width and height and
+   * should return the address of a buffer that is large enough to store
+   * all of the resulting pixels (widht * height * 3 bytes). If it returns nullptr,
+   * then the decoder will abort, but return true, as the caller has received
+   * valid dimensions.
+   */
+  virtual uint8* SetSize(int width, int height) = 0;
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderCallback);
+};
+
+class BmpDecoderHelper {
+ public:
+  BmpDecoderHelper() { }
+  ~BmpDecoderHelper() { }
+  bool DecodeImage(const char* data,
+                   size_t len,
+                   int max_pixels,
+                   BmpDecoderCallback* callback);
+
+ private:
+  DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderHelper);
+
+  void DoRLEDecode();
+  void DoStandardDecode();
+  void PutPixel(int x, int y, uint8 col);
+
+  int GetInt();
+  int GetShort();
+  uint8 GetByte();
+  int CalcShiftRight(uint32 mask);
+  int CalcShiftLeft(uint32 mask);
+
+  const uint8* data_;
+  size_t pos_;
+  size_t len_;
+  int width_;
+  int height_;
+  int bpp_;
+  int pixelPad_;
+  int rowPad_;
+  scoped_array<uint8> colTab_;
+  uint32 redBits_;
+  uint32 greenBits_;
+  uint32 blueBits_;
+  int redShiftRight_;
+  int greenShiftRight_;
+  int blueShiftRight_;
+  int redShiftLeft_;
+  int greenShiftLeft_;
+  int blueShiftLeft_;
+  uint8* output_;
+  bool inverted_;
+};
+
+} // namespace
+
+#endif
diff --git a/src/ports/SkImageDecoder_CG.cpp b/src/ports/SkImageDecoder_CG.cpp
index ead0ed6..c4446ae 100644
--- a/src/ports/SkImageDecoder_CG.cpp
+++ b/src/ports/SkImageDecoder_CG.cpp
@@ -11,6 +11,7 @@
 #include "SkCGUtils.h"
 #include "SkColorPriv.h"
 #include "SkData.h"
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkMovie.h"
 #include "SkStream.h"
@@ -28,6 +29,210 @@
 #include <MobileCoreServices/MobileCoreServices.h>
 #endif
 
+static void data_unref_proc(void* skdata, const void*, size_t) {
+    SkASSERT(skdata);
+    static_cast<SkData*>(skdata)->unref();
+}
+
+static CGDataProviderRef SkStreamToDataProvider(SkStream* stream) {
+    // TODO: use callbacks, so we don't have to load all the data into RAM
+    SkData* skdata = SkCopyStreamToData(stream).release();
+    if (!skdata) {
+        return nullptr;
+    }
+
+    return CGDataProviderCreateWithData(skdata, skdata->data(), skdata->size(), data_unref_proc);
+}
+
+static CGImageSourceRef SkStreamToCGImageSource(SkStream* stream) {
+    CGDataProviderRef data = SkStreamToDataProvider(stream);
+    if (!data) {
+        return nullptr;
+    }
+    CGImageSourceRef imageSrc = CGImageSourceCreateWithDataProvider(data, 0);
+    CGDataProviderRelease(data);
+    return imageSrc;
+}
+
+class SkImageDecoder_CG : public SkImageDecoder {
+protected:
+    virtual Result onDecode(SkStream* stream, SkBitmap* bm, Mode);
+};
+
+static void argb_4444_force_opaque(void* row, int count) {
+    uint16_t* row16 = (uint16_t*)row;
+    for (int i = 0; i < count; ++i) {
+        row16[i] |= 0xF000;
+    }
+}
+
+static void argb_8888_force_opaque(void* row, int count) {
+    // can use RGBA or BGRA, they have the same shift for alpha
+    const uint32_t alphaMask = 0xFF << SK_RGBA_A32_SHIFT;
+    uint32_t* row32 = (uint32_t*)row;
+    for (int i = 0; i < count; ++i) {
+        row32[i] |= alphaMask;
+    }
+}
+
+static void alpha_8_force_opaque(void* row, int count) {
+    memset(row, 0xFF, count);
+}
+
+static void force_opaque(SkBitmap* bm) {
+    SkAutoLockPixels alp(*bm);
+    if (!bm->getPixels()) {
+        return;
+    }
+
+    void (*proc)(void*, int);
+    switch (bm->colorType()) {
+        case kARGB_4444_SkColorType:
+            proc = argb_4444_force_opaque;
+            break;
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            proc = argb_8888_force_opaque;
+            break;
+        case kAlpha_8_SkColorType:
+            proc = alpha_8_force_opaque;
+            break;
+        default:
+            return;
+    }
+
+    char* row = (char*)bm->getPixels();
+    for (int y = 0; y < bm->height(); ++y) {
+        proc(row, bm->width());
+        row += bm->rowBytes();
+    }
+    bm->setAlphaType(kOpaque_SkAlphaType);
+}
+
+#define BITMAP_INFO (kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast)
+
+class AutoCFDataRelease {
+    CFDataRef fDR;
+public:
+    AutoCFDataRelease(CFDataRef dr) : fDR(dr) {}
+    ~AutoCFDataRelease() { if (fDR) { CFRelease(fDR); } }
+
+    operator CFDataRef () { return fDR; }
+};
+
+static bool colorspace_is_sRGB(CGColorSpaceRef cs) {
+#ifdef SK_BUILD_FOR_IOS
+    return true;    // iOS seems to define itself to always return sRGB <reed>
+#else
+    AutoCFDataRelease data(CGColorSpaceCopyICCProfile(cs));
+    if (data) {
+        // found by inspection -- need a cleaner way to sniff a profile
+        const CFIndex ICC_PROFILE_OFFSET_TO_SRGB_TAG = 52;
+
+        if (CFDataGetLength(data) >= ICC_PROFILE_OFFSET_TO_SRGB_TAG + 4) {
+            return !memcmp(CFDataGetBytePtr(data) + ICC_PROFILE_OFFSET_TO_SRGB_TAG, "sRGB", 4);
+        }
+    }
+    return false;
+#endif
+}
+
+SkImageDecoder::Result SkImageDecoder_CG::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+    if (nullptr == imageSrc) {
+        return kFailure;
+    }
+    SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+
+    CGImageRef image = CGImageSourceCreateImageAtIndex(imageSrc, 0, nullptr);
+    if (nullptr == image) {
+        return kFailure;
+    }
+    SkAutoTCallVProc<CGImage, CGImageRelease> arimage(image);
+
+    const int width = SkToInt(CGImageGetWidth(image));
+    const int height = SkToInt(CGImageGetHeight(image));
+    SkColorProfileType cpType = kLinear_SkColorProfileType;
+
+    CGColorSpaceRef cs = CGImageGetColorSpace(image);
+    if (cs) {
+        CGColorSpaceModel m = CGColorSpaceGetModel(cs);
+        if (kCGColorSpaceModelRGB == m && colorspace_is_sRGB(cs)) {
+            cpType = kSRGB_SkColorProfileType;
+        }
+    }
+
+    SkAlphaType at = kPremul_SkAlphaType;
+    switch (CGImageGetAlphaInfo(image)) {
+        case kCGImageAlphaNone:
+        case kCGImageAlphaNoneSkipLast:
+        case kCGImageAlphaNoneSkipFirst:
+            at = kOpaque_SkAlphaType;
+            break;
+        default:
+            break;
+    }
+
+    bm->setInfo(SkImageInfo::Make(width, height, kN32_SkColorType, at, cpType));
+    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+        return kSuccess;
+    }
+
+    if (!this->allocPixelRef(bm, nullptr)) {
+        return kFailure;
+    }
+
+    SkAutoLockPixels alp(*bm);
+
+    if (!SkCopyPixelsFromCGImage(bm->info(), bm->rowBytes(), bm->getPixels(), image)) {
+        return kFailure;
+    }
+
+    CGImageAlphaInfo info = CGImageGetAlphaInfo(image);
+    switch (info) {
+        case kCGImageAlphaNone:
+        case kCGImageAlphaNoneSkipLast:
+        case kCGImageAlphaNoneSkipFirst:
+            // We're opaque, but we can't rely on the data always having 0xFF
+            // in the alpha slot (which Skia wants), so we have to ram it in
+            // ourselves.
+            force_opaque(bm);
+            break;
+        default:
+            // we don't know if we're opaque or not, so compute it.
+            if (SkBitmap::ComputeIsOpaque(*bm)) {
+                bm->setAlphaType(kOpaque_SkAlphaType);
+            }
+    }
+    if (!bm->isOpaque() && this->getRequireUnpremultipliedColors()) {
+        // CGBitmapContext does not support unpremultiplied, so the image has been premultiplied.
+        // Convert to unpremultiplied.
+        for (int i = 0; i < width; ++i) {
+            for (int j = 0; j < height; ++j) {
+                uint32_t* addr = bm->getAddr32(i, j);
+                *addr = SkUnPreMultiply::UnPreMultiplyPreservingByteOrder(*addr);
+            }
+        }
+        bm->setAlphaType(kUnpremul_SkAlphaType);
+    }
+    return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+    SkImageDecoder* decoder = image_decoder_from_stream(stream);
+    if (nullptr == decoder) {
+        // If no image decoder specific to the stream exists, use SkImageDecoder_CG.
+        return new SkImageDecoder_CG;
+    } else {
+        return decoder;
+    }
+}
+
 /////////////////////////////////////////////////////////////////////////
 
 SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
@@ -150,13 +355,57 @@
 
 static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_cg_factory);
 
-class SkPNGImageEncoder_CG : public SkImageEncoder_CG {
+#ifdef SK_BUILD_FOR_IOS
+class SkPNGImageEncoder_IOS : public SkImageEncoder_CG {
 public:
-    SkPNGImageEncoder_CG()
+    SkPNGImageEncoder_IOS()
         : SkImageEncoder_CG(kPNG_Type) {
     }
 };
 
-DEFINE_ENCODER_CREATOR(PNGImageEncoder_CG);
+DEFINE_ENCODER_CREATOR(PNGImageEncoder_IOS);
+#endif
+
+struct FormatConversion {
+    CFStringRef             fUTType;
+    SkImageDecoder::Format  fFormat;
+};
+
+// Array of the types supported by the decoder.
+static const FormatConversion gFormatConversions[] = {
+    { kUTTypeBMP, SkImageDecoder::kBMP_Format },
+    { kUTTypeGIF, SkImageDecoder::kGIF_Format },
+    { kUTTypeICO, SkImageDecoder::kICO_Format },
+    { kUTTypeJPEG, SkImageDecoder::kJPEG_Format },
+    // Also include JPEG2000
+    { kUTTypeJPEG2000, SkImageDecoder::kJPEG_Format },
+    { kUTTypePNG, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format UTType_to_Format(const CFStringRef uttype) {
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+        if (CFStringCompare(uttype, gFormatConversions[i].fUTType, 0) == kCFCompareEqualTo) {
+            return gFormatConversions[i].fFormat;
+        }
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder::Format get_format_cg(SkStreamRewindable* stream) {
+    CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+    if (nullptr == imageSrc) {
+        return SkImageDecoder::kUnknown_Format;
+    }
+
+    SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+    const CFStringRef name = CGImageSourceGetType(imageSrc);
+    if (nullptr == name) {
+        return SkImageDecoder::kUnknown_Format;
+    }
+    return UTType_to_Format(name);
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_cg);
 
 #endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp
index 43068fc..5febd85 100644
--- a/src/ports/SkImageDecoder_WIC.cpp
+++ b/src/ports/SkImageDecoder_WIC.cpp
@@ -31,6 +31,7 @@
 
 #include <wincodec.h>
 #include "SkAutoCoInitialize.h"
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkIStream.h"
 #include "SkMovie.h"
@@ -47,6 +48,222 @@
 #undef CLSID_WICImagingFactory
 #endif
 
+class SkImageDecoder_WIC : public SkImageDecoder {
+public:
+    // Decoding modes corresponding to SkImageDecoder::Mode, plus an extra mode for decoding
+    // only the format.
+    enum WICModes {
+        kDecodeFormat_WICMode,
+        kDecodeBounds_WICMode,
+        kDecodePixels_WICMode,
+    };
+
+    /**
+     *  Helper function to decode an SkStream.
+     *  @param stream SkStream to decode. Must be at the beginning.
+     *  @param bm   SkBitmap to decode into. Only used if wicMode is kDecodeBounds_WICMode or
+     *      kDecodePixels_WICMode, in which case it must not be nullptr.
+     *  @param format Out parameter for the SkImageDecoder::Format of the SkStream. Only used if
+     *      wicMode is kDecodeFormat_WICMode.
+     */
+    bool decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode, Format* format) const;
+
+protected:
+    Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+};
+
+struct FormatConversion {
+    GUID                    fGuidFormat;
+    SkImageDecoder::Format  fFormat;
+};
+
+static const FormatConversion gFormatConversions[] = {
+    { GUID_ContainerFormatBmp, SkImageDecoder::kBMP_Format },
+    { GUID_ContainerFormatGif, SkImageDecoder::kGIF_Format },
+    { GUID_ContainerFormatIco, SkImageDecoder::kICO_Format },
+    { GUID_ContainerFormatJpeg, SkImageDecoder::kJPEG_Format },
+    { GUID_ContainerFormatPng, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format GuidContainerFormat_to_Format(REFGUID guid) {
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+        if (IsEqualGUID(guid, gFormatConversions[i].fGuidFormat)) {
+            return gFormatConversions[i].fFormat;
+        }
+    }
+    return SkImageDecoder::kUnknown_Format;
+}
+
+SkImageDecoder::Result SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+    WICModes wicMode;
+    switch (mode) {
+        case SkImageDecoder::kDecodeBounds_Mode:
+            wicMode = kDecodeBounds_WICMode;
+            break;
+        case SkImageDecoder::kDecodePixels_Mode:
+            wicMode = kDecodePixels_WICMode;
+            break;
+    }
+    return this->decodeStream(stream, bm, wicMode, nullptr) ? kSuccess : kFailure;
+}
+
+bool SkImageDecoder_WIC::decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode,
+                                      Format* format) const {
+    //Initialize COM.
+    SkAutoCoInitialize scopedCo;
+    if (!scopedCo.succeeded()) {
+        return false;
+    }
+
+    HRESULT hr = S_OK;
+
+    //Create Windows Imaging Component ImagingFactory.
+    SkTScopedComPtr<IWICImagingFactory> piImagingFactory;
+    if (SUCCEEDED(hr)) {
+        hr = CoCreateInstance(
+            CLSID_WICImagingFactory
+            , nullptr
+            , CLSCTX_INPROC_SERVER
+            , IID_PPV_ARGS(&piImagingFactory)
+        );
+    }
+
+    //Convert SkStream to IStream.
+    SkTScopedComPtr<IStream> piStream;
+    if (SUCCEEDED(hr)) {
+        hr = SkIStream::CreateFromSkStream(stream, false, &piStream);
+    }
+
+    //Make sure we're at the beginning of the stream.
+    if (SUCCEEDED(hr)) {
+        LARGE_INTEGER liBeginning = { 0 };
+        hr = piStream->Seek(liBeginning, STREAM_SEEK_SET, nullptr);
+    }
+
+    //Create the decoder from the stream content.
+    SkTScopedComPtr<IWICBitmapDecoder> piBitmapDecoder;
+    if (SUCCEEDED(hr)) {
+        hr = piImagingFactory->CreateDecoderFromStream(
+            piStream.get()                    //Image to be decoded
+            , nullptr                            //No particular vendor
+            , WICDecodeMetadataCacheOnDemand  //Cache metadata when needed
+            , &piBitmapDecoder                //Pointer to the decoder
+        );
+    }
+
+    if (kDecodeFormat_WICMode == wicMode) {
+        SkASSERT(format != nullptr);
+        //Get the format
+        if (SUCCEEDED(hr)) {
+            GUID guidFormat;
+            hr = piBitmapDecoder->GetContainerFormat(&guidFormat);
+            if (SUCCEEDED(hr)) {
+                *format = GuidContainerFormat_to_Format(guidFormat);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //Get the first frame from the decoder.
+    SkTScopedComPtr<IWICBitmapFrameDecode> piBitmapFrameDecode;
+    if (SUCCEEDED(hr)) {
+        hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode);
+    }
+
+    //Get the BitmapSource interface of the frame.
+    SkTScopedComPtr<IWICBitmapSource> piBitmapSourceOriginal;
+    if (SUCCEEDED(hr)) {
+        hr = piBitmapFrameDecode->QueryInterface(
+            IID_PPV_ARGS(&piBitmapSourceOriginal)
+        );
+    }
+
+    //Get the size of the bitmap.
+    UINT width;
+    UINT height;
+    if (SUCCEEDED(hr)) {
+        hr = piBitmapSourceOriginal->GetSize(&width, &height);
+    }
+
+    //Exit early if we're only looking for the bitmap bounds.
+    if (SUCCEEDED(hr)) {
+        bm->setInfo(SkImageInfo::MakeN32Premul(width, height));
+        if (kDecodeBounds_WICMode == wicMode) {
+            return true;
+        }
+        if (!this->allocPixelRef(bm, nullptr)) {
+            return false;
+        }
+    }
+
+    //Create a format converter.
+    SkTScopedComPtr<IWICFormatConverter> piFormatConverter;
+    if (SUCCEEDED(hr)) {
+        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
+            , destinationPixelFormat          //Destination pixel format
+            , WICBitmapDitherTypeNone         //Specified dither patterm
+            , nullptr                            //Specify a particular palette
+            , 0.f                             //Alpha threshold
+            , WICBitmapPaletteTypeCustom      //Palette translation type
+        );
+    }
+
+    //Get the BitmapSource interface of the format converter.
+    SkTScopedComPtr<IWICBitmapSource> piBitmapSourceConverted;
+    if (SUCCEEDED(hr)) {
+        hr = piFormatConverter->QueryInterface(
+            IID_PPV_ARGS(&piBitmapSourceConverted)
+        );
+    }
+
+    //Copy the pixels into the bitmap.
+    if (SUCCEEDED(hr)) {
+        SkAutoLockPixels alp(*bm);
+        bm->eraseColor(SK_ColorTRANSPARENT);
+        const UINT stride = (UINT) bm->rowBytes();
+        hr = piBitmapSourceConverted->CopyPixels(
+            nullptr,                             //Get all the pixels
+            stride,
+            stride * height,
+            reinterpret_cast<BYTE *>(bm->getPixels())
+        );
+
+        // Note: we don't need to premultiply here since we specified PBGRA
+        if (SkBitmap::ComputeIsOpaque(*bm)) {
+            bm->setAlphaType(kOpaque_SkAlphaType);
+        }
+    }
+
+    return SUCCEEDED(hr);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+    SkImageDecoder* decoder = image_decoder_from_stream(stream);
+    if (nullptr == decoder) {
+        // If no image decoder specific to the stream exists, use SkImageDecoder_WIC.
+        return new SkImageDecoder_WIC;
+    } else {
+        return decoder;
+    }
+}
+
 /////////////////////////////////////////////////////////////////////////
 
 SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
@@ -58,10 +275,6 @@
 class SkImageEncoder_WIC : public SkImageEncoder {
 public:
     SkImageEncoder_WIC(Type t) : fType(t) {}
-    
-    // DO NOT USE this constructor.  This exists only so SkForceLinking can
-    // link the WIC image encoder.
-    SkImageEncoder_WIC() {}
 
 protected:
     virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
@@ -241,6 +454,15 @@
 
 static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_wic_factory);
 
-DEFINE_ENCODER_CREATOR(ImageEncoder_WIC);
+static SkImageDecoder::Format get_format_wic(SkStreamRewindable* stream) {
+    SkImageDecoder::Format format;
+    SkImageDecoder_WIC codec;
+    if (!codec.decodeStream(stream, nullptr, SkImageDecoder_WIC::kDecodeFormat_WICMode, &format)) {
+        format = SkImageDecoder::kUnknown_Format;
+    }
+    return format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_wic);
 
 #endif // defined(SK_BUILD_FOR_WIN32)
diff --git a/src/ports/SkImageDecoder_empty.cpp b/src/ports/SkImageDecoder_empty.cpp
index 33e07ac..f52dada 100644
--- a/src/ports/SkImageDecoder_empty.cpp
+++ b/src/ports/SkImageDecoder_empty.cpp
@@ -8,11 +8,74 @@
 
 #include "SkBitmap.h"
 #include "SkImage.h"
+#include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkMovie.h"
 #include "SkPixelSerializer.h"
 #include "SkStream.h"
 
+class SkColorTable;
+class SkPngChunkReader;
+
+// Empty implementations for SkImageDecoder.
+
+SkImageDecoder::SkImageDecoder() {}
+
+SkImageDecoder::~SkImageDecoder() {}
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable*) {
+    return nullptr;
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* ) {}
+
+bool SkImageDecoder::DecodeFile(const char[], SkBitmap*, SkColorType, Mode, Format*) {
+    return false;
+}
+
+SkImageDecoder::Result SkImageDecoder::decode(SkStream*, SkBitmap*, SkColorType, Mode) {
+    return kFailure;
+}
+
+bool SkImageDecoder::DecodeStream(SkStreamRewindable*, SkBitmap*, SkColorType, Mode, Format*) {
+    return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void*, size_t, SkBitmap*, SkColorType, Mode, Format*) {
+    return false;
+}
+
+bool SkImageDecoder::decodeYUV8Planes(SkStream*, SkISize[3], void*[3],
+                                      size_t[3], SkYUVColorSpace*) {
+    return false;
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+    return kUnknown_Format;
+}
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStreamRewindable*) {
+    return kUnknown_Format;
+}
+
+const char* SkImageDecoder::GetFormatName(Format) {
+    return nullptr;
+}
+
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader*) {
+    return nullptr;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator*) {
+    return nullptr;
+}
+
+void SkImageDecoder::setSampleSize(int) {}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap*, SkColorTable*) const {
+    return false;
+}
+
 /////////////////////////////////////////////////////////////////////////
 
 // Empty implementation for SkMovie.