Create an SkAndroidCodec API separate from SkCodec

We will implement this API using SkCodecs.

SkAndroidCodecs will be used to implement the
BitmapRegionDecoder Java API (and possibly
BitmapFactory).

BUG=skia:

Review URL: https://codereview.chromium.org/1406223002
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 5d98fe7..c51a6fb 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -226,9 +226,6 @@
         case CodecSrc::kCodec_Mode:
             folder.append("codec");
             break;
-        case CodecSrc::kScaledCodec_Mode:
-            folder.append("scaled_codec");
-            break;
         case CodecSrc::kScanline_Mode:
             folder.append("scanline");
             break;
@@ -262,6 +259,37 @@
     push_src("image", folder, src);
 }
 
+static void push_android_codec_src(Path path, AndroidCodecSrc::Mode mode,
+        CodecSrc::DstColorType dstColorType, int sampleSize) {
+    SkString folder;
+    switch (mode) {
+        case AndroidCodecSrc::kFullImage_Mode:
+            folder.append("scaled_codec");
+            break;
+        case AndroidCodecSrc::kDivisor_Mode:
+            folder.append("scaled_codec_divisor");
+            break;
+    }
+
+    switch (dstColorType) {
+        case CodecSrc::kGrayscale_Always_DstColorType:
+            folder.append("_kGray8");
+            break;
+        case CodecSrc::kIndex8_Always_DstColorType:
+            folder.append("_kIndex8");
+            break;
+        default:
+            break;
+    }
+
+    if (1 != sampleSize) {
+        folder.appendf("_%.3f", get_scale_from_sample_size(sampleSize));
+    }
+
+    AndroidCodecSrc* src = new AndroidCodecSrc(path, mode, dstColorType, sampleSize);
+    push_src("image", folder, src);
+}
+
 static void push_codec_srcs(Path path) {
     SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(path.c_str()));
     if (!encoded) {
@@ -288,7 +316,9 @@
     switch (codec->getInfo().colorType()) {
         case kGray_8_SkColorType:
             // FIXME: Is this a long term solution for testing wbmps decodes to kIndex8?
-            // Further discussion on this topic is at skbug.com/3683
+            // Further discussion on this topic is at skbug.com/3683.
+            // This causes us to try to convert grayscale jpegs to kIndex8.  We currently
+            // fail non-fatally in this case.
             colorTypes[0] = CodecSrc::kGetFromCanvas_DstColorType;
             colorTypes[1] = CodecSrc::kGrayscale_Always_DstColorType;
             colorTypes[2] = CodecSrc::kIndex8_Always_DstColorType;
@@ -313,24 +343,34 @@
         }
     }
 
-    if (path.endsWith(".ico") || path.endsWith(".ICO")) {
-        // FIXME: skbug.com/4404: ICO does not have the ability to decode scanlines, so we cannot
-        // use SkScaledCodec with it.
+    // skbug.com/4428
+    static const char* const exts[] = {
+        "jpg", "jpeg", "png", "webp",
+        "JPG", "JPEG", "PNG", "WEBP",
+    };
+    bool supported = false;
+    for (const char* ext : exts) {
+        if (path.endsWith(ext)) {
+            supported = true;
+            break;
+        }
+    }
+    if (!supported) {
         return;
     }
 
-    // SkScaledCodec Scales
-    // The native scales are included to make sure that SkScaledCodec defaults to the native
-    // scaling strategy when possible.
-    // 0.1, 0.16, 0.2 etc allow us to test SkScaledCodec with sampleSize 10, 6, 5, etc.
-    // 0.4, 0.7 etc allow to test what happens when the client requests a scale that
-    // does not exactly match a sampleSize or native scaling capability.
-    const float samplingScales[] = { 0.1f, 0.125f, 0.167f, 0.2f, 0.25f, 0.333f, 0.375f, 0.4f, 0.5f,
-            0.6f, 0.625f, 0.750f, 0.8f, 0.875f, 1.0f };
+    const int sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
 
-    for (float scale : samplingScales) {
-        for (uint32_t i = 0; i < numColorTypes; i++) {
-            push_codec_src(path, CodecSrc::kScaledCodec_Mode, colorTypes[i], scale);
+    const AndroidCodecSrc::Mode androidModes[] = {
+            AndroidCodecSrc::kFullImage_Mode,
+            AndroidCodecSrc::kDivisor_Mode,
+    };
+
+    for (int sampleSize : sampleSizes) {
+        for (AndroidCodecSrc::Mode mode : androidModes) {
+            for (uint32_t i = 0; i < numColorTypes; i++) {
+                push_android_codec_src(path, mode, colorTypes[i], sampleSize);
+            }
         }
     }
 }
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 394b84f..24ae2d6 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -7,6 +7,7 @@
 
 #include "DMSrcSink.h"
 #include "SamplePipeControllers.h"
+#include "SkAndroidCodec.h"
 #include "SkCodec.h"
 #include "SkCodecTools.h"
 #include "SkCommonFlags.h"
@@ -25,11 +26,9 @@
 #include "SkRecorder.h"
 #include "SkRemote.h"
 #include "SkSVGCanvas.h"
-#include "SkScaledCodec.h"
 #include "SkStream.h"
 #include "SkTLogic.h"
 #include "SkXMLWriter.h"
-#include "SkScaledCodec.h"
 #include "SkSwizzler.h"
 
 DEFINE_bool(multiPage, false, "For document-type backends, render the source"
@@ -244,46 +243,47 @@
         || flags.approach != SinkFlags::kDirect;
 }
 
+bool get_decode_info(SkImageInfo* decodeInfo, const SkImageInfo& defaultInfo,
+        SkColorType canvasColorType, CodecSrc::DstColorType dstColorType) {
+    switch (dstColorType) {
+        case CodecSrc::kIndex8_Always_DstColorType:
+            if (kRGB_565_SkColorType == canvasColorType) {
+                return false;
+            }
+            *decodeInfo = defaultInfo.makeColorType(kIndex_8_SkColorType);
+            break;
+        case CodecSrc::kGrayscale_Always_DstColorType:
+            if (kRGB_565_SkColorType == canvasColorType) {
+                return false;
+            }
+            *decodeInfo = defaultInfo.makeColorType(kGray_8_SkColorType);
+            break;
+        default:
+            *decodeInfo = defaultInfo.makeColorType(canvasColorType);
+            break;
+    }
+
+    // FIXME: Currently we cannot draw unpremultiplied sources.
+    if (decodeInfo->alphaType() == kUnpremul_SkAlphaType) {
+        decodeInfo->makeAlphaType(kPremul_SkAlphaType);
+    }
+    return true;
+}
+
 Error CodecSrc::draw(SkCanvas* canvas) const {
     SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str()));
     if (!encoded) {
         return SkStringPrintf("Couldn't read %s.", fPath.c_str());
     }
-    SkAutoTDelete<SkCodec> codec(NULL);
-    if (kScaledCodec_Mode == fMode) {
-        codec.reset(SkScaledCodec::NewFromData(encoded));
-        // TODO (msarett): This should fall through to a fatal error once we support scaled
-        //                 codecs for all image types.
-        if (nullptr == codec.get()) {
-            return Error::Nonfatal(SkStringPrintf("Couldn't create scaled codec for %s.",
-                    fPath.c_str()));
-        }
-    } else {
-        codec.reset(SkCodec::NewFromData(encoded));
-    }
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(encoded));
     if (nullptr == codec.get()) {
         return SkStringPrintf("Couldn't create codec for %s.", fPath.c_str());
     }
 
-    // Choose the color type to decode to
-    SkImageInfo decodeInfo = codec->getInfo();
-    SkColorType canvasColorType = canvas->imageInfo().colorType();
-    switch (fDstColorType) {
-        case kIndex8_Always_DstColorType:
-            decodeInfo = codec->getInfo().makeColorType(kIndex_8_SkColorType);
-            if (kRGB_565_SkColorType == canvasColorType) {
-                return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
-            }
-            break;
-        case kGrayscale_Always_DstColorType:
-            decodeInfo = codec->getInfo().makeColorType(kGray_8_SkColorType);
-            if (kRGB_565_SkColorType == canvasColorType) {
-                return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
-            }
-            break;
-        default:
-            decodeInfo = decodeInfo.makeColorType(canvasColorType);
-            break;
+    SkImageInfo decodeInfo;
+    if (!get_decode_info(&decodeInfo, codec->getInfo(), canvas->imageInfo().colorType(),
+            fDstColorType)) {
+        return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
     }
 
     // Try to scale the image if it is desired
@@ -311,11 +311,6 @@
         colorCountPtr = &maxColors;
     }
 
-    // FIXME: Currently we cannot draw unpremultiplied sources.
-    if (decodeInfo.alphaType() == kUnpremul_SkAlphaType) {
-        decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
-    }
-
     SkBitmap bitmap;
     if (!bitmap.tryAllocPixels(decodeInfo, nullptr, colorTable.get())) {
         return SkStringPrintf("Image(%s) is too large (%d x %d)\n", fPath.c_str(),
@@ -323,7 +318,6 @@
     }
 
     switch (fMode) {
-        case kScaledCodec_Mode:
         case kCodec_Mode: {
             switch (codec->getPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), nullptr,
                     colorPtr, colorCountPtr)) {
@@ -585,14 +579,7 @@
 
 SkISize CodecSrc::size() const {
     SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str()));
-    SkAutoTDelete<SkCodec> codec(nullptr);
-
-    if (kScaledCodec_Mode == fMode) {
-        codec.reset(SkScaledCodec::NewFromData(encoded));
-    } else {
-        codec.reset(SkCodec::NewFromData(encoded));
-    }
-
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(encoded));
     if (nullptr == codec) {
         return SkISize::Make(0, 0);
     }
@@ -608,6 +595,163 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
+AndroidCodecSrc::AndroidCodecSrc(Path path, Mode mode, CodecSrc::DstColorType dstColorType,
+        int sampleSize)
+    : fPath(path)
+    , fMode(mode)
+    , fDstColorType(dstColorType)
+    , fSampleSize(sampleSize)
+{}
+
+bool AndroidCodecSrc::veto(SinkFlags flags) const {
+    // No need to test decoding to non-raster or indirect backend.
+    // TODO: Once we implement GPU paths (e.g. JPEG YUV), we should use a deferred decode to
+    // let the GPU handle it.
+    return flags.type != SinkFlags::kRaster
+        || flags.approach != SinkFlags::kDirect;
+}
+
+Error AndroidCodecSrc::draw(SkCanvas* canvas) const {
+    SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str()));
+    if (!encoded) {
+        return SkStringPrintf("Couldn't read %s.", fPath.c_str());
+    }
+    SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromData(encoded));
+    if (nullptr == codec.get()) {
+        return SkStringPrintf("Couldn't create android codec for %s.", fPath.c_str());
+    }
+
+    SkImageInfo decodeInfo;
+    if (!get_decode_info(&decodeInfo, codec->getInfo(), canvas->imageInfo().colorType(),
+            fDstColorType)) {
+        return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
+    }
+
+    // Scale the image if it is desired.
+    SkISize size = codec->getSampledDimensions(fSampleSize);
+
+    // Visually inspecting very small output images is not necessary.  We will
+    // cover these cases in unit testing.
+    if ((size.width() <= 10 || size.height() <= 10) && 1 != fSampleSize) {
+        return Error::Nonfatal("Scaling very small images is uninteresting.");
+    }
+    decodeInfo = decodeInfo.makeWH(size.width(), size.height());
+
+    // Construct a color table for the decode if necessary
+    SkAutoTUnref<SkColorTable> colorTable(nullptr);
+    SkPMColor* colorPtr = nullptr;
+    int* colorCountPtr = nullptr;
+    int maxColors = 256;
+    if (kIndex_8_SkColorType == decodeInfo.colorType()) {
+        SkPMColor colors[256];
+        colorTable.reset(new SkColorTable(colors, maxColors));
+        colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
+        colorCountPtr = &maxColors;
+    }
+
+    SkBitmap bitmap;
+    if (!bitmap.tryAllocPixels(decodeInfo, nullptr, colorTable.get())) {
+        return SkStringPrintf("Image(%s) is too large (%d x %d)\n", fPath.c_str(),
+                              decodeInfo.width(), decodeInfo.height());
+    }
+
+    // Create options for the codec.
+    SkAndroidCodec::AndroidOptions options;
+    options.fColorPtr = colorPtr;
+    options.fColorCount = colorCountPtr;
+    options.fSampleSize = fSampleSize;
+
+    switch (fMode) {
+        case kFullImage_Mode: {
+            switch (codec->getAndroidPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(),
+                    &options)) {
+                case SkCodec::kSuccess:
+                case SkCodec::kIncompleteInput:
+                    break;
+                case SkCodec::kInvalidConversion:
+                    return Error::Nonfatal("Cannot convert to requested color type.\n");
+                default:
+                    return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str());
+            }
+            canvas->drawBitmap(bitmap, 0, 0);
+            return "";
+        }
+        case kDivisor_Mode: {
+            const int width = codec->getInfo().width();
+            const int height = codec->getInfo().height();
+            const int divisor = 2;
+            if (width < divisor || height < divisor) {
+                return Error::Nonfatal("Divisor is larger than image dimension.\n");
+            }
+
+            // Rounding the size of the subsets may leave some pixels uninitialized on the bottom
+            // and right edges of the bitmap.
+            bitmap.eraseColor(0);
+            for (int x = 0; x < divisor; x++) {
+                for (int y = 0; y < divisor; y++) {
+                    // Calculate the subset dimensions
+                    int subsetWidth = width / divisor;
+                    int subsetHeight = height / divisor;
+                    const int left = x * subsetWidth;
+                    const int top = y * subsetHeight;
+
+                    // Increase the size of the last subset in each row or column, when the
+                    // divisor does not divide evenly into the image dimensions
+                    subsetWidth += (x + 1 == divisor) ? (width % divisor) : 0;
+                    subsetHeight += (y + 1 == divisor) ? (height % divisor) : 0;
+                    SkIRect subset = SkIRect::MakeXYWH(left, top, subsetWidth, subsetHeight);
+                    if (!codec->getSupportedSubset(&subset)) {
+                        return "Could not get supported subset to decode.\n";
+                    }
+                    options.fSubset = &subset;
+                    void* pixels = bitmap.getAddr(subset.left() / fSampleSize,
+                            subset.top() / fSampleSize);
+                    SkISize scaledSubsetSize = codec->getSampledSubsetDimensions(fSampleSize,
+                            subset);
+                    SkImageInfo subsetDecodeInfo = decodeInfo.makeWH(scaledSubsetSize.width(),
+                            scaledSubsetSize.height());
+
+                    switch (codec->getAndroidPixels(subsetDecodeInfo, pixels, bitmap.rowBytes(),
+                            &options)) {
+                        case SkCodec::kSuccess:
+                        case SkCodec::kIncompleteInput:
+                            break;
+                        case SkCodec::kInvalidConversion:
+                            return Error::Nonfatal("Cannot convert to requested color type.\n");
+                        default:
+                            return SkStringPrintf("Couldn't getPixels %s.", fPath.c_str());
+                    }
+                }
+            }
+            canvas->drawBitmap(bitmap, 0, 0);
+            return "";
+        }
+        default:
+            SkASSERT(false);
+            return "Error: Should not be reached.\n";
+    }
+}
+
+SkISize AndroidCodecSrc::size() const {
+    SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str()));
+    SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromData(encoded));
+    if (nullptr == codec) {
+        return SkISize::Make(0, 0);
+    }
+    return codec->getSampledDimensions(fSampleSize);
+}
+
+Name AndroidCodecSrc::name() const {
+    // We will replicate the names used by CodecSrc so that images can
+    // be compared in Gold.
+    if (1 == fSampleSize) {
+        return SkOSPath::Basename(fPath.c_str());
+    }
+    return get_scaled_name(fPath, get_scale_from_sample_size(fSampleSize));
+}
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
 ImageSrc::ImageSrc(Path path, int divisor) : fPath(path), fDivisor(divisor) {}
 
 bool ImageSrc::veto(SinkFlags flags) const {
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index 1beaec9..a7cca83 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -104,7 +104,6 @@
 class CodecSrc : public Src {
 public:
     enum Mode {
-        kScaledCodec_Mode,
         kCodec_Mode,
         kScanline_Mode,
         kScanline_Subset_Mode,
@@ -129,6 +128,28 @@
     float                   fScale;
 };
 
+class AndroidCodecSrc : public Src {
+public:
+    enum Mode {
+        kFullImage_Mode,
+        // Splits the image into multiple subsets using a divisor and decodes the subsets
+        // separately.
+        kDivisor_Mode,
+    };
+
+    AndroidCodecSrc(Path, Mode, CodecSrc::DstColorType, int sampleSize);
+
+    Error draw(SkCanvas*) const override;
+    SkISize size() const override;
+    Name name() const override;
+    bool veto(SinkFlags) const override;
+private:
+    Path                    fPath;
+    Mode                    fMode;
+    CodecSrc::DstColorType  fDstColorType;
+    int                     fSampleSize;
+};
+
 // Allows for testing of various implementations of Android's BitmapRegionDecoder
 class BRDSrc : public Src {
 public:
diff --git a/gyp/codec.gyp b/gyp/codec.gyp
index 1f3287a..350410a 100644
--- a/gyp/codec.gyp
+++ b/gyp/codec.gyp
@@ -32,6 +32,7 @@
         '../src/core',
       ],
       'sources': [
+        '../src/codec/SkAndroidCodec.cpp',
         '../src/codec/SkBmpCodec.cpp',
         '../src/codec/SkBmpMaskCodec.cpp',
         '../src/codec/SkBmpRLECodec.cpp',
@@ -49,6 +50,7 @@
         '../src/codec/SkSampler.cpp',
         '../src/codec/SkScaledCodec.cpp',
         '../src/codec/SkSwizzler.cpp',
+        '../src/codec/SkWebpAdapterCodec.cpp',
         '../src/codec/SkWebpCodec.cpp',
       ],
       'direct_dependent_settings': {
diff --git a/include/codec/SkAndroidCodec.h b/include/codec/SkAndroidCodec.h
new file mode 100644
index 0000000..42e7fa8
--- /dev/null
+++ b/include/codec/SkAndroidCodec.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkAndroidCodec_DEFINED
+#define SkAndroidCodec_DEFINED
+
+#include "SkCodec.h"
+#include "SkEncodedFormat.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+
+/**
+ *  Abstract interface defining image codec functionality that is necessary for
+ *  Android.
+ */
+class SkAndroidCodec : SkNoncopyable {
+public:
+    /**
+     *  If this stream represents an encoded image that we know how to decode,
+     *  return an SkAndroidCodec that can decode it. Otherwise return NULL.
+     *
+     *  If NULL is returned, the stream is deleted immediately. Otherwise, the
+     *  SkCodec takes ownership of it, and will delete it when done with it.
+     */
+    static SkAndroidCodec* NewFromStream(SkStream*);
+
+    /**
+     *  If this data represents an encoded image that we know how to decode,
+     *  return an SkAndroidCodec that can decode it. Otherwise return NULL.
+     *
+     *  Will take a ref if it returns a codec, else will not affect the data.
+     */
+    static SkAndroidCodec* NewFromData(SkData*);
+
+    virtual ~SkAndroidCodec() {}
+
+
+    const SkImageInfo& getInfo() const { return fInfo; }
+
+    /**
+     *  Format of the encoded data.
+     */
+    SkEncodedFormat getEncodedFormat() const { return this->onGetEncodedFormat(); }
+
+    /**
+     *  Returns the dimensions of the scaled output image, for an input
+     *  sampleSize.
+     *
+     *  When the sample size divides evenly into the original dimensions, the
+     *  scaled output dimensions will simply be equal to the original
+     *  dimensions divided by the sample size.
+     *
+     *  When the sample size does not divide even into the original
+     *  dimensions, the codec may round up or down, depending on what is most
+     *  efficient to decode.
+     *
+     *  Finally, the codec will always recommend a non-zero output, so the output
+     *  dimension will always be one if the sampleSize is greater than the
+     *  original dimension.
+     */
+    SkISize getSampledDimensions(int sampleSize) const;
+
+    /**
+     *  Return (via desiredSubset) a subset which can decoded from this codec,
+     *  or false if the input subset is invalid.
+     *
+     *  @param desiredSubset in/out parameter
+     *                       As input, a desired subset of the original bounds
+     *                       (as specified by getInfo).
+     *                       As output, if true is returned, desiredSubset may
+     *                       have been modified to a subset which is
+     *                       supported. Although a particular change may have
+     *                       been made to desiredSubset to create something
+     *                       supported, it is possible other changes could
+     *                       result in a valid subset.  If false is returned,
+     *                       desiredSubset's value is undefined.
+     *  @return true         If the input desiredSubset is valid.
+     *                       desiredSubset may be modified to a subset
+     *                       supported by the codec.
+     *          false        If desiredSubset is invalid (NULL or not fully
+     *                       contained within the image).
+     */
+    bool getSupportedSubset(SkIRect* desiredSubset) const;
+    // TODO: Rename SkCodec::getValidSubset() to getSupportedSubset()
+
+    /**
+     *  Returns the dimensions of the scaled, partial output image, for an
+     *  input sampleSize and subset.
+     *
+     *  @param sampleSize Factor to scale down by.
+     *  @param subset     Must be a valid subset of the original image
+     *                    dimensions and a subset supported by SkAndroidCodec.
+     *                    getSubset() can be used to obtain a subset supported
+     *                    by SkAndroidCodec.
+     *  @return           Size of the scaled partial image.  Or zero size
+     *                    if either of the inputs is invalid.
+     */
+    SkISize getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const;
+
+    /**
+     *  Additional options to pass to getAndroidPixels().
+     */
+    // FIXME: It's a bit redundant to name these AndroidOptions when this class is already
+    //        called SkAndroidCodec.  On the other hand, it's may be a bit confusing to call
+    //        these Options when SkCodec has a slightly different set of Options.  Maybe these
+    //        should be DecodeOptions or SamplingOptions?
+    struct AndroidOptions {
+        AndroidOptions()
+            : fZeroInitialized(SkCodec::kNo_ZeroInitialized)
+            , fSubset(nullptr)
+            , fColorPtr(nullptr)
+            , fColorCount(nullptr)
+            , fSampleSize(1)
+        {}
+
+        /**
+         *  Indicates is destination pixel memory is zero initialized.
+         */
+        SkCodec::ZeroInitialized fZeroInitialized;
+
+        /**
+         *  If not NULL, represents a subset of the original image to decode.
+         *
+         *  Must be within the bounds returned by getInfo().
+         *
+         *  If the EncodedFormat is kWEBP_SkEncodedFormat, the top and left
+         *  values must be even.
+         */
+        SkIRect* fSubset;
+
+        /**
+         *  If the client has requested a decode to kIndex8_SkColorType
+         *  (specified in the SkImageInfo), then the caller must provide
+         *  storage for up to 256 SkPMColor values in fColorPtr.  On success,
+         *  the codec must copy N colors into that storage, (where N is the
+         *  logical number of table entries) and set fColorCount to N.
+         *
+         *  If the client does not request kIndex8_SkColorType, then the last
+         *  two parameters may be NULL. If fColorCount is not null, it will be
+         *  set to 0.
+         */
+        SkPMColor* fColorPtr;
+        int*       fColorCount;
+
+        /**
+         *  The client may provide an integer downscale factor for the decode.
+         *  The codec may implement this downscaling by sampling or another
+         *  method if it is more efficient.
+         */
+        int fSampleSize;
+    };
+
+    /**
+     *  Decode into the given pixels, a block of memory of size at
+     *  least (info.fHeight - 1) * rowBytes + (info.fWidth *
+     *  bytesPerPixel)
+     *
+     *  Repeated calls to this function should give the same results,
+     *  allowing the PixelRef to be immutable.
+     *
+     *  @param info A description of the format (config, size)
+     *         expected by the caller.  This can simply be identical
+     *         to the info returned by getInfo().
+     *
+     *         This contract also allows the caller to specify
+     *         different output-configs, which the implementation can
+     *         decide to support or not.
+     *
+     *         A size that does not match getInfo() implies a request
+     *         to scale or subset. If the codec cannot perform this
+     *         scaling or subsetting, it will return an error code.
+     *
+     *  If info is kIndex8_SkColorType, then the caller must provide storage for up to 256
+     *  SkPMColor values in options->fColorPtr. On success the codec must copy N colors into
+     *  that storage, (where N is the logical number of table entries) and set
+     *  options->fColorCount to N.
+     *
+     *  If info is not kIndex8_SkColorType, options->fColorPtr and options->fColorCount may
+     *  be nullptr.
+     *
+     *  The AndroidOptions object is also used to specify any requested scaling or subsetting
+     *  using options->fSampleSize and options->fSubset.
+     *
+     *  @return Result kSuccess, or another value explaining the type of failure.
+     */
+    // FIXME: It's a bit redundant to name this getAndroidPixels() when this class is already
+    //        called SkAndroidCodec.  On the other hand, it's may be a bit confusing to call
+    //        this getPixels() when it is a slightly different API than SkCodec's getPixels().
+    //        Maybe this should be decode() or decodeSubset()?
+    SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
+            AndroidOptions* options);
+
+    /**
+     *  Simplified version of getAndroidPixels() where we supply the default AndroidOptions.
+     *
+     *  This will return an error if the info is kIndex_8_SkColorType and also will not perform
+     *  any scaling or subsetting.
+     */
+    SkCodec::Result getAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes);
+
+protected:
+
+    SkAndroidCodec(const SkImageInfo&);
+
+    virtual SkEncodedFormat onGetEncodedFormat() const = 0;
+
+    virtual SkISize onGetSampledDimensions(int sampleSize) const = 0;
+
+    virtual bool onGetSupportedSubset(SkIRect* desiredSubset) const = 0;
+
+    virtual SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels,
+            size_t rowBytes, AndroidOptions& options) = 0;
+
+private:
+
+    // This will always be a reference to the info that is contained by the
+    // embedded SkCodec.
+    const SkImageInfo& fInfo;
+};
+#endif // SkAndroidCodec_DEFINED
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index 8b6e210..5127a7c 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -584,6 +584,6 @@
      */
     virtual SkSampler* getSampler(bool createIfNecessary) { return nullptr; }
 
-    friend class SkScaledCodec;
+    friend class SkSampledCodec;
 };
 #endif // SkCodec_DEFINED
diff --git a/include/codec/SkScaledCodec.h b/include/codec/SkScaledCodec.h
deleted file mode 100644
index 028706b..0000000
--- a/include/codec/SkScaledCodec.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#ifndef SkScaledCodec_DEFINED
-#define SkScaledCodec_DEFINED
-
-#include "SkCodec.h"
-
-class SkStream;
-
-/**
- * This class implements scaling, by sampling scanlines in the y direction.
- * x-wise sampling is implemented in the swizzler, when getScanlines() is called.
- */
-class SkScaledCodec : public SkCodec {
-public:
-    static SkCodec* NewFromStream(SkStream*);
-    static SkCodec* NewFromData(SkData*);
-
-    virtual ~SkScaledCodec();
-
-    static void ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim,
-                                  int* sampleSizeX, int* sampleSizeY);
-
-protected:
-    bool onRewind() override;
-
-    /**
-     * Recommend a set of destination dimensions given a requested scale
-     */
-    SkISize onGetScaledDimensions(float desiredScale) const override;
-    bool onDimensionsSupported(const SkISize&) override;
-
-    Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*)
-            override;
-    SkEncodedFormat onGetEncodedFormat() const override {
-        return fCodec->getEncodedFormat();
-    }
-
-    bool onReallyHasAlpha() const override {
-        return fCodec->reallyHasAlpha();
-    }
-
-    uint32_t onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const override;
-
-    SkScanlineOrder onGetScanlineOrder() const override;
-
-private:
-
-    SkAutoTDelete<SkCodec> fCodec;
-
-    explicit SkScaledCodec(SkCodec*);
-
-    typedef SkCodec INHERITED;
-};
-#endif // SkScaledCodec_DEFINED
diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp
new file mode 100644
index 0000000..43ed90f
--- /dev/null
+++ b/src/codec/SkAndroidCodec.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAndroidCodec.h"
+#include "SkCodec.h"
+#include "SkCodecPriv.h"
+#include "SkSampledCodec.h"
+#include "SkWebpAdapterCodec.h"
+
+static bool is_valid_sample_size(int sampleSize) {
+    // FIXME: As Leon has mentioned elsewhere, surely there is also a maximum sampleSize?
+    return sampleSize > 0;
+}
+
+SkAndroidCodec::SkAndroidCodec(const SkImageInfo& info)
+    : fInfo(info)
+{}
+
+SkAndroidCodec* SkAndroidCodec::NewFromStream(SkStream* stream) {
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
+    if (nullptr == codec) {
+        return nullptr;
+    }
+
+    switch (codec->getEncodedFormat()) {
+        case kWEBP_SkEncodedFormat:
+            return new SkWebpAdapterCodec((SkWebpCodec*) codec.detach());
+        case kPNG_SkEncodedFormat:
+        case kJPEG_SkEncodedFormat:
+            return new SkSampledCodec(codec.detach());
+        default:
+            // FIXME: SkSampledCodec is temporarily disabled for other formats
+            // while focusing on the formats that are supported by
+            // BitmapRegionDecoder.
+            return nullptr;
+    }
+}
+
+SkAndroidCodec* SkAndroidCodec::NewFromData(SkData* data) {
+    if (!data) {
+        return nullptr;
+    }
+
+    return NewFromStream(new SkMemoryStream(data));
+}
+
+SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const {
+    if (!is_valid_sample_size(sampleSize)) {
+        return SkISize::Make(0, 0);
+    }
+
+    return this->onGetSampledDimensions(sampleSize);
+}
+
+bool SkAndroidCodec::getSupportedSubset(SkIRect* desiredSubset) const {
+    if (!desiredSubset || !is_valid_subset(*desiredSubset, fInfo.dimensions())) {
+        return false;
+    }
+
+    return this->onGetSupportedSubset(desiredSubset);
+}
+
+SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const {
+    if (!is_valid_sample_size(sampleSize)) {
+        return SkISize::Make(0, 0);
+    }
+
+    // We require that the input subset is a subset that is supported by SkAndroidCodec.
+    // We test this by calling getSupportedSubset() and verifying that no modifications
+    // are made to the subset.
+    SkIRect copySubset = subset;
+    if (!this->getSupportedSubset(&copySubset) || copySubset != subset) {
+        return SkISize::Make(0, 0);
+    }
+
+    // If the subset is the entire image, for consistency, use onGetSampledDimensions().
+    if (fInfo.dimensions() == subset.size()) {
+        return onGetSampledDimensions(sampleSize);
+    }
+
+    // This should perhaps call a virtual function, but currently both of our subclasses
+    // want the same implementation.
+    return SkISize::Make(get_scaled_dimension(subset.width(), sampleSize),
+                get_scaled_dimension(subset.height(), sampleSize));
+}
+
+SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels,
+        size_t rowBytes, AndroidOptions* options) {
+    if (!pixels) {
+        return SkCodec::kInvalidParameters;
+    }
+    if (rowBytes < info.minRowBytes()) {
+        return SkCodec::kInvalidParameters;
+    }
+
+    AndroidOptions defaultOptions;
+    if (!options) {
+        options = &defaultOptions;
+    } else if (options->fSubset) {
+        if (!is_valid_subset(*options->fSubset, fInfo.dimensions())) {
+            return SkCodec::kInvalidParameters;
+        }
+    }
+
+    return this->onGetAndroidPixels(info, pixels, rowBytes, *options);
+}
+
+SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels,
+        size_t rowBytes) {
+    return this->getAndroidPixels(info, pixels, rowBytes, nullptr);
+}
diff --git a/src/codec/SkBmpCodec.cpp b/src/codec/SkBmpCodec.cpp
index 0222c8c..191c2ad 100644
--- a/src/codec/SkBmpCodec.cpp
+++ b/src/codec/SkBmpCodec.cpp
@@ -11,7 +11,6 @@
 #include "SkBmpStandardCodec.h"
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
-#include "SkScaledCodec.h"
 #include "SkStream.h"
 
 /*
diff --git a/src/codec/SkBmpRLECodec.cpp b/src/codec/SkBmpRLECodec.cpp
index 67e262a..32580c3 100644
--- a/src/codec/SkBmpRLECodec.cpp
+++ b/src/codec/SkBmpRLECodec.cpp
@@ -8,7 +8,6 @@
 #include "SkBmpRLECodec.h"
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
-#include "SkScaledCodec.h"
 #include "SkStream.h"
 
 /*
diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h
index 0442625..1b6723f 100644
--- a/src/codec/SkCodecPriv.h
+++ b/src/codec/SkCodecPriv.h
@@ -31,9 +31,19 @@
 #define COMPUTE_RESULT_ALPHA                    \
     SkSwizzler::GetResult(zeroAlpha, maxAlpha);
 
+// FIXME: Consider sharing with dm, nanbench, and tools.
+inline float get_scale_from_sample_size(int sampleSize) {
+    return 1.0f / ((float) sampleSize);
+}
+
+inline bool is_valid_subset(const SkIRect& subset, const SkISize& imageDims) {
+    return SkIRect::MakeSize(imageDims).contains(subset);
+}
+
 /*
  * returns a scaled dimension based on the original dimension and the sampleSize
  * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder
+ * FIXME: I think we should call this get_sampled_dimension().
  */
 inline int get_scaled_dimension(int srcDimension, int sampleSize) {
     if (sampleSize > srcDimension) {
diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp
index f4d7556..54b5787 100644
--- a/src/codec/SkCodec_libpng.cpp
+++ b/src/codec/SkCodec_libpng.cpp
@@ -11,7 +11,6 @@
 #include "SkColorTable.h"
 #include "SkBitmap.h"
 #include "SkMath.h"
-#include "SkScaledCodec.h"
 #include "SkSize.h"
 #include "SkStream.h"
 #include "SkSwizzler.h"
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index cca71c1..9975748 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -11,7 +11,6 @@
 #include "SkJpegUtility_codec.h"
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
-#include "SkScaledCodec.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkTypes.h"
diff --git a/src/codec/SkMaskSwizzler.cpp b/src/codec/SkMaskSwizzler.cpp
index 958df61..e5facc1 100644
--- a/src/codec/SkMaskSwizzler.cpp
+++ b/src/codec/SkMaskSwizzler.cpp
@@ -8,7 +8,6 @@
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
 #include "SkMaskSwizzler.h"
-#include "SkScaledCodec.h"
 
 static SkSwizzler::ResultAlpha swizzle_mask16_to_n32_opaque(
         void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks,
diff --git a/src/codec/SkSampledCodec.h b/src/codec/SkSampledCodec.h
new file mode 100644
index 0000000..277a50f
--- /dev/null
+++ b/src/codec/SkSampledCodec.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkSampledCodec_DEFINED
+#define SkSampledCodec_DEFINED
+
+#include "SkAndroidCodec.h"
+#include "SkCodec.h"
+
+/**
+ *  This class implements the functionality of SkAndroidCodec.  Scaling will
+ *  be provided by sampling if it cannot be provided by fCodec.
+ */
+class SkSampledCodec : public SkAndroidCodec {
+public:
+
+    explicit SkSampledCodec(SkCodec*);
+
+    virtual ~SkSampledCodec() {}
+
+protected:
+
+    SkEncodedFormat onGetEncodedFormat() const override { return fCodec->getEncodedFormat(); };
+
+    SkISize onGetSampledDimensions(int sampleSize) const override;
+
+    bool onGetSupportedSubset(SkIRect* desiredSubset) const override { return true; }
+
+    SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
+            AndroidOptions& options) override;
+
+private:
+
+    /**
+     *  This fulfills the same contract as onGetAndroidPixels().
+     *
+     *  We call this function from onGetAndroidPixels() if we have determined
+     *  that fCodec does not support the requested scale, and we need to
+     *  provide the scale by sampling.
+     */
+    SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes,
+            AndroidOptions& options);
+
+    SkAutoTDelete<SkCodec> fCodec;
+
+    typedef SkAndroidCodec INHERITED;
+};
+#endif // SkSampledCodec_DEFINED
diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp
index 6b50a09..c2be2d6 100644
--- a/src/codec/SkScaledCodec.cpp
+++ b/src/codec/SkScaledCodec.cpp
@@ -5,344 +5,210 @@
  * found in the LICENSE file.
  */
 
+#include "SkCodec.h"
 #include "SkCodecPriv.h"
-#include "SkScaledCodec.h"
-#include "SkStream.h"
-#include "SkWebpCodec.h"
+#include "SkSampledCodec.h"
 
+// FIXME: Rename this file to SkSampledCodec.cpp
 
-SkCodec* SkScaledCodec::NewFromStream(SkStream* stream) {
-    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
-    if (nullptr == codec) {
-        return nullptr;
-    }
-
-    switch (codec->getEncodedFormat()) {
-        case kWEBP_SkEncodedFormat:
-            // Webp codec supports scaling and subsetting natively
-            return codec.detach();
-        case kPNG_SkEncodedFormat:
-        case kJPEG_SkEncodedFormat:
-            // wrap in new SkScaledCodec
-            return new SkScaledCodec(codec.detach());
-        default:
-            // FIXME: SkScaledCodec is temporarily disabled for other formats
-            // while focusing on the formats that are supported by
-            // BitmapRegionDecoder.
-            return nullptr;
-    }
-}
-
-SkCodec* SkScaledCodec::NewFromData(SkData* data) {
-    if (!data) {
-        return nullptr;
-    }
-    return NewFromStream(new SkMemoryStream(data));
-}
-
-SkScaledCodec::SkScaledCodec(SkCodec* codec)
-    : INHERITED(codec->getInfo(), nullptr)
+SkSampledCodec::SkSampledCodec(SkCodec* codec)
+    : INHERITED(codec->getInfo())
     , fCodec(codec)
 {}
 
-SkScaledCodec::~SkScaledCodec() {}
-
-bool SkScaledCodec::onRewind() {
-    return fCodec->onRewind();
-}
-
-static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& nativeDims,
-                                      const SkISize& scaledCodecDims, float desiredScale) {
-    if (nativeDims == scaledCodecDims) {
-        // does not matter which to return if equal. Return here to skip below calculations
-        return nativeDims;
+SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const {
+    // Fast path for when we are not scaling.
+    if (1 == sampleSize) {
+        return fCodec->getInfo().dimensions();
     }
-    float idealWidth = origDims.width() * desiredScale;
-    float idealHeight = origDims.height() * desiredScale;
 
-    // calculate difference between native dimensions and ideal dimensions
-    float nativeWDiff = SkTAbs(idealWidth - nativeDims.width());
-    float nativeHDiff = SkTAbs(idealHeight - nativeDims.height());
-    float nativeDiff = nativeWDiff + nativeHDiff;
+    const int width = fCodec->getInfo().width();
+    const int height = fCodec->getInfo().height();
 
+    // Check if the codec can provide the scaling natively.
+    float scale = get_scale_from_sample_size(sampleSize);
+    SkSize idealSize = SkSize::Make(scale * (float) width, scale * (float) height);
+    SkISize nativeSize = fCodec->getScaledDimensions(scale);
+    float widthDiff = SkTAbs(((float) nativeSize.width()) - idealSize.width());
+    float heightDiff = SkTAbs(((float) nativeSize.height()) - idealSize.height());
     // Native scaling is preferred to sampling.  If we can scale natively to
     // within one of the ideal value, we should choose to scale natively.
-    if (nativeWDiff < 1.0f && nativeHDiff < 1.0f) {
-        return nativeDims;
+    if (widthDiff < 1.0f && heightDiff < 1.0f) {
+        return nativeSize;
     }
 
-    // calculate difference between scaledCodec dimensions and ideal dimensions
-    float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width());
-    float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height());
-    float scaledCodecDiff = scaledCodecWDiff + scaledCodecHDiff;
-
-    // return dimensions closest to ideal dimensions.
-    // If the differences are equal, return nativeDims, as native scaling is more efficient.
-    return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims;
+    // Provide the scaling by sampling.
+    return SkISize::Make(get_scaled_dimension(width, sampleSize),
+            get_scaled_dimension(height, sampleSize));
 }
 
-/*
- * Return a valid set of output dimensions for this decoder, given an input scale
- */
-SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const {
-    SkISize nativeDimensions = fCodec->getScaledDimensions(desiredScale);
-    // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ...
-    SkISize scaledCodecDimensions;
-    if (desiredScale > 0.5f) {
-        // sampleSize = 1
-        scaledCodecDimensions = fCodec->getInfo().dimensions();
-    }
-    // sampleSize determines the step size between samples
-    // Ex: sampleSize = 2, sample every second pixel in x and y directions
-    int sampleSize = int ((1.0f / desiredScale) + 0.5f);
+SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
+        size_t rowBytes, AndroidOptions& options) {
+    // Create an Options struct for the codec.
+    SkCodec::Options codecOptions;
+    codecOptions.fZeroInitialized = options.fZeroInitialized;
 
-    int scaledWidth = get_scaled_dimension(this->getInfo().width(), sampleSize);
-    int scaledHeight = get_scaled_dimension(this->getInfo().height(), sampleSize);
-
-    // Return the calculated output dimensions for the given scale
-    scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight);
-
-    return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions,
-                                  scaledCodecDimensions, desiredScale);
-}
-
-// check if scaling to dstInfo size from srcInfo size using sampleSize is possible
-static bool scaling_supported(const SkISize& dstDim, const SkISize& srcDim,
-                              int* sampleX, int* sampleY) {
-    SkScaledCodec::ComputeSampleSize(dstDim, srcDim, sampleX, sampleY);
-    const int dstWidth = dstDim.width();
-    const int dstHeight = dstDim.height();
-    const int srcWidth = srcDim.width();
-    const int srcHeight = srcDim.height();
-     // only support down sampling, not up sampling
-    if (dstWidth > srcWidth || dstHeight  > srcHeight) {
-        return false;
-    }
-    // check that srcWidth is scaled down by an integer value
-    if (get_scaled_dimension(srcWidth, *sampleX) != dstWidth) {
-        return false;
-    }
-    // check that src height is scaled down by an integer value
-    if (get_scaled_dimension(srcHeight, *sampleY) != dstHeight) {
-        return false;
-    }
-    // sampleX and sampleY should be equal unless the original sampleSize requested was larger
-    // than srcWidth or srcHeight. If so, the result of this is dstWidth or dstHeight = 1.
-    // This functionality allows for tall thin images to still be scaled down by scaling factors.
-    if (*sampleX != *sampleY){
-        if (1 != dstWidth && 1 != dstHeight) {
-            return false;
+    SkIRect* subset = options.fSubset;
+    if (!subset || subset->size() == fCodec->getInfo().dimensions()) {
+        if (fCodec->dimensionsSupported(info.dimensions())) {
+            return fCodec->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr,
+                    options.fColorCount);
         }
-    }
-    return true;
-}
 
-bool SkScaledCodec::onDimensionsSupported(const SkISize& dim) {
-    // Check with fCodec first. No need to call the non-virtual version, which
-    // just checks if it matches the original, since a match means this method
-    // will not be called.
-    if (fCodec->onDimensionsSupported(dim)) {
-        return true;
+        // If the native codec does not support the requested scale, scale by sampling.
+        return this->sampledDecode(info, pixels, rowBytes, options);
     }
 
-    // FIXME: These variables are unused, but are needed by scaling_supported.
-    // This class could also cache these values, and avoid calling this in
-    // onGetPixels (since getPixels already calls it).
-    int sampleX;
-    int sampleY;
-    return scaling_supported(dim, this->getInfo().dimensions(), &sampleX, &sampleY);
-}
+    // We are performing a subset decode.
+    int sampleSize = options.fSampleSize;
+    SkISize scaledSize = this->onGetSampledDimensions(sampleSize);
+    if (!fCodec->dimensionsSupported(scaledSize)) {
+        // If the native codec does not support the requested scale, scale by sampling.
+        return this->sampledDecode(info, pixels, rowBytes, options);
+    }
 
-// calculates sampleSize in x and y direction
-void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim,
-                                      int* sampleXPtr, int* sampleYPtr) {
-    int srcWidth = srcDim.width();
-    int dstWidth = dstDim.width();
-    int srcHeight = srcDim.height();
-    int dstHeight = dstDim.height();
+    // Calculate the scaled subset bounds.
+    int scaledSubsetX = subset->x() / sampleSize;
+    int scaledSubsetY = subset->y() / sampleSize;
+    int scaledSubsetWidth = info.width();
+    int scaledSubsetHeight = info.height();
 
-    int sampleX = srcWidth / dstWidth;
-    int sampleY = srcHeight / dstHeight;
+    // Start the scanline decode.
+    SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
+            scaledSize.height());
+    codecOptions.fSubset = &scanlineSubset;
+    SkCodec::Result result = fCodec->startScanlineDecode(info.makeWH(scaledSize.width(),
+            scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount);
+    if (SkCodec::kSuccess != result) {
+        return result;
+    }
 
-    // only support down sampling, not up sampling
-    SkASSERT(dstWidth <= srcWidth);
-    SkASSERT(dstHeight <= srcHeight);
-
-    // sampleX and sampleY should be equal unless the original sampleSize requested was
-    // larger than srcWidth or srcHeight.
-    // If so, the result of this is dstWidth or dstHeight = 1. This functionality
-    // allows for tall thin images to still be scaled down by scaling factors.
-
-    if (sampleX != sampleY){
-        if (1 != dstWidth && 1 != dstHeight) {
-
-            // rounding during onGetScaledDimensions can cause different sampleSizes
-            // Ex: srcWidth = 79, srcHeight = 20, sampleSize = 10
-            // dstWidth = 7, dstHeight = 2, sampleX = 79/7 = 11, sampleY = 20/2 = 10
-            // correct for this rounding by comparing width to sampleY and height to sampleX
-
-            if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) {
-                sampleX = sampleY;
-            } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) {
-                sampleY = sampleX;
+    // At this point, we are only concerned with subsetting.  Either no scale was
+    // requested, or the fCodec is handling the scale.
+    switch (fCodec->getScanlineOrder()) {
+        case SkCodec::kTopDown_SkScanlineOrder:
+        case SkCodec::kNone_SkScanlineOrder: {
+            if (!fCodec->skipScanlines(scaledSubsetY)) {
+                fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                        scaledSubsetHeight, 0);
+                return SkCodec::kIncompleteInput;
             }
-        }
-    }
 
-    if (sampleXPtr) {
-        *sampleXPtr = sampleX;
-    }
-    if (sampleYPtr) {
-        *sampleYPtr = sampleY;
+            int decodedLines = fCodec->getScanlines(pixels, scaledSubsetHeight, rowBytes);
+            if (decodedLines != scaledSubsetHeight) {
+                return SkCodec::kIncompleteInput;
+            }
+            return SkCodec::kSuccess;
+        }
+        default:
+            SkASSERT(false);
+            return SkCodec::kUnimplemented;
     }
 }
 
-// TODO: Implement subsetting in onGetPixels which works when and when not sampling 
 
-SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
-                                           size_t rowBytes, const Options& options,
-                                           SkPMColor ctable[], int* ctableCount,
-                                           int* rowsDecoded) {
+SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels,
+        size_t rowBytes, AndroidOptions& options) {
+    // Create options struct for the codec.
+    SkCodec::Options sampledOptions;
+    sampledOptions.fZeroInitialized = options.fZeroInitialized;
 
+    // Check if there is a subset.
+    SkIRect subset;
+    int subsetY = 0;
+    int subsetWidth = fCodec->getInfo().width();
+    int subsetHeight = fCodec->getInfo().height();
     if (options.fSubset) {
-        // Subsets are not supported.
-        return kUnimplemented;
+        // We will need to know about subsetting in the y-dimension in order to use the
+        // scanline decoder.
+        SkIRect* subsetPtr = options.fSubset;
+        subsetY = subsetPtr->y();
+        subsetWidth = subsetPtr->width();
+        subsetHeight = subsetPtr->height();
+
+        // The scanline decoder only needs to be aware of subsetting in the x-dimension.
+        subset.setXYWH(subsetPtr->x(), 0, subsetWidth, fCodec->getInfo().height());
+        sampledOptions.fSubset = &subset;
     }
 
-    if (fCodec->dimensionsSupported(requestedInfo.dimensions())) {
-        // Make sure that the parent class does not fill on an incomplete decode, since
-        // fCodec will take care of filling the uninitialized lines.
-        *rowsDecoded = requestedInfo.height();
-        return fCodec->getPixels(requestedInfo, dst, rowBytes, &options, ctable, ctableCount);
-    }
-
-    // scaling requested
-    int sampleX;
-    int sampleY;
-    if (!scaling_supported(requestedInfo.dimensions(), fCodec->getInfo().dimensions(),
-                           &sampleX, &sampleY)) {
-        // onDimensionsSupported would have returned false, meaning we should never reach here.
-        SkASSERT(false);
-        return kInvalidScale;
-    }
-
-    // set first sample pixel in y direction
-    const int Y0 = get_start_coord(sampleY);
-
-    const int dstHeight = requestedInfo.height();
-    const int srcWidth = fCodec->getInfo().width();
-    const int srcHeight = fCodec->getInfo().height();
-
-    const SkImageInfo info = requestedInfo.makeWH(srcWidth, srcHeight);
-
-    Result result = fCodec->startScanlineDecode(info, &options, ctable, ctableCount);
-
-    if (kSuccess != result) {
+    // Start the scanline decode.
+    SkCodec::Result result = fCodec->startScanlineDecode(
+            info.makeWH(fCodec->getInfo().width(), fCodec->getInfo().height()), &sampledOptions,
+            options.fColorPtr, options.fColorCount);
+    if (SkCodec::kSuccess != result) {
         return result;
     }
 
     SkSampler* sampler = fCodec->getSampler(true);
     if (!sampler) {
-        return kUnimplemented;
+        return SkCodec::kUnimplemented;
     }
 
-    if (sampler->setSampleX(sampleX) != requestedInfo.width()) {
-        return kInvalidScale;
+    // Since we guarantee that output dimensions are always at least one (even if the sampleSize
+    // is greater than a given dimension), the input sampleSize is not always the sampleSize that
+    // we use in practice.
+    const int sampleX = subsetWidth / info.width();
+    const int sampleY = subsetHeight / info.height();
+    if (sampler->setSampleX(sampleX) != info.width()) {
+        return SkCodec::kInvalidScale;
+    }
+    if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) {
+        return SkCodec::kInvalidScale;
     }
 
+    const int samplingOffsetY = get_start_coord(sampleY);
+    const int startY = samplingOffsetY + subsetY;
+    int dstHeight = info.height();
     switch(fCodec->getScanlineOrder()) {
         case SkCodec::kTopDown_SkScanlineOrder: {
-            if (!fCodec->skipScanlines(Y0)) {
-                *rowsDecoded = 0;
-                return kIncompleteInput;
+            if (!fCodec->skipScanlines(startY)) {
+                fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                        dstHeight, 0);
+                return SkCodec::kIncompleteInput;
             }
+            void* pixelPtr = pixels;
             for (int y = 0; y < dstHeight; y++) {
-                if (1 != fCodec->getScanlines(dst, 1, rowBytes)) {
-                    // The failed call to getScanlines() will take care of
-                    // filling the failed row, so we indicate that we have
-                    // decoded (y + 1) rows.
-                    *rowsDecoded = y + 1;
-                    return kIncompleteInput;
+                if (1 != fCodec->getScanlines(pixelPtr, 1, rowBytes)) {
+                    fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                            dstHeight, y + 1);
+                    return SkCodec::kIncompleteInput;
                 }
-                if (y < dstHeight - 1) {
-                    if (!fCodec->skipScanlines(sampleY - 1)) {
-                        *rowsDecoded = y + 1;
-                        return kIncompleteInput;
-                    }
+                int linesToSkip = SkTMin(sampleY - 1, dstHeight - y - 1);
+                if (!fCodec->skipScanlines(linesToSkip)) {
+                    fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                            dstHeight, y + 1);
+                    return SkCodec::kIncompleteInput;
                 }
-                dst = SkTAddOffset<void>(dst, rowBytes);
+                pixelPtr = SkTAddOffset<void>(pixelPtr, rowBytes);
             }
-            return kSuccess;
-        }
-        case SkCodec::kBottomUp_SkScanlineOrder:
-        case SkCodec::kOutOfOrder_SkScanlineOrder: {
-            Result result = kSuccess;
-            int y;
-            for (y = 0; y < srcHeight; y++) {
-                int srcY = fCodec->nextScanline();
-                if (is_coord_necessary(srcY, sampleY, dstHeight)) {
-                    void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY));
-                    if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) {
-                        result = kIncompleteInput;
-                        break;
-                    }
-                } else {
-                    if (!fCodec->skipScanlines(1)) {
-                        result = kIncompleteInput;
-                        break;
-                    }
-                }
-            }
-
-            // We handle filling uninitialized memory here instead of in the parent class.
-            // The parent class does not know that we are sampling.
-            if (kIncompleteInput == result) {
-                const uint32_t fillValue = fCodec->getFillValue(requestedInfo.colorType(),
-                        requestedInfo.alphaType());
-                for (; y < srcHeight; y++) {
-                    int srcY = fCodec->outputScanline(y);
-                    if (is_coord_necessary(srcY, sampleY, dstHeight)) {
-                        void* dstRow = SkTAddOffset<void>(dst,
-                                rowBytes * get_dst_coord(srcY, sampleY));
-                        SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow,
-                                rowBytes, fillValue, options.fZeroInitialized);
-                    }
-                }
-                *rowsDecoded = dstHeight;
-            }
-            return result;
+            return SkCodec::kSuccess;
         }
         case SkCodec::kNone_SkScanlineOrder: {
-            SkAutoMalloc storage(srcHeight * rowBytes);
+            const int linesNeeded = subsetHeight - samplingOffsetY;
+            SkAutoMalloc storage(linesNeeded * rowBytes);
             uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
-            int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes);
-            storagePtr += Y0 * rowBytes;
-            scanlines -= Y0;
-            int y = 0;
-            while (y < dstHeight && scanlines > 0) {
-                memcpy(dst, storagePtr, rowBytes);
+
+            if (!fCodec->skipScanlines(startY)) {
+                fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                        dstHeight, 0);
+                return SkCodec::kIncompleteInput;
+            }
+            int scanlines = fCodec->getScanlines(storagePtr, linesNeeded, rowBytes);
+
+            for (int y = 0; y < dstHeight; y++) {
+                memcpy(pixels, storagePtr, info.minRowBytes());
                 storagePtr += sampleY * rowBytes;
-                dst = SkTAddOffset<void>(dst, rowBytes);
-                scanlines -= sampleY;
-                y++;
+                pixels = SkTAddOffset<void>(pixels, rowBytes);
             }
-            if (y < dstHeight) {
+
+            if (scanlines < dstHeight) {
                 // fCodec has already handled filling uninitialized memory.
-                *rowsDecoded = dstHeight;
-                return kIncompleteInput;
+                return SkCodec::kIncompleteInput;
             }
-            return kSuccess;
+            return SkCodec::kSuccess;
         }
         default:
             SkASSERT(false);
-            return kUnimplemented;
+            return SkCodec::kUnimplemented;
     }
 }
-
-uint32_t SkScaledCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const {
-    return fCodec->onGetFillValue(colorType, alphaType);
-}
-
-SkCodec::SkScanlineOrder SkScaledCodec::onGetScanlineOrder() const {
-    return fCodec->onGetScanlineOrder();
-}
diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp
index de69124..4eea879 100644
--- a/src/codec/SkSwizzler.cpp
+++ b/src/codec/SkSwizzler.cpp
@@ -7,7 +7,6 @@
 
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
-#include "SkScaledCodec.h"
 #include "SkSwizzler.h"
 #include "SkTemplates.h"
 
diff --git a/src/codec/SkWebpAdapterCodec.cpp b/src/codec/SkWebpAdapterCodec.cpp
new file mode 100644
index 0000000..0804e74
--- /dev/null
+++ b/src/codec/SkWebpAdapterCodec.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec.h"
+#include "SkCodecPriv.h"
+#include "SkWebpAdapterCodec.h"
+
+SkWebpAdapterCodec::SkWebpAdapterCodec(SkWebpCodec* codec)
+    : INHERITED(codec->getInfo())
+    , fCodec(codec)
+{}
+
+SkISize SkWebpAdapterCodec::onGetSampledDimensions(int sampleSize) const {
+    float scale = get_scale_from_sample_size(sampleSize);
+    return fCodec->getScaledDimensions(scale);
+}
+
+bool SkWebpAdapterCodec::onGetSupportedSubset(SkIRect* desiredSubset) const {
+    return fCodec->getValidSubset(desiredSubset);
+}
+
+SkCodec::Result SkWebpAdapterCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels,
+        size_t rowBytes, AndroidOptions& options) {
+    // SkWebpCodec will support pretty much any dimensions that we provide, but we want
+    // to be stricter about the type of scaling that we allow, so we will add an extra
+    // check here.
+    SkISize supportedSize;
+    if (!options.fSubset) {
+        supportedSize = this->onGetSampledDimensions(options.fSampleSize);
+    } else {
+        supportedSize = this->getSampledSubsetDimensions(options.fSampleSize, *options.fSubset);
+    }
+    if (supportedSize != info.dimensions()) {
+        return SkCodec::kInvalidParameters;
+    }
+
+    SkCodec::Options codecOptions;
+    codecOptions.fZeroInitialized = options.fZeroInitialized;
+    codecOptions.fSubset = options.fSubset;
+    return fCodec->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr,
+            options.fColorCount);
+}
diff --git a/src/codec/SkWebpAdapterCodec.h b/src/codec/SkWebpAdapterCodec.h
new file mode 100644
index 0000000..b48f39b
--- /dev/null
+++ b/src/codec/SkWebpAdapterCodec.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkWebpAdapterCodec_DEFINED
+#define SkWebpAdapterCodec_DEFINED
+
+#include "SkAndroidCodec.h"
+#include "SkWebpCodec.h"
+
+/**
+ *  This class implements the functionality of SkAndroidCodec.  It uses an
+ *  SkWebpCodec.
+ */
+class SkWebpAdapterCodec : public SkAndroidCodec {
+public:
+
+    explicit SkWebpAdapterCodec(SkWebpCodec*);
+
+    virtual ~SkWebpAdapterCodec() {}
+
+protected:
+
+    SkEncodedFormat onGetEncodedFormat() const override { return kWEBP_SkEncodedFormat; };
+
+    SkISize onGetSampledDimensions(int sampleSize) const override;
+
+    bool onGetSupportedSubset(SkIRect* desiredSubset) const override;
+
+    SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
+            AndroidOptions& options) override;
+
+private:
+
+    SkAutoTDelete<SkWebpCodec> fCodec;
+
+    typedef SkAndroidCodec INHERITED;
+};
+#endif // SkWebpAdapterCodec_DEFINED
diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp
index 5ef1f8a..b2743bf 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -6,12 +6,12 @@
  */
 
 #include "Resources.h"
+#include "SkAndroidCodec.h"
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkData.h"
 #include "SkMD5.h"
 #include "SkRandom.h"
-#include "SkScaledCodec.h"
 #include "Test.h"
 
 static SkStreamAsset* resource(const char path[]) {
@@ -63,6 +63,20 @@
     }
 }
 
+static void test_android_info(skiatest::Reporter* r, SkAndroidCodec* codec, const SkImageInfo& info,
+                              SkCodec::Result expectedResult, const SkMD5::Digest* goodDigest) {
+    SkBitmap bm;
+    bm.allocPixels(info);
+    SkAutoLockPixels autoLockPixels(bm);
+
+    SkCodec::Result result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes());
+    REPORTER_ASSERT(r, result == expectedResult);
+
+    if (goodDigest) {
+        compare_to_good_digest(r, *goodDigest, bm);
+    }
+}
+
 SkIRect generate_random_subset(SkRandom* rand, int w, int h) {
     SkIRect rect;
     do {
@@ -128,6 +142,59 @@
     }
 }
 
+static void test_android_codec(skiatest::Reporter* r, SkAndroidCodec* codec, SkBitmap& bm,
+        const SkImageInfo& info, const SkISize& size, bool supports565,
+        SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) {
+
+    REPORTER_ASSERT(r, info.dimensions() == size);
+    bm.allocPixels(info);
+    SkAutoLockPixels autoLockPixels(bm);
+
+    SkCodec::Result result = codec->getAndroidPixels(info, bm.getPixels(), bm.rowBytes());
+    REPORTER_ASSERT(r, result == expectedResult);
+
+    md5(bm, digest);
+    if (goodDigest) {
+        REPORTER_ASSERT(r, *digest == *goodDigest);
+    }
+
+    {
+        // Test decoding to 565
+        SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType);
+        SkCodec::Result expected565 = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ?
+                expectedResult : SkCodec::kInvalidConversion;
+        test_android_info(r, codec, info565, expected565, nullptr);
+    }
+
+    // Verify that re-decoding gives the same result.  It is interesting to check this after
+    // a decode to 565, since choosing to decode to 565 may result in some of the decode
+    // options being modified.  These options should return to their defaults on another
+    // decode to kN32, so the new digest should match the old digest.
+    test_android_info(r, codec, info, expectedResult, digest);
+
+    {
+        // Check alpha type conversions
+        if (info.alphaType() == kOpaque_SkAlphaType) {
+            test_android_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType),
+                    SkCodec::kInvalidConversion, nullptr);
+            test_android_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType),
+                    SkCodec::kInvalidConversion, nullptr);
+        } else {
+            // Decoding to opaque should fail
+            test_android_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType),
+                    SkCodec::kInvalidConversion, nullptr);
+            SkAlphaType otherAt = info.alphaType();
+            if (kPremul_SkAlphaType == otherAt) {
+                otherAt = kUnpremul_SkAlphaType;
+            } else {
+                otherAt = kPremul_SkAlphaType;
+            }
+            // The other non-opaque alpha type should always succeed, but not match.
+            test_android_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr);
+        }
+    }
+}
+
 // FIXME: SkScaledCodec is currently only supported for types used by BRD
 // skbug.com/4428
 static bool supports_scaled_codec(const char path[]) {
@@ -288,13 +355,13 @@
             return;
         }
 
-        SkAutoTDelete<SkCodec> codec(nullptr);
+        SkAutoTDelete<SkAndroidCodec> codec(nullptr);
         if (isIncomplete) {
             size_t size = stream->getLength();
             SkAutoTUnref<SkData> data((SkData::NewFromStream(stream, 2 * size / 3)));
-            codec.reset(SkScaledCodec::NewFromData(data));
+            codec.reset(SkAndroidCodec::NewFromData(data));
         } else {
-            codec.reset(SkScaledCodec::NewFromStream(stream.detach()));
+            codec.reset(SkAndroidCodec::NewFromStream(stream.detach()));
         }
         if (!codec) {
             ERRORF(r, "Unable to decode '%s'", path);
@@ -303,8 +370,8 @@
 
         SkBitmap bm;
         SkMD5::Digest scaledCodecDigest;
-        test_codec(r, codec, bm, info, size, supports565, expectedResult, &scaledCodecDigest,
-                &codecDigest);
+        test_android_codec(r, codec, bm, info, size, supports565, expectedResult,
+                &scaledCodecDigest, &codecDigest);
     }
 
     // If we've just tested incomplete decodes, let's run the same test again on full decodes.
@@ -463,8 +530,9 @@
     SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false));
     REPORTER_ASSERT(r, !codec);
 
-    codec = SkScaledCodec::NewFromStream(new SkMemoryStream(stream, len, false));
-    REPORTER_ASSERT(r, !codec);
+    SkAndroidCodec* androidCodec =
+            SkAndroidCodec::NewFromStream(new SkMemoryStream(stream, len, false));
+    REPORTER_ASSERT(r, !androidCodec);
 }
 
 // Ensure that SkCodec::NewFromStream handles freeing the passed in SkStream,
@@ -496,8 +564,8 @@
     SkCodec* codec = SkCodec::NewFromStream(nullptr);
     REPORTER_ASSERT(r, !codec);
 
-    codec = SkScaledCodec::NewFromStream(nullptr);
-    REPORTER_ASSERT(r, !codec);
+    SkAndroidCodec* androidCodec = SkAndroidCodec::NewFromStream(nullptr);
+    REPORTER_ASSERT(r, !androidCodec);
 }
 
 static void test_dimensions(skiatest::Reporter* r, const char path[]) {
@@ -507,16 +575,16 @@
         SkDebugf("Missing resource '%s'\n", path);
         return;
     }
-    SkAutoTDelete<SkCodec> codec(SkScaledCodec::NewFromStream(stream.detach()));
+    SkAutoTDelete<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(stream.detach()));
     if (!codec) {
         ERRORF(r, "Unable to create codec '%s'", path);
         return;
     }
 
     // Check that the decode is successful for a variety of scales
-    for (float scale = 0.05f; scale < 2.0f; scale += 0.05f) {
+    for (int sampleSize = 1; sampleSize < 10; sampleSize++) {
         // Scale the output dimensions
-        SkISize scaledDims = codec->getScaledDimensions(scale);
+        SkISize scaledDims = codec->getSampledDimensions(sampleSize);
         SkImageInfo scaledInfo = codec->getInfo()
                 .makeWH(scaledDims.width(), scaledDims.height())
                 .makeColorType(kN32_SkColorType);
@@ -526,8 +594,10 @@
         size_t totalBytes = scaledInfo.getSafeSize(rowBytes);
         SkAutoTMalloc<SkPMColor> pixels(totalBytes);
 
+        SkAndroidCodec::AndroidOptions options;
+        options.fSampleSize = sampleSize;
         SkCodec::Result result =
-                codec->getPixels(scaledInfo, pixels.get(), rowBytes, nullptr, nullptr, nullptr);
+                codec->getAndroidPixels(scaledInfo, pixels.get(), rowBytes, &options);
         REPORTER_ASSERT(r, SkCodec::kSuccess == result);
     }
 }