Merge SkCodec with SkScanlineDecoder

Benefits:
- This mimics other decoding APIs (including the ones SkCodec relies
on, e.g. a png_struct, which can be used to decode an entire image or
one line at a time).

- It allows a client to ask us to do what we can do efficiently - i.e.
start from encoded data and either decode the whole thing or scanlines.

- It removes the duplicate methods which appeared in both SkCodec and
SkScanlineDecoder (some of which, e.g. in SkJpegScanlineDecoder, just
call fCodec->sameMethod()).

- It simplifies moving more checks into the base class (e.g. the
examples in skbug.com/4284).

BUG=skia:4175
BUG=skia:4284

=====================================================================

SkScanlineDecoder.h/.cpp:
Removed.

SkCodec.h/.cpp:
Add methods, enums, and variables which were previously in
SkScanlineDecoder.
Default fCurrScanline to -1, as a sentinel that start has not been
called.

General changes:
Convert SkScanlineDecoders to SkCodecs.

General changes in SkCodec subclasses:
Merge SkScanlineDecoder implementation into SkCodec. Most (all?) owned
an SkCodec, so they now call this-> instead of fCodec->.

SkBmpCodec.h/.cpp:
Replace the unused rowOrder method with an override for
onGetScanlineOrder.
Make getDstRow const, since it is called by onGetY, which is const.

SkCodec_libpng.h/.cpp:
Make SkPngCodec an abstract class, with two subclasses which handle
scanline decoding separately (they share code for decoding the entire
image). Reimplement onReallyHasAlpha so that it can return the most
recent result (e.g. after a scanline decode which only decoded part
of the image) or a better answer (e.g. if the whole image is known to
be opaque).
Compute fNumberPasses early, so we know which subclass to instantiate.
Make SkPngInterlaceScanlineDecoder use the base class' fCurrScanline
rather than a separate variable.

CodexTest.cpp:
Add tests for the state changes in SkCodec (need to call start before
decoding scanlines; calling getPixels means that start will need to
be called again before decoding more scanlines).
Add a test which decodes in stripes, currently only used for an
interlaced PNG.

TODO: Add tests for onReallyHasAlpha.

Review URL: https://codereview.chromium.org/1365313002
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index a1db0ac..900ba4d 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -38,7 +38,6 @@
 #include "SkOSFile.h"
 #include "SkPictureRecorder.h"
 #include "SkPictureUtils.h"
-#include "SkScanlineDecoder.h"
 #include "SkString.h"
 #include "SkSurface.h"
 #include "SkTaskGroup.h"
@@ -522,9 +521,7 @@
         int colorCount;
         const SkImageInfo info = codec->getInfo().makeColorType(colorType);
         SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]);
-        SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(SkScanlineDecoder::NewFromData(encoded));
-        if (nullptr == scanlineDecoder || scanlineDecoder->start(info, nullptr,
-                colors, &colorCount) != SkCodec::kSuccess)
+        if (codec->startScanlineDecode(info, nullptr, colors, &colorCount) != SkCodec::kSuccess)
         {
             SkDebugf("Could not create scanline decoder for %s with color type %s.  "
                     "Skipping bench.\n", path.c_str(), color_type_to_str(colorType));
diff --git a/bench/subset/SubsetSingleBench.cpp b/bench/subset/SubsetSingleBench.cpp
index 2e5703f..e409cf7 100644
--- a/bench/subset/SubsetSingleBench.cpp
+++ b/bench/subset/SubsetSingleBench.cpp
@@ -12,7 +12,6 @@
 #include "SkCodec.h"
 #include "SkImageDecoder.h"
 #include "SkOSFile.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 
 /*
@@ -65,20 +64,19 @@
     SkPMColor colors[256];
     if (fUseCodec) {
         for (int count = 0; count < n; count++) {
-            SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(
-                    SkScanlineDecoder::NewFromStream(fStream->duplicate()));
-            const SkImageInfo info = scanlineDecoder->getInfo().makeColorType(fColorType);
+            SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(fStream->duplicate()));
+            const SkImageInfo info = codec->getInfo().makeColorType(fColorType);
             SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]);
-            scanlineDecoder->start(info, nullptr, colors, &colorCount);
+            codec->startScanlineDecode(info, nullptr, colors, &colorCount);
 
             SkBitmap bitmap;
             SkImageInfo subsetInfo = info.makeWH(fSubsetWidth, fSubsetHeight);
             alloc_pixels(&bitmap, subsetInfo, colors, colorCount);
 
-            scanlineDecoder->skipScanlines(fOffsetTop);
+            codec->skipScanlines(fOffsetTop);
             uint32_t bpp = info.bytesPerPixel();
             for (uint32_t y = 0; y < fSubsetHeight; y++) {
-                scanlineDecoder->getScanlines(row.get(), 1, 0);
+                codec->getScanlines(row.get(), 1, 0);
                 memcpy(bitmap.getAddr(0, y), row.get() + fOffsetLeft * bpp,
                         fSubsetWidth * bpp);
             }
diff --git a/bench/subset/SubsetTranslateBench.cpp b/bench/subset/SubsetTranslateBench.cpp
index bed3580..a5c4d46 100644
--- a/bench/subset/SubsetTranslateBench.cpp
+++ b/bench/subset/SubsetTranslateBench.cpp
@@ -12,7 +12,6 @@
 #include "SkCodec.h"
 #include "SkImageDecoder.h"
 #include "SkOSFile.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 
 /*
@@ -61,11 +60,10 @@
     SkPMColor colors[256];
     if (fUseCodec) {
         for (int count = 0; count < n; count++) {
-            SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(
-                    SkScanlineDecoder::NewFromStream(fStream->duplicate()));
-            const SkImageInfo info = scanlineDecoder->getInfo().makeColorType(fColorType);
+            SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(fStream->duplicate()));
+            const SkImageInfo info = codec->getInfo().makeColorType(fColorType);
             SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]);
-            scanlineDecoder->start(info, nullptr, colors, &colorCount);
+            codec->startScanlineDecode(info, nullptr, colors, &colorCount);
 
             SkBitmap bitmap;
             // Note that we use the same bitmap for all of the subsets.
@@ -75,7 +73,7 @@
 
             for (int x = 0; x < info.width(); x += fSubsetWidth) {
                 for (int y = 0; y < info.height(); y += fSubsetHeight) {
-                    scanlineDecoder->skipScanlines(y);
+                    codec->skipScanlines(y);
                     const uint32_t currSubsetWidth =
                             x + (int) fSubsetWidth > info.width() ?
                             info.width() - x : fSubsetWidth;
@@ -84,7 +82,7 @@
                             info.height() - y : fSubsetHeight;
                     const uint32_t bpp = info.bytesPerPixel();
                     for (uint32_t y = 0; y < currSubsetHeight; y++) {
-                        scanlineDecoder->getScanlines(row.get(), 1, 0);
+                        codec->getScanlines(row.get(), 1, 0);
                         memcpy(bitmap.getAddr(0, y), row.get() + x * bpp,
                                 currSubsetWidth * bpp);
                     }
diff --git a/bench/subset/SubsetZoomBench.cpp b/bench/subset/SubsetZoomBench.cpp
index 655285f..869e930 100644
--- a/bench/subset/SubsetZoomBench.cpp
+++ b/bench/subset/SubsetZoomBench.cpp
@@ -12,7 +12,6 @@
 #include "SkCodec.h"
 #include "SkImageDecoder.h"
 #include "SkOSFile.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 
 /*
@@ -61,11 +60,10 @@
     SkPMColor colors[256];
     if (fUseCodec) {
         for (int count = 0; count < n; count++) {
-            SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(
-                    SkScanlineDecoder::NewFromStream(fStream->duplicate()));
-            const SkImageInfo info = scanlineDecoder->getInfo().makeColorType(fColorType);
+            SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(fStream->duplicate()));
+            const SkImageInfo info = codec->getInfo().makeColorType(fColorType);
             SkAutoTDeleteArray<uint8_t> row(new uint8_t[info.minRowBytes()]);
-            scanlineDecoder->start(info, nullptr, colors, &colorCount);
+            codec->startScanlineDecode(info, nullptr, colors, &colorCount);
 
             const int centerX = info.width() / 2;
             const int centerY = info.height() / 2;
@@ -83,9 +81,9 @@
                 alloc_pixels(&bitmap, subsetInfo, colors, colorCount);
 
                 uint32_t bpp = info.bytesPerPixel();
-                scanlineDecoder->skipScanlines(subsetStartY);
+                codec->skipScanlines(subsetStartY);
                 for (int y = 0; y < subsetHeight; y++) {
-                    scanlineDecoder->getScanlines(row.get(), 1, 0);
+                    codec->getScanlines(row.get(), 1, 0);
                     memcpy(bitmap.getAddr(0, y), row.get() + subsetStartX * bpp,
                             subsetWidth * bpp);
                 }
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 11581fd..0faca8f 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -311,6 +311,12 @@
         }
     }
 
+    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.
+        return;
+    }
+
     // SkScaledCodec Scales
     // The native scales are included to make sure that SkScaledCodec defaults to the native
     // scaling strategy when possible.
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 96a0ab1..2e22556 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -25,7 +25,6 @@
 #include "SkRecorder.h"
 #include "SkSVGCanvas.h"
 #include "SkScaledCodec.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 #include "SkTLogic.h"
 #include "SkXMLWriter.h"
@@ -238,18 +237,6 @@
         || flags.approach != SinkFlags::kDirect;
 }
 
-SkScanlineDecoder* start_scanline_decoder(SkData* encoded, const SkImageInfo& info,
-        SkPMColor* colorPtr, int* colorCountPtr) {
-    SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(SkScanlineDecoder::NewFromData(encoded));
-    if (nullptr == scanlineDecoder) {
-        return nullptr;
-    }
-    if (SkCodec::kSuccess != scanlineDecoder->start(info, NULL, colorPtr, colorCountPtr)) {
-        return nullptr;
-    }
-    return scanlineDecoder.detach();
-}
-
 Error CodecSrc::draw(SkCanvas* canvas) const {
     SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(fPath.c_str()));
     if (!encoded) {
@@ -348,25 +335,24 @@
             break;
         }
         case kScanline_Mode: {
-            SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(
-                    start_scanline_decoder(encoded.get(), decodeInfo, colorPtr, colorCountPtr));
-            if (nullptr == scanlineDecoder) {
+            if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+                                                                colorCountPtr)) {
                 return Error::Nonfatal("Could not start scanline decoder");
             }
 
             SkCodec::Result result = SkCodec::kUnimplemented;
-            switch (scanlineDecoder->getScanlineOrder()) {
-                case SkScanlineDecoder::kTopDown_SkScanlineOrder:
-                case SkScanlineDecoder::kBottomUp_SkScanlineOrder:
-                case SkScanlineDecoder::kNone_SkScanlineOrder:
-                    result = scanlineDecoder->getScanlines(bitmap.getAddr(0, 0),
+            switch (codec->getScanlineOrder()) {
+                case SkCodec::kTopDown_SkScanlineOrder:
+                case SkCodec::kBottomUp_SkScanlineOrder:
+                case SkCodec::kNone_SkScanlineOrder:
+                    result = codec->getScanlines(bitmap.getAddr(0, 0),
                             decodeInfo.height(), bitmap.rowBytes());
                     break;
-                case SkScanlineDecoder::kOutOfOrder_SkScanlineOrder: {
+                case SkCodec::kOutOfOrder_SkScanlineOrder: {
                     for (int y = 0; y < decodeInfo.height(); y++) {
-                        int dstY = scanlineDecoder->getY();
+                        int dstY = codec->nextScanline();
                         void* dstPtr = bitmap.getAddr(0, dstY);
-                        result = scanlineDecoder->getScanlines(dstPtr, 1, bitmap.rowBytes());
+                        result = codec->getScanlines(dstPtr, 1, bitmap.rowBytes());
                         if (SkCodec::kSuccess != result && SkCodec::kIncompleteInput != result) {
                             return SkStringPrintf("%s failed with error message %d",
                                                   fPath.c_str(), (int) result);
@@ -432,11 +418,10 @@
                             subsetHeight + extraY : subsetHeight;
                     const int y = row * subsetHeight;
                     //create scanline decoder for each subset
-                    SkAutoTDelete<SkScanlineDecoder> decoder(start_scanline_decoder(encoded.get(),
-                            decodeInfo, colorPtr, colorCountPtr));
-                    // TODO (msarett): Support this mode for all scanline orderings.
-                    if (nullptr == decoder || SkScanlineDecoder::kTopDown_SkScanlineOrder !=
-                            decoder->getScanlineOrder()) {
+                    if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+                                                                        colorCountPtr)
+                            // TODO (msarett): Support this mode for all scanline orderings.
+                            || SkCodec::kTopDown_SkScanlineOrder != codec->getScanlineOrder()) {
                         if (x == 0 && y == 0) {
                             //first try, image may not be compatible
                             return Error::Nonfatal("Could not start top-down scanline decoder");
@@ -445,7 +430,7 @@
                         }
                     }
                     //skip to first line of subset
-                    const SkCodec::Result skipResult = decoder->skipScanlines(y);
+                    const SkCodec::Result skipResult = codec->skipScanlines(y);
                     switch (skipResult) {
                         case SkCodec::kSuccess:
                         case SkCodec::kIncompleteInput:
@@ -461,7 +446,7 @@
                     SkAssertResult(largestSubsetBm.extractSubset(&subsetBm, bounds));
                     SkAutoLockPixels autlockSubsetBm(subsetBm, true);
                     const SkCodec::Result subsetResult =
-                            decoder->getScanlines(buffer, currentSubsetHeight, rowBytes);
+                            codec->getScanlines(buffer, currentSubsetHeight, rowBytes);
                     switch (subsetResult) {
                         case SkCodec::kSuccess:
                         case SkCodec::kIncompleteInput:
@@ -501,10 +486,9 @@
             const int numStripes = (height + stripeHeight - 1) / stripeHeight;
 
             // Decode odd stripes
-            SkAutoTDelete<SkScanlineDecoder> decoder(
-                    start_scanline_decoder(encoded.get(), decodeInfo, colorPtr, colorCountPtr));
-            if (nullptr == decoder ||
-                    SkScanlineDecoder::kTopDown_SkScanlineOrder != decoder->getScanlineOrder()) {
+            if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+                                                                colorCountPtr)
+                    || SkCodec::kTopDown_SkScanlineOrder != codec->getScanlineOrder()) {
                 // This mode was designed to test the new skip scanlines API in libjpeg-turbo.
                 // Jpegs have kTopDown_SkScanlineOrder, and at this time, it is not interesting
                 // to run this test for image types that do not have this scanline ordering.
@@ -513,7 +497,7 @@
             for (int i = 0; i < numStripes; i += 2) {
                 // Skip a stripe
                 const int linesToSkip = SkTMin(stripeHeight, height - i * stripeHeight);
-                SkCodec::Result result = decoder->skipScanlines(linesToSkip);
+                SkCodec::Result result = codec->skipScanlines(linesToSkip);
                 switch (result) {
                     case SkCodec::kSuccess:
                     case SkCodec::kIncompleteInput:
@@ -526,7 +510,7 @@
                 const int startY = (i + 1) * stripeHeight;
                 const int linesToRead = SkTMin(stripeHeight, height - startY);
                 if (linesToRead > 0) {
-                    result = decoder->getScanlines(bitmap.getAddr(0, startY),
+                    result = codec->getScanlines(bitmap.getAddr(0, startY),
                             linesToRead, bitmap.rowBytes());
                     switch (result) {
                         case SkCodec::kSuccess:
@@ -539,8 +523,8 @@
             }
 
             // Decode even stripes
-            const SkCodec::Result startResult = decoder->start(decodeInfo, nullptr, colorPtr,
-                                                               colorCountPtr);
+            const SkCodec::Result startResult = codec->startScanlineDecode(decodeInfo, nullptr,
+                    colorPtr, colorCountPtr);
             if (SkCodec::kSuccess != startResult) {
                 return "Failed to restart scanline decoder with same parameters.";
             }
@@ -548,7 +532,7 @@
                 // Read a stripe
                 const int startY = i * stripeHeight;
                 const int linesToRead = SkTMin(stripeHeight, height - startY);
-                SkCodec::Result result = decoder->getScanlines(bitmap.getAddr(0, startY),
+                SkCodec::Result result = codec->getScanlines(bitmap.getAddr(0, startY),
                         linesToRead, bitmap.rowBytes());
                 switch (result) {
                     case SkCodec::kSuccess:
@@ -561,7 +545,7 @@
                 // Skip a stripe
                 const int linesToSkip = SkTMin(stripeHeight, height - (i + 1) * stripeHeight);
                 if (linesToSkip > 0) {
-                    result = decoder->skipScanlines(linesToSkip);
+                    result = codec->skipScanlines(linesToSkip);
                     switch (result) {
                         case SkCodec::kSuccess:
                         case SkCodec::kIncompleteInput:
diff --git a/gyp/codec.gyp b/gyp/codec.gyp
index a99707e..205ff91 100644
--- a/gyp/codec.gyp
+++ b/gyp/codec.gyp
@@ -47,7 +47,6 @@
         '../src/codec/SkMaskSwizzler.cpp',
         '../src/codec/SkMasks.cpp',
         '../src/codec/SkScaledCodec.cpp',
-        '../src/codec/SkScanlineDecoder.cpp',
         '../src/codec/SkSwizzler.cpp',
         '../src/codec/SkWebpCodec.cpp',
       ],
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index bdfa232..01fd4fa 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -45,7 +45,7 @@
     /**
      *  Return the ImageInfo associated with this codec.
      */
-    const SkImageInfo& getInfo() const { return fInfo; }
+    const SkImageInfo& getInfo() const { return fSrcInfo; }
 
     /**
      *  Return a size that approximately supports the desired scale factor.
@@ -131,7 +131,12 @@
          */
         kCouldNotRewind,
         /**
-         *  This method is not implemented by this generator.
+         *  startScanlineDecode() was not called before calling getScanlines.
+         */
+        kScanlineDecodingNotStarted,
+        /**
+         *  This method is not implemented by this codec.
+         *  FIXME: Perhaps this should be kUnsupported?
          */
         kUnimplemented,
     };
@@ -202,6 +207,9 @@
      *  If info is not kIndex8_SkColorType, then the last two parameters may be NULL. If ctableCount
      *  is not null, it will be set to 0.
      *
+     *  If a scanline decode is in progress, scanline mode will end, requiring the client to call
+     *  startScanlineDecode() in order to return to decoding scanlines.
+     *
      *  @return Result kSuccess, or another value explaining the type of failure.
      */
     Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options*,
@@ -225,6 +233,147 @@
         return this->onReallyHasAlpha();
     }
 
+    /**
+     * The remaining functions revolve around decoding scanlines.
+     */
+
+    /**
+     *  Prepare for a scanline decode with the specified options.
+     *
+     *  After this call, this class will be ready to decode the first scanline.
+     *
+     *  This must be called in order to call getScanlines or skipScanlines.
+     *
+     *  This may require rewinding the stream.
+     *
+     *  Not all SkCodecs support this.
+     *
+     *  @param dstInfo Info of the destination. If the dimensions do not match
+     *      those of getInfo, this implies a scale.
+     *  @param options Contains decoding options, including if memory is zero
+     *      initialized.
+     *  @param ctable A pointer to a color table.  When dstInfo.colorType() is
+     *      kIndex8, this should be non-NULL and have enough storage for 256
+     *      colors.  The color table will be populated after decoding the palette.
+     *  @param ctableCount A pointer to the size of the color table.  When
+     *      dstInfo.colorType() is kIndex8, this should be non-NULL.  It will
+     *      be modified to the true size of the color table (<= 256) after
+     *      decoding the palette.
+     *  @return Enum representing success or reason for failure.
+     */
+    Result startScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options* options,
+                 SkPMColor ctable[], int* ctableCount);
+
+    /**
+     *  Simplified version of startScanlineDecode() that asserts that info is NOT
+     *  kIndex8_SkColorType and uses the default Options.
+     */
+    Result startScanlineDecode(const SkImageInfo& dstInfo);
+
+    /**
+     *  Write the next countLines scanlines into dst.
+     *
+     *  Not valid to call before calling startScanlineDecode().
+     *
+     *  @param dst Must be non-null, and large enough to hold countLines
+     *      scanlines of size rowBytes.
+     *  @param countLines Number of lines to write.
+     *  @param rowBytes Number of bytes per row. Must be large enough to hold
+     *      a scanline based on the SkImageInfo used to create this object.
+     */
+    Result getScanlines(void* dst, int countLines, size_t rowBytes);
+
+    /**
+     *  Skip count scanlines.
+     *
+     *  Not valid to call before calling startScanlineDecode().
+     *
+     *  The default version just calls onGetScanlines and discards the dst.
+     *  NOTE: If skipped lines are the only lines with alpha, this default
+     *  will make reallyHasAlpha return true, when it could have returned
+     *  false.
+     */
+    Result skipScanlines(int countLines);
+
+    /**
+     *  The order in which rows are output from the scanline decoder is not the
+     *  same for all variations of all image types.  This explains the possible
+     *  output row orderings.
+     */
+    enum SkScanlineOrder {
+        /*
+         * By far the most common, this indicates that the image can be decoded
+         * reliably using the scanline decoder, and that rows will be output in
+         * the logical order.
+         */
+        kTopDown_SkScanlineOrder,
+
+        /*
+         * This indicates that the scanline decoder reliably outputs rows, but
+         * they will be returned in reverse order.  If the scanline format is
+         * kBottomUp, the nextScanline() API can be used to determine the actual
+         * y-coordinate of the next output row, but the client is not forced
+         * to take advantage of this, given that it's not too tough to keep
+         * track independently.
+         *
+         * For full image decodes, it is safe to get all of the scanlines at
+         * once, since the decoder will handle inverting the rows as it
+         * decodes.
+         *
+         * For subset decodes and sampling, it is simplest to get and skip
+         * scanlines one at a time, using the nextScanline() API.  It is
+         * possible to ask for larger chunks at a time, but this should be used
+         * with caution.  As with full image decodes, the decoder will handle
+         * inverting the requested rows, but rows will still be delivered
+         * starting from the bottom of the image.
+         *
+         * Upside down bmps are an example.
+         */
+        kBottomUp_SkScanlineOrder,
+
+        /*
+         * This indicates that the scanline decoder reliably outputs rows, but
+         * they will not be in logical order.  If the scanline format is
+         * kOutOfOrder, the nextScanline() API should be used to determine the
+         * actual y-coordinate of the next output row.
+         *
+         * For this scanline ordering, it is advisable to get and skip
+         * scanlines one at a time.
+         *
+         * Interlaced gifs are an example.
+         */
+        kOutOfOrder_SkScanlineOrder,
+
+        /*
+         * Indicates that the entire image must be decoded in order to output
+         * any amount of scanlines.  In this case, it is a REALLY BAD IDEA to
+         * request scanlines 1-by-1 or in small chunks.  The client should
+         * determine which scanlines are needed and ask for all of them in
+         * a single call to getScanlines().
+         *
+         * Interlaced pngs are an example.
+         */
+        kNone_SkScanlineOrder,
+    };
+
+    /**
+     *  An enum representing the order in which scanlines will be returned by
+     *  the scanline decoder.
+     */
+    SkScanlineOrder getScanlineOrder() const { return this->onGetScanlineOrder(); }
+
+    /**
+     *  Returns the y-coordinate of the next row to be returned by the scanline
+     *  decoder.  This will be overridden in the case of
+     *  kOutOfOrder_SkScanlineOrder and should be unnecessary in the case of
+     *  kNone_SkScanlineOrder.
+     *
+     *  Results are undefined when not in scanline decoding mode.
+     */
+    int nextScanline() const {
+        return this->onNextScanline();
+    }
+
 protected:
     SkCodec(const SkImageInfo&, SkStream*);
 
@@ -275,9 +424,59 @@
         return fStream.get();
     }
 
+    /**
+     *  The remaining functions revolve around decoding scanlines.
+     */
+
+    /**
+     *  Most images types will be kTopDown and will not need to override this function.
+     */
+    virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; }
+
+    /**
+     *  Most images will be kTopDown and will not need to override this function.
+     */
+    virtual int onNextScanline() const { return fCurrScanline; }
+
+    /**
+     *  Update the next scanline. Used by interlaced png.
+     */
+    void updateNextScanline(int newY) { fCurrScanline = newY; }
+
+    const SkImageInfo& dstInfo() const { return fDstInfo; }
+
+    const SkCodec::Options& options() const { return fOptions; }
+
 private:
-    const SkImageInfo       fInfo;
+    const SkImageInfo       fSrcInfo;
     SkAutoTDelete<SkStream> fStream;
     bool                    fNeedsRewind;
+    // These fields are only meaningful during scanline decodes.
+    SkImageInfo             fDstInfo;
+    SkCodec::Options        fOptions;
+    int                     fCurrScanline;
+
+    // Methods for scanline decoding.
+    virtual SkCodec::Result onStartScanlineDecode(const SkImageInfo& dstInfo,
+            const SkCodec::Options& options, SkPMColor ctable[], int* ctableCount) {
+        return kUnimplemented;
+    }
+
+    // Naive default version just calls onGetScanlines on temp memory.
+    virtual SkCodec::Result onSkipScanlines(int countLines) {
+        SkAutoMalloc storage(fDstInfo.minRowBytes());
+        // Note that we pass 0 to rowBytes so we continue to use the same memory.
+        // Also note that while getScanlines checks that rowBytes is big enough,
+        // onGetScanlines bypasses that check.
+        // Calling the virtual method also means we do not double count
+        // countLines.
+        return this->onGetScanlines(storage.get(), countLines, 0);
+    }
+
+    virtual SkCodec::Result onGetScanlines(void* dst, int countLines,
+                                                    size_t rowBytes) {
+        return kUnimplemented;
+    }
+
 };
 #endif // SkCodec_DEFINED
diff --git a/include/codec/SkScaledCodec.h b/include/codec/SkScaledCodec.h
index 1bcdf08..20428d8 100644
--- a/include/codec/SkScaledCodec.h
+++ b/include/codec/SkScaledCodec.h
@@ -8,9 +8,7 @@
 #define SkScaledCodec_DEFINED
 
 #include "SkCodec.h"
-#include "SkScanlineDecoder.h"
 
-class SkScanlineDecoder;
 class SkStream;
 
 /**
@@ -52,18 +50,18 @@
     Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*)
             override;
     SkEncodedFormat onGetEncodedFormat() const override {
-        return fScanlineDecoder->getEncodedFormat();
+        return fCodec->getEncodedFormat();
     }
 
     bool onReallyHasAlpha() const override {
-        return fScanlineDecoder->reallyHasAlpha();
+        return fCodec->reallyHasAlpha();
     }
 
 private:
 
-    SkAutoTDelete<SkScanlineDecoder> fScanlineDecoder;
+    SkAutoTDelete<SkCodec> fCodec;
 
-    explicit SkScaledCodec(SkScanlineDecoder*);
+    explicit SkScaledCodec(SkCodec*);
 
     typedef SkCodec INHERITED;
 };
diff --git a/include/codec/SkScanlineDecoder.h b/include/codec/SkScanlineDecoder.h
deleted file mode 100644
index 61184d6..0000000
--- a/include/codec/SkScanlineDecoder.h
+++ /dev/null
@@ -1,288 +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 SkScanlineDecoder_DEFINED
-#define SkScanlineDecoder_DEFINED
-
-#include "../private/SkTemplates.h"
-#include "SkCodec.h"
-#include "SkImageInfo.h"
-#include "SkTypes.h"
-
-class SkScanlineDecoder : public SkNoncopyable {
-public:
-    /**
-     *  If this stream represents an encoded image that we know how to decode
-     *  in scanlines, return an SkScanlineDecoder that can decode it. Otherwise
-     *  return NULL.
-     *
-     *  start() must be called in order to decode any scanlines.
-     *
-     *  If NULL is returned, the stream is deleted immediately. Otherwise, the
-     *  SkScanlineDecoder takes ownership of it, and will delete it when done
-     *  with it.
-     */
-    static SkScanlineDecoder* NewFromStream(SkStream*);
-
-    /**
-     *  Similar to NewFromStream, but reads from an SkData.
-     *
-     *  Will take a ref if it returns a scanline decoder, else will not affect
-     *  the data.
-     */
-    static SkScanlineDecoder* NewFromData(SkData*);
-
-    /**
-     *  Clean up after reading/skipping scanlines.
-     *
-     *  It is possible that not all scanlines will have been read/skipped.  In
-     *  fact, in the case of subset decodes, it is likely that there will be
-     *  scanlines at the bottom of the image that have been ignored.
-     */
-    virtual ~SkScanlineDecoder() {}
-
-    /**
-     *  Return a size that approximately supports the desired scale factor.
-     *  The codec may not be able to scale efficiently to the exact scale
-     *  factor requested, so return a size that approximates that scale.
-     *  The returned value is the codec's suggestion for the closest valid
-     *  scale that it can natively support
-     *  FIXME: share this with SkCodec
-     */
-    SkISize getScaledDimensions(float desiredScale) {
-        return this->onGetScaledDimensions(desiredScale);
-    }
-
-    /**
-     *  Returns the default info, corresponding to the encoded data.
-     */
-    const SkImageInfo& getInfo() { return fSrcInfo; }
-
-    /**
-     *  Initialize on the first scanline, with the specified options.
-     *
-     *  This must be called in order to call getScanlnies or skipScanlines. If
-     *  it has been called before, this will require rewinding the stream.
-     *
-     *  @param dstInfo Info of the destination. If the dimensions do not match
-     *      those of getInfo, this implies a scale.
-     *  @param options Contains decoding options, including if memory is zero
-     *      initialized.
-     *  @param ctable A pointer to a color table.  When dstInfo.colorType() is
-     *      kIndex8, this should be non-NULL and have enough storage for 256
-     *      colors.  The color table will be populated after decoding the palette.
-     *  @param ctableCount A pointer to the size of the color table.  When
-     *      dstInfo.colorType() is kIndex8, this should be non-NULL.  It will
-     *      be modified to the true size of the color table (<= 256) after
-     *      decoding the palette.
-     *  @return Enum representing success or reason for failure.
-     */
-    SkCodec::Result start(const SkImageInfo& dstInfo, const SkCodec::Options* options,
-                          SkPMColor ctable[], int* ctableCount);
-
-    /**
-     *  Simplified version of start() that asserts that info is NOT
-     *  kIndex8_SkColorType and uses the default Options.
-     */
-    SkCodec::Result start(const SkImageInfo& dstInfo);
-
-    /**
-     *  Write the next countLines scanlines into dst.
-     *
-     *  Not valid to call before calling start().
-     *
-     *  @param dst Must be non-null, and large enough to hold countLines
-     *      scanlines of size rowBytes.
-     *  @param countLines Number of lines to write.
-     *  @param rowBytes Number of bytes per row. Must be large enough to hold
-     *      a scanline based on the SkImageInfo used to create this object.
-     */
-    SkCodec::Result getScanlines(void* dst, int countLines, size_t rowBytes) {
-        SkASSERT(!fDstInfo.isEmpty());
-        if ((rowBytes < fDstInfo.minRowBytes() && countLines > 1 ) || countLines <= 0
-                || fCurrScanline + countLines > fDstInfo.height()) {
-            return SkCodec::kInvalidParameters;
-        }
-        const SkCodec::Result result = this->onGetScanlines(dst, countLines, rowBytes);
-        fCurrScanline += countLines;
-        return result;
-    }
-
-    /**
-     *  Skip count scanlines.
-     *
-     *  Not valid to call before calling start().
-     *
-     *  The default version just calls onGetScanlines and discards the dst.
-     *  NOTE: If skipped lines are the only lines with alpha, this default
-     *  will make reallyHasAlpha return true, when it could have returned
-     *  false.
-     */
-    SkCodec::Result skipScanlines(int countLines) {
-        SkASSERT(!fDstInfo.isEmpty());
-        if (fCurrScanline + countLines > fDstInfo.height()) {
-            // Arguably, we could just skip the scanlines which are remaining,
-            // and return kSuccess. We choose to return invalid so the client
-            // can catch their bug.
-            return SkCodec::kInvalidParameters;
-        }
-        const SkCodec::Result result = this->onSkipScanlines(countLines);
-        fCurrScanline += countLines;
-        return result;
-    }
-
-    /**
-     *  Some images may initially report that they have alpha due to the format
-     *  of the encoded data, but then never use any colors which have alpha
-     *  less than 100%. This function can be called *after* decoding to
-     *  determine if such an image truly had alpha. Calling it before decoding
-     *  is undefined.
-     *  FIXME: see skbug.com/3582.
-     */
-    bool reallyHasAlpha() const {
-        return this->onReallyHasAlpha();
-    }
-
-    /**
-     *  Format of the encoded data.
-     */
-    SkEncodedFormat getEncodedFormat() const { return this->onGetEncodedFormat(); }
-
-    /**
-     *  The order in which rows are output from the scanline decoder is not the
-     *  same for all variations of all image types.  This explains the possible
-     *  output row orderings.
-     */
-    enum SkScanlineOrder {
-        /*
-         * By far the most common, this indicates that the image can be decoded
-         * reliably using the scanline decoder, and that rows will be output in
-         * the logical order.
-         */
-        kTopDown_SkScanlineOrder,
-
-        /*
-         * This indicates that the scanline decoder reliably outputs rows, but
-         * they will be returned in reverse order.  If the scanline format is
-         * kBottomUp, the getY() API can be used to determine the actual
-         * y-coordinate of the next output row, but the client is not forced
-         * to take advantage of this, given that it's not too tough to keep
-         * track independently.
-         *
-         * For full image decodes, it is safe to get all of the scanlines at
-         * once, since the decoder will handle inverting the rows as it
-         * decodes.
-         *
-         * For subset decodes and sampling, it is simplest to get and skip
-         * scanlines one at a time, using the getY() API.  It is possible to
-         * ask for larger chunks at a time, but this should be used with
-         * caution.  As with full image decodes, the decoder will handle
-         * inverting the requested rows, but rows will still be delivered
-         * starting from the bottom of the image.
-         *
-         * Upside down bmps are an example.
-         */
-        kBottomUp_SkScanlineOrder,
-
-        /*
-         * This indicates that the scanline decoder reliably outputs rows, but
-         * they will not be in logical order.  If the scanline format is
-         * kOutOfOrder, the getY() API should be used to determine the actual
-         * y-coordinate of the next output row.
-         *
-         * For this scanline ordering, it is advisable to get and skip
-         * scanlines one at a time.
-         *
-         * Interlaced gifs are an example.
-         */
-        kOutOfOrder_SkScanlineOrder,
-
-        /*
-         * Indicates that the entire image must be decoded in order to output
-         * any amount of scanlines.  In this case, it is a REALLY BAD IDEA to
-         * request scanlines 1-by-1 or in small chunks.  The client should
-         * determine which scanlines are needed and ask for all of them in
-         * a single call to getScanlines().
-         *
-         * Interlaced pngs are an example.
-         */
-        kNone_SkScanlineOrder,
-    };
-
-    /**
-     *  An enum representing the order in which scanlines will be returned by
-     *  the scanline decoder.
-     */
-    SkScanlineOrder getScanlineOrder() const { return this->onGetScanlineOrder(); }
-
-    /**
-     *  Returns the y-coordinate of the next row to be returned by the scanline
-     *  decoder.  This will be overridden in the case of
-     *  kOutOfOrder_SkScanlineOrder and should be unnecessary in the case of
-     *  kNone_SkScanlineOrder.
-     */
-    int getY() const {
-        SkASSERT(kNone_SkScanlineOrder != this->getScanlineOrder());
-        return this->onGetY();
-    }
-
-protected:
-    SkScanlineDecoder(const SkImageInfo& srcInfo)
-        : fSrcInfo(srcInfo)
-        , fDstInfo()
-        , fOptions()
-        , fCurrScanline(0) {}
-
-    virtual SkISize onGetScaledDimensions(float /* desiredScale */) {
-        // By default, scaling is not supported.
-        return this->getInfo().dimensions();
-    }
-
-    virtual SkEncodedFormat onGetEncodedFormat() const = 0;
-
-    virtual bool onReallyHasAlpha() const { return false; }
-
-    /**
-     *  Most images types will be kTopDown and will not need to override this function.
-     */
-    virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; }
-
-    /**
-     *  Most images will be kTopDown and will not need to override this function.
-     */
-    virtual int onGetY() const { return fCurrScanline; }
-
-    const SkImageInfo& dstInfo() const { return fDstInfo; }
-
-    const SkCodec::Options& options() const { return fOptions; }
-
-private:
-    const SkImageInfo   fSrcInfo;
-    SkImageInfo         fDstInfo;
-    SkCodec::Options    fOptions;
-    int                 fCurrScanline;
-
-    virtual SkCodec::Result onStart(const SkImageInfo& dstInfo,
-                                    const SkCodec::Options& options,
-                                    SkPMColor ctable[], int* ctableCount) = 0;
-
-    // Naive default version just calls onGetScanlines on temp memory.
-    virtual SkCodec::Result onSkipScanlines(int countLines) {
-        SkAutoMalloc storage(fDstInfo.minRowBytes());
-        // Note that we pass 0 to rowBytes so we continue to use the same memory.
-        // Also note that while getScanlines checks that rowBytes is big enough,
-        // onGetScanlines bypasses that check.
-        // Calling the virtual method also means we do not double count
-        // countLines.
-        return this->onGetScanlines(storage.get(), countLines, 0);
-    }
-
-    virtual SkCodec::Result onGetScanlines(void* dst, int countLines,
-                                                    size_t rowBytes) = 0;
-
-};
-#endif // SkScanlineDecoder_DEFINED
diff --git a/resources/plane_interlaced.png b/resources/plane_interlaced.png
new file mode 100644
index 0000000..3b217f2
--- /dev/null
+++ b/resources/plane_interlaced.png
Binary files differ
diff --git a/src/codec/SkBmpCodec.cpp b/src/codec/SkBmpCodec.cpp
index a55cb8c..4e801f0 100644
--- a/src/codec/SkBmpCodec.cpp
+++ b/src/codec/SkBmpCodec.cpp
@@ -262,10 +262,10 @@
     }
 
     // Check for valid dimensions from header
-    SkScanlineDecoder::SkScanlineOrder rowOrder = SkScanlineDecoder::kBottomUp_SkScanlineOrder;
+    SkCodec::SkScanlineOrder rowOrder = SkCodec::kBottomUp_SkScanlineOrder;
     if (height < 0) {
         height = -height;
-        rowOrder = SkScanlineDecoder::kTopDown_SkScanlineOrder;
+        rowOrder = SkCodec::kTopDown_SkScanlineOrder;
     }
     // The height field for bmp in ico is double the actual height because they
     // contain an XOR mask followed by an AND mask
@@ -531,7 +531,7 @@
 }
 
 SkBmpCodec::SkBmpCodec(const SkImageInfo& info, SkStream* stream,
-        uint16_t bitsPerPixel, SkScanlineDecoder::SkScanlineOrder rowOrder)
+        uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder)
     : INHERITED(info, stream)
     , fBitsPerPixel(bitsPerPixel)
     , fRowOrder(rowOrder)
@@ -541,11 +541,11 @@
     return SkBmpCodec::ReadHeader(this->stream(), this->inIco(), nullptr);
 }
 
-int32_t SkBmpCodec::getDstRow(int32_t y, int32_t height) {
-    if (SkScanlineDecoder::kTopDown_SkScanlineOrder == fRowOrder) {
+int32_t SkBmpCodec::getDstRow(int32_t y, int32_t height) const {
+    if (SkCodec::kTopDown_SkScanlineOrder == fRowOrder) {
         return y;
     }
-    SkASSERT(SkScanlineDecoder::kBottomUp_SkScanlineOrder == fRowOrder);
+    SkASSERT(SkCodec::kBottomUp_SkScanlineOrder == fRowOrder);
     return height - y - 1;
 }
 
@@ -557,7 +557,7 @@
  * filling at the top of the image.
  */
 void* SkBmpCodec::getDstStartRow(void* dst, size_t dstRowBytes, int32_t y) const {
-    return (SkScanlineDecoder::kTopDown_SkScanlineOrder == fRowOrder) ?
+    return (SkCodec::kTopDown_SkScanlineOrder == fRowOrder) ?
             SkTAddOffset<void*>(dst, y * dstRowBytes) : dst;
 }
 
@@ -574,71 +574,36 @@
     return numColors;
 }
 
-/*
- * Scanline decoder for bmps
- */
-class SkBmpScanlineDecoder : public SkScanlineDecoder {
-public:
-    SkBmpScanlineDecoder(SkBmpCodec* codec)
-        : INHERITED(codec->getInfo())
-        , fCodec(codec)
-    {}
-
-    SkEncodedFormat onGetEncodedFormat() const override {
-        return kBMP_SkEncodedFormat;
+SkCodec::Result SkBmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
+        const SkCodec::Options& options, SkPMColor inputColorPtr[], int* inputColorCount) {
+    if (!this->rewindIfNeeded()) {
+        return kCouldNotRewind;
     }
-
-    SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options& options,
-                            SkPMColor inputColorPtr[], int* inputColorCount) override {
-        if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
+    if (options.fSubset) {
+        // Subsets are not supported.
+        return kUnimplemented;
+    }
+    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
+        if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
+            return SkCodec::kInvalidScale;
         }
-        if (options.fSubset) {
-            // Subsets are not supported.
-            return SkCodec::kUnimplemented;
-        }
-        if (dstInfo.dimensions() != this->getInfo().dimensions()) {
-            if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return SkCodec::kInvalidScale;
-            }
-        }
-        if (!conversion_possible(dstInfo, this->getInfo())) {
-            SkCodecPrintf("Error: cannot convert input type to output type.\n");
-            return SkCodec::kInvalidConversion;
-        }
-
-        return fCodec->prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount);
+    }
+    if (!conversion_possible(dstInfo, this->getInfo())) {
+        SkCodecPrintf("Error: cannot convert input type to output type.\n");
+        return kInvalidConversion;
     }
 
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        // Create a new image info representing the portion of the image to decode
-        SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count);
+    return prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount);
+}
 
-        // Decode the requested rows
-        return fCodec->decodeRows(rowInfo, dst, rowBytes, this->options());
-    }
+SkCodec::Result SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
+    // Create a new image info representing the portion of the image to decode
+    SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count);
 
-    SkScanlineOrder onGetScanlineOrder() const override {
-        return fCodec->fRowOrder;
-    }
+    // Decode the requested rows
+    return this->decodeRows(rowInfo, dst, rowBytes, this->options());
+}
 
-    int onGetY() const override {
-        return fCodec->getDstRow(this->INHERITED::onGetY(), this->dstInfo().height());
-    }
-
-    // TODO(msarett): Override default skipping with something more clever.
-
-private:
-    SkAutoTDelete<SkBmpCodec> fCodec;
-
-    typedef SkScanlineDecoder INHERITED;
-};
-
-SkScanlineDecoder* SkBmpCodec::NewSDFromStream(SkStream* stream) {
-    SkAutoTDelete<SkBmpCodec> codec(static_cast<SkBmpCodec*>(SkBmpCodec::NewFromStream(stream)));
-    if (!codec) {
-        return NULL;
-    }
-
-    return new SkBmpScanlineDecoder(codec.detach());
+int SkBmpCodec::onNextScanline() const {
+    return this->getDstRow(this->INHERITED::onNextScanline(), this->dstInfo().height());
 }
diff --git a/src/codec/SkBmpCodec.h b/src/codec/SkBmpCodec.h
index 4b2cd2a..ea6f789 100644
--- a/src/codec/SkBmpCodec.h
+++ b/src/codec/SkBmpCodec.h
@@ -11,7 +11,6 @@
 #include "SkColorTable.h"
 #include "SkImageInfo.h"
 #include "SkMaskSwizzler.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 #include "SkSwizzler.h"
 #include "SkTypes.h"
@@ -41,17 +40,10 @@
      */
     static SkCodec* NewFromIco(SkStream*);
 
-    /*
-     * Assumes IsBmp was called and returned true
-     * Creates a bmp scanline decoder
-     * Takes ownership of the stream
-     */
-    static SkScanlineDecoder* NewSDFromStream(SkStream* stream);
-
 protected:
 
     SkBmpCodec(const SkImageInfo& info, SkStream* stream, uint16_t bitsPerPixel,
-            SkScanlineDecoder::SkScanlineOrder rowOrder);
+            SkCodec::SkScanlineOrder rowOrder);
 
     SkEncodedFormat onGetEncodedFormat() const override { return kBMP_SkEncodedFormat; }
 
@@ -87,7 +79,7 @@
      *               when we want to decode the full or one when we are
      *               sampling.
      */
-    int32_t getDstRow(int32_t y, int32_t height);
+    int32_t getDstRow(int32_t y, int32_t height) const;
 
     /*
      * Get the destination row to start filling from
@@ -107,7 +99,7 @@
      * Accessors used by subclasses
      */
     uint16_t bitsPerPixel() const { return fBitsPerPixel; }
-    SkScanlineDecoder::SkScanlineOrder rowOrder() const { return fRowOrder; }
+    SkScanlineOrder onGetScanlineOrder() const override { return fRowOrder; }
 
     /*
      * To be overriden by bmp subclasses, which provide unique implementations.
@@ -153,10 +145,17 @@
     virtual Result decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes,
             const Options& opts) = 0;
 
-    const uint16_t                           fBitsPerPixel;
-    const SkScanlineDecoder::SkScanlineOrder fRowOrder;
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options&,
+            SkPMColor inputColorPtr[], int* inputColorCount) override;
 
-    friend class SkBmpScanlineDecoder;
+    Result onGetScanlines(void* dst, int count, size_t rowBytes) override;
+
+    int onNextScanline() const override;
+
+    // TODO(msarett): Override default skipping with something more clever.
+
+    const uint16_t          fBitsPerPixel;
+    const SkScanlineOrder   fRowOrder;
 
     typedef SkCodec INHERITED;
 };
diff --git a/src/codec/SkBmpMaskCodec.cpp b/src/codec/SkBmpMaskCodec.cpp
index 3036f33..2e88d54 100644
--- a/src/codec/SkBmpMaskCodec.cpp
+++ b/src/codec/SkBmpMaskCodec.cpp
@@ -14,7 +14,7 @@
  */
 SkBmpMaskCodec::SkBmpMaskCodec(const SkImageInfo& info, SkStream* stream,
                                uint16_t bitsPerPixel, SkMasks* masks,
-                               SkScanlineDecoder::SkScanlineOrder rowOrder)
+                               SkCodec::SkScanlineOrder rowOrder)
     : INHERITED(info, stream, bitsPerPixel, rowOrder)
     , fMasks(masks)
     , fMaskSwizzler(nullptr)
diff --git a/src/codec/SkBmpMaskCodec.h b/src/codec/SkBmpMaskCodec.h
index 58b2e6b..0b12912 100644
--- a/src/codec/SkBmpMaskCodec.h
+++ b/src/codec/SkBmpMaskCodec.h
@@ -30,7 +30,7 @@
      */
     SkBmpMaskCodec(const SkImageInfo& srcInfo, SkStream* stream,
             uint16_t bitsPerPixel, SkMasks* masks,
-            SkScanlineDecoder::SkScanlineOrder rowOrder);
+            SkCodec::SkScanlineOrder rowOrder);
 
 protected:
 
diff --git a/src/codec/SkBmpRLECodec.cpp b/src/codec/SkBmpRLECodec.cpp
index 58c0605..79dd633 100644
--- a/src/codec/SkBmpRLECodec.cpp
+++ b/src/codec/SkBmpRLECodec.cpp
@@ -9,7 +9,6 @@
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
 #include "SkScaledCodec.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 
 /*
@@ -19,7 +18,7 @@
 SkBmpRLECodec::SkBmpRLECodec(const SkImageInfo& info, SkStream* stream,
                              uint16_t bitsPerPixel, uint32_t numColors,
                              uint32_t bytesPerColor, uint32_t offset,
-                             SkScanlineDecoder::SkScanlineOrder rowOrder,
+                             SkCodec::SkScanlineOrder rowOrder,
                              size_t RLEBytes)
     : INHERITED(info, stream, bitsPerPixel, rowOrder)
     , fColorTable(nullptr)
diff --git a/src/codec/SkBmpRLECodec.h b/src/codec/SkBmpRLECodec.h
index aa5b061..ea2c2b6 100644
--- a/src/codec/SkBmpRLECodec.h
+++ b/src/codec/SkBmpRLECodec.h
@@ -36,7 +36,7 @@
      */
     SkBmpRLECodec(const SkImageInfo& srcInfo, SkStream* stream,
             uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor,
-            uint32_t offset, SkScanlineDecoder::SkScanlineOrder rowOrder,
+            uint32_t offset, SkCodec::SkScanlineOrder rowOrder,
             size_t RLEBytes);
 
 protected:
diff --git a/src/codec/SkBmpStandardCodec.cpp b/src/codec/SkBmpStandardCodec.cpp
index 59bc917..02f2d42 100644
--- a/src/codec/SkBmpStandardCodec.cpp
+++ b/src/codec/SkBmpStandardCodec.cpp
@@ -8,7 +8,6 @@
 #include "SkBmpStandardCodec.h"
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 
 /*
@@ -18,7 +17,7 @@
 SkBmpStandardCodec::SkBmpStandardCodec(const SkImageInfo& info, SkStream* stream,
                                        uint16_t bitsPerPixel, uint32_t numColors,
                                        uint32_t bytesPerColor, uint32_t offset,
-                                       SkScanlineDecoder::SkScanlineOrder rowOrder, bool inIco)
+                                       SkCodec::SkScanlineOrder rowOrder, bool inIco)
     : INHERITED(info, stream, bitsPerPixel, rowOrder)
     , fColorTable(nullptr)
     , fNumColors(this->computeNumColors(numColors))
diff --git a/src/codec/SkBmpStandardCodec.h b/src/codec/SkBmpStandardCodec.h
index fcc246f..0d81cbf 100644
--- a/src/codec/SkBmpStandardCodec.h
+++ b/src/codec/SkBmpStandardCodec.h
@@ -37,7 +37,7 @@
      */
     SkBmpStandardCodec(const SkImageInfo& srcInfo, SkStream* stream,
             uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor,
-            uint32_t offset, SkScanlineDecoder::SkScanlineOrder rowOrder,
+            uint32_t offset, SkCodec::SkScanlineOrder rowOrder,
             bool isIco);
 
 protected:
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index d12de21..97bcf3e 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -76,9 +76,12 @@
 }
 
 SkCodec::SkCodec(const SkImageInfo& info, SkStream* stream)
-    : fInfo(info)
+    : fSrcInfo(info)
     , fStream(stream)
     , fNeedsRewind(false)
+    , fDstInfo()
+    , fOptions()
+    , fCurrScanline(-1)
 {}
 
 SkCodec::~SkCodec() {}
@@ -92,6 +95,9 @@
         return true;
     }
 
+    // startScanlineDecode will need to be called before decoding scanlines.
+    fCurrScanline = -1;
+
     if (!fStream->rewind()) {
         return false;
     }
@@ -148,3 +154,75 @@
 SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) {
     return this->getPixels(info, pixels, rowBytes, nullptr, nullptr, nullptr);
 }
+
+SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo,
+        const SkCodec::Options* options, SkPMColor ctable[], int* ctableCount) {
+    // Reset fCurrScanline in case of failure.
+    fCurrScanline = -1;
+    // Ensure that valid color ptrs are passed in for kIndex8 color type
+    if (kIndex_8_SkColorType == dstInfo.colorType()) {
+        if (nullptr == ctable || nullptr == ctableCount) {
+            return SkCodec::kInvalidParameters;
+        }
+    } else {
+        if (ctableCount) {
+            *ctableCount = 0;
+        }
+        ctableCount = nullptr;
+        ctable = nullptr;
+    }
+
+    // Set options.
+    Options optsStorage;
+    if (nullptr == options) {
+        options = &optsStorage;
+    }
+
+    const Result result = this->onStartScanlineDecode(dstInfo, *options, ctable, ctableCount);
+    if (result != SkCodec::kSuccess) {
+        return result;
+    }
+
+    fCurrScanline = 0;
+    fDstInfo = dstInfo;
+    fOptions = *options;
+    return kSuccess;
+}
+
+SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo) {
+    return this->startScanlineDecode(dstInfo, nullptr, nullptr, nullptr);
+}
+
+SkCodec::Result SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) {
+    if (fCurrScanline < 0) {
+        return kScanlineDecodingNotStarted;
+    }
+
+    SkASSERT(!fDstInfo.isEmpty());
+    if ((rowBytes < fDstInfo.minRowBytes() && countLines > 1 ) || countLines <= 0
+         || fCurrScanline + countLines > fDstInfo.height()) {
+        return kInvalidParameters;
+    }
+
+    const Result result = this->onGetScanlines(dst, countLines, rowBytes);
+    fCurrScanline += countLines;
+    return result;
+}
+
+SkCodec::Result SkCodec::skipScanlines(int countLines) {
+    if (fCurrScanline < 0) {
+        return kScanlineDecodingNotStarted;
+    }
+
+    SkASSERT(!fDstInfo.isEmpty());
+    if (fCurrScanline + countLines > fDstInfo.height()) {
+        // Arguably, we could just skip the scanlines which are remaining,
+        // and return kSuccess. We choose to return invalid so the client
+        // can catch their bug.
+        return SkCodec::kInvalidParameters;
+    }
+
+    const Result result = this->onSkipScanlines(countLines);
+    fCurrScanline += countLines;
+    return result;
+}
diff --git a/src/codec/SkCodec_libgif.cpp b/src/codec/SkCodec_libgif.cpp
index d8889de..ad7fb5b 100644
--- a/src/codec/SkCodec_libgif.cpp
+++ b/src/codec/SkCodec_libgif.cpp
@@ -582,124 +582,94 @@
     return kSuccess;
 }
 
-// TODO (msarett): skbug.com/3582
-//                 Should we implement reallyHasAlpha?  Or should we read extension blocks in the
-//                 header?  Or should we do both?
-
-class SkGifScanlineDecoder : public SkScanlineDecoder {
-public:
-    SkGifScanlineDecoder(const SkImageInfo& srcInfo, SkGifCodec* codec)
-        : INHERITED(srcInfo)
-        , fCodec(codec)
-    {}
-
-    SkEncodedFormat onGetEncodedFormat() const override {
-        return kGIF_SkEncodedFormat;
+SkCodec::Result SkGifCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
+        const SkCodec::Options& opts, SkPMColor inputColorPtr[], int* inputColorCount) {
+    Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount,
+                                          this->options());
+    if (kSuccess != result) {
+        return result;
     }
 
-    SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options& opts,
-                            SkPMColor inputColorPtr[], int* inputColorCount) override {
-        SkCodec::Result result = fCodec->prepareToDecode(dstInfo, inputColorPtr, inputColorCount,
-                this->options());
-        if (SkCodec::kSuccess != result) {
-            return result;
+    // Check to see if scaling was requested.
+    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
+        if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
+            return gif_error("Scaling not supported.\n", SkCodec::kInvalidScale);
         }
-
-        // Check to see if scaling was requested.
-        if (dstInfo.dimensions() != this->getInfo().dimensions()) {
-            if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return gif_error("Scaling not supported.\n", SkCodec::kInvalidScale);
-            }
-        }
-
-        // Initialize the swizzler
-        if (fCodec->fFrameIsSubset) {
-            int sampleX;
-            SkScaledCodec::ComputeSampleSize(dstInfo, fCodec->getInfo(), &sampleX, NULL);
-            const SkImageInfo subsetDstInfo = dstInfo.makeWH(
-                    get_scaled_dimension(fCodec->fFrameDims.width(), sampleX),
-                    fCodec->fFrameDims.height());
-            if (SkCodec::kSuccess != fCodec->initializeSwizzler(subsetDstInfo,
-                    opts.fZeroInitialized)) {
-                return gif_error("Could not initialize swizzler.\n", SkCodec::kUnimplemented);
-            }
-        } else {
-            if (SkCodec::kSuccess != fCodec->initializeSwizzler(dstInfo, opts.fZeroInitialized)) {
-                return gif_error("Could not initialize swizzler.\n", SkCodec::kUnimplemented);
-            }
-        }
-
-        return SkCodec::kSuccess;
     }
 
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        if (fCodec->fFrameIsSubset) {
-            // Fill the requested rows
-            const SkPMColor* colorPtr = get_color_ptr(fCodec->fColorTable.get());
-            SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count, fCodec->fFillIndex,
-                    colorPtr, this->options().fZeroInitialized);
+    // Initialize the swizzler
+    if (fFrameIsSubset) {
+        int sampleX;
+        SkScaledCodec::ComputeSampleSize(dstInfo, this->getInfo(), &sampleX, NULL);
+        const SkImageInfo subsetDstInfo = dstInfo.makeWH(
+                    get_scaled_dimension(fFrameDims.width(), sampleX),
+                    fFrameDims.height());
+        if (kSuccess != this->initializeSwizzler(subsetDstInfo, opts.fZeroInitialized)) {
+            return gif_error("Could not initialize swizzler.\n", kUnimplemented);
+        }
+    } else {
+        if (kSuccess != this->initializeSwizzler(dstInfo, opts.fZeroInitialized)) {
+            return gif_error("Could not initialize swizzler.\n", kUnimplemented);
+        }
+    }
 
-            // Do nothing for rows before the image frame
-            int rowsBeforeFrame = fCodec->fFrameDims.top() - INHERITED::getY();
-            if (rowsBeforeFrame > 0) {
-                count = SkTMin(0, count - rowsBeforeFrame);
-                dst = SkTAddOffset<void>(dst, rowBytes * rowsBeforeFrame);
-            }
+    return kSuccess;
+}
 
-            // Do nothing for rows after the image frame
-            int rowsAfterFrame = INHERITED::getY() + count - fCodec->fFrameDims.bottom();
-            if (rowsAfterFrame > 0) {
-                count = SkTMin(0, count - rowsAfterFrame);
-            }
+SkCodec::Result SkGifCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
+    if (fFrameIsSubset) {
+        // Fill the requested rows
+        const SkPMColor* colorPtr = get_color_ptr(fColorTable.get());
+        SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count, fFillIndex,
+                         colorPtr, this->options().fZeroInitialized);
+
+        // Do nothing for rows before the image frame
+        // FIXME: nextScanline is not virtual, so using "INHERITED" does not change
+        // behavior. Was the intent to call this->INHERITED::onNextScanline()? Same
+        // for the next call down below.
+        int rowsBeforeFrame = fFrameDims.top() - this->INHERITED::nextScanline();
+        if (rowsBeforeFrame > 0) {
+            count = SkTMin(0, count - rowsBeforeFrame);
+            dst = SkTAddOffset<void>(dst, rowBytes * rowsBeforeFrame);
+        }
+
+        // Do nothing for rows after the image frame
+        int rowsAfterFrame = this->INHERITED::nextScanline() + count - fFrameDims.bottom();
+        if (rowsAfterFrame > 0) {
+            count = SkTMin(0, count - rowsAfterFrame);
+        }
 
             // Adjust dst pointer for left offset
-            dst = SkTAddOffset<void>(dst, SkColorTypeBytesPerPixel(
-                    this->dstInfo().colorType()) * fCodec->fFrameDims.left());
-        }
-
-        for (int i = 0; i < count; i++) {
-            if (SkCodec::kSuccess != fCodec->readRow()) {
-                const SkPMColor* colorPtr = get_color_ptr(fCodec->fColorTable.get());
-                SkSwizzler::Fill(dst, this->dstInfo(), rowBytes,
-                        count - i, fCodec->fFillIndex, colorPtr,
-                        this->options().fZeroInitialized);
-                return gif_error("Could not decode line\n", SkCodec::kIncompleteInput);
-            }
-            fCodec->fSwizzler->swizzle(dst, fCodec->fSrcBuffer.get());
-            dst = SkTAddOffset<void>(dst, rowBytes);
-        }
-        return SkCodec::kSuccess;
+        int bpp = SkColorTypeBytesPerPixel(this->dstInfo().colorType()) * fFrameDims.left();
+        dst = SkTAddOffset<void>(dst, bpp);
     }
 
-    SkScanlineOrder onGetScanlineOrder() const override {
-        if (fCodec->fGif->Image.Interlace) {
-            return kOutOfOrder_SkScanlineOrder;
-        } else {
-            return kTopDown_SkScanlineOrder;
+    for (int i = 0; i < count; i++) {
+        if (kSuccess != this->readRow()) {
+            const SkPMColor* colorPtr = get_color_ptr(fColorTable.get());
+            SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count - i, fFillIndex, colorPtr,
+                             this->options().fZeroInitialized);
+            return gif_error("Could not decode line\n", SkCodec::kIncompleteInput);
         }
+        fSwizzler->swizzle(dst, fSrcBuffer.get());
+        dst = SkTAddOffset<void>(dst, rowBytes);
     }
-
-    int onGetY() const override {
-        if (fCodec->fGif->Image.Interlace) {
-            return get_output_row_interlaced(INHERITED::onGetY(), this->dstInfo().height());
-        } else {
-            return INHERITED::onGetY();
-        }
-    }
-
-private:
-    SkAutoTDelete<SkGifCodec>   fCodec;
-
-    typedef SkScanlineDecoder INHERITED;
-};
-
-SkScanlineDecoder* SkGifCodec::NewSDFromStream(SkStream* stream) {
-    SkAutoTDelete<SkGifCodec> codec (static_cast<SkGifCodec*>(SkGifCodec::NewFromStream(stream)));
-    if (!codec) {
-        return NULL;
-    }
-
-    const SkImageInfo& srcInfo = codec->getInfo();
-
-    return new SkGifScanlineDecoder(srcInfo, codec.detach());
+    return SkCodec::kSuccess;
 }
+
+SkCodec::SkScanlineOrder SkGifCodec::onGetScanlineOrder() const {
+    if (fGif->Image.Interlace) {
+        return kOutOfOrder_SkScanlineOrder;
+    } else {
+        return kTopDown_SkScanlineOrder;
+    }
+}
+
+int SkGifCodec::onNextScanline() const {
+    if (fGif->Image.Interlace) {
+        return get_output_row_interlaced(this->INHERITED::onNextScanline(), this->dstInfo().height());
+    } else {
+        return this->INHERITED::onNextScanline();
+    }
+}
+
diff --git a/src/codec/SkCodec_libgif.h b/src/codec/SkCodec_libgif.h
index 8845536..3999c41 100644
--- a/src/codec/SkCodec_libgif.h
+++ b/src/codec/SkCodec_libgif.h
@@ -8,7 +8,6 @@
 #include "SkCodec.h"
 #include "SkColorTable.h"
 #include "SkImageInfo.h"
-#include "SkScanlineDecoder.h"
 #include "SkSwizzler.h"
 
 #include "gif_lib.h"
@@ -33,8 +32,6 @@
      */
     static SkCodec* NewFromStream(SkStream*);
 
-    static SkScanlineDecoder* NewSDFromStream(SkStream* stream);
-
 protected:
 
     /*
@@ -83,7 +80,7 @@
      * @param transIndex This call will set the transparent index based on the
      *                   extension data.
      */
-     static SkCodec::Result ReadUpToFirstImage(GifFileType* gif, uint32_t* transIndex);
+     static Result ReadUpToFirstImage(GifFileType* gif, uint32_t* transIndex);
 
      /*
       * A gif may contain many image frames, all of different sizes.
@@ -114,7 +111,7 @@
     * Checks for invalid inputs and calls rewindIfNeeded(), setFramDimensions(), and
     * initializeColorTable() in the proper sequence.
     */
-    SkCodec::Result prepareToDecode(const SkImageInfo& dstInfo, SkPMColor* inputColorPtr,
+    Result prepareToDecode(const SkImageInfo& dstInfo, SkPMColor* inputColorPtr,
             int* inputColorCount, const Options& opts);
 
     /*
@@ -125,14 +122,22 @@
      *                 indicated in the header.
      * @param zeroInit Indicates if destination memory is zero initialized.
      */
-    SkCodec::Result initializeSwizzler(const SkImageInfo& dstInfo,
-            ZeroInitialized zeroInit);
+    Result initializeSwizzler(const SkImageInfo& dstInfo, ZeroInitialized zeroInit);
 
     /*
      * @return kSuccess if the read is successful and kIncompleteInput if the
      *         read fails.
      */
-    SkCodec::Result readRow();
+    Result readRow();
+
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& opts,
+                   SkPMColor inputColorPtr[], int* inputColorCount) override;
+
+    Result onGetScanlines(void* dst, int count, size_t rowBytes) override;
+
+    SkScanlineOrder onGetScanlineOrder() const override;
+
+    int onNextScanline() const override;
 
     /*
      * This function cleans up the gif object after the decode completes
@@ -168,7 +173,5 @@
     SkAutoTDelete<SkSwizzler>               fSwizzler;
     SkAutoTUnref<SkColorTable>              fColorTable;
 
-    friend class SkGifScanlineDecoder;
-
     typedef SkCodec INHERITED;
 };
diff --git a/src/codec/SkCodec_libpng.cpp b/src/codec/SkCodec_libpng.cpp
index 2ba2d5b..faba79f 100644
--- a/src/codec/SkCodec_libpng.cpp
+++ b/src/codec/SkCodec_libpng.cpp
@@ -12,7 +12,6 @@
 #include "SkBitmap.h"
 #include "SkMath.h"
 #include "SkScaledCodec.h"
-#include "SkScanlineDecoder.h"
 #include "SkSize.h"
 #include "SkStream.h"
 #include "SkSwizzler.h"
@@ -163,7 +162,10 @@
         palette++;
     }
 
-    fReallyHasAlpha = transLessThanFF < 0;
+    if (transLessThanFF >= 0) {
+        // No transparent colors were found.
+        fAlphaState = kOpaque_AlphaState;
+    }
 
     for (; index < numPalette; index++) {
         *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
@@ -216,7 +218,8 @@
 // png_destroy_read_struct. If it returns false, the passed in fields (except
 // stream) are unchanged.
 static bool read_header(SkStream* stream, png_structp* png_ptrp,
-                        png_infop* info_ptrp, SkImageInfo* imageInfo, int* bitDepthPtr) {
+                        png_infop* info_ptrp, SkImageInfo* imageInfo,
+                        int* bitDepthPtr, int* numberPassesPtr) {
     // The image is known to be a PNG. Decode enough to know the SkImageInfo.
     png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
                                                  sk_error_fn, sk_warning_fn);
@@ -333,6 +336,11 @@
             SkASSERT(false);
     }
 
+    int numberPasses = png_set_interlace_handling(png_ptr);
+    if (numberPassesPtr) {
+        *numberPassesPtr = numberPasses;
+    }
+
     // FIXME: Also need to check for sRGB (skbug.com/3471).
 
     if (imageInfo) {
@@ -349,29 +357,21 @@
     return true;
 }
 
-SkCodec* SkPngCodec::NewFromStream(SkStream* stream) {
-    SkAutoTDelete<SkStream> streamDeleter(stream);
-    png_structp png_ptr;
-    png_infop info_ptr;
-    SkImageInfo imageInfo;
-    int bitDepth;
-    if (read_header(stream, &png_ptr, &info_ptr, &imageInfo, &bitDepth)) {
-        return new SkPngCodec(imageInfo, streamDeleter.detach(), png_ptr, info_ptr, bitDepth);
-    }
-    return nullptr;
-}
-
-#define INVALID_NUMBER_PASSES -1
 SkPngCodec::SkPngCodec(const SkImageInfo& info, SkStream* stream,
-                       png_structp png_ptr, png_infop info_ptr, int bitDepth)
+                       png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses)
     : INHERITED(info, stream)
     , fPng_ptr(png_ptr)
     , fInfo_ptr(info_ptr)
     , fSrcConfig(SkSwizzler::kUnknown)
-    , fNumberPasses(INVALID_NUMBER_PASSES)
-    , fReallyHasAlpha(false)
+    , fNumberPasses(numberPasses)
     , fBitDepth(bitDepth)
-{}
+{
+    if (info.alphaType() == kOpaque_SkAlphaType) {
+        fAlphaState = kOpaque_AlphaState;
+    } else {
+        fAlphaState = kUnknown_AlphaState;
+    }
+}
 
 SkPngCodec::~SkPngCodec() {
     this->destroyReadStruct();
@@ -401,13 +401,9 @@
         SkCodecPrintf("setjmp long jump!\n");
         return kInvalidInput;
     }
-    fNumberPasses = png_set_interlace_handling(fPng_ptr);
     png_read_update_info(fPng_ptr, fInfo_ptr);  
 
-    // Set to the default before calling decodePalette, which may change it.
-    fReallyHasAlpha = false;
-
-    //srcColorType was determined in readHeader() which determined png color type
+    //srcColorType was determined in read_header() which determined png color type
     const SkColorType srcColorType = this->getInfo().colorType();
 
     switch (srcColorType) {
@@ -459,7 +455,7 @@
 
     png_structp png_ptr;
     png_infop info_ptr;
-    if (!read_header(this->stream(), &png_ptr, &info_ptr, nullptr, nullptr)) {
+    if (!read_header(this->stream(), &png_ptr, &info_ptr, nullptr, nullptr, nullptr)) {
         return false;
     }
 
@@ -498,7 +494,8 @@
         return kInvalidInput;
     }
 
-    SkASSERT(fNumberPasses != INVALID_NUMBER_PASSES);
+    bool hasAlpha = false;
+    // FIXME: We could split these out based on subclass.
     SkAutoMalloc storage;
     void* dstRow = dst;
     if (fNumberPasses > 1) {
@@ -522,7 +519,7 @@
         // Now swizzle it.
         uint8_t* srcRow = base;
         for (int y = 0; y < height; y++) {
-            fReallyHasAlpha |= !SkSwizzler::IsOpaque(fSwizzler->swizzle(dstRow, srcRow));
+            hasAlpha |= !SkSwizzler::IsOpaque(fSwizzler->swizzle(dstRow, srcRow));
             dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
             srcRow += srcRowBytes;
         }
@@ -531,11 +528,18 @@
         uint8_t* srcRow = static_cast<uint8_t*>(storage.get());
         for (int y = 0; y < requestedInfo.height(); y++) {
             png_read_rows(fPng_ptr, &srcRow, png_bytepp_NULL, 1);
-            fReallyHasAlpha |= !SkSwizzler::IsOpaque(fSwizzler->swizzle(dstRow, srcRow));
+            // FIXME: Only call IsOpaque once, outside the loop. Same for onGetScanlines.
+            hasAlpha |= !SkSwizzler::IsOpaque(fSwizzler->swizzle(dstRow, srcRow));
             dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
         }
     }
 
+    if (hasAlpha) {
+        fAlphaState = kHasAlpha_AlphaState;
+    } else {
+        fAlphaState = kOpaque_AlphaState;
+    }
+
     // FIXME: do we need substituteTranspColor? Note that we cannot do it for
     // scanline decoding, but we could do it here. Alternatively, we could do
     // it as we go, instead of in post-processing like SkPNGImageDecoder.
@@ -547,136 +551,170 @@
 
     // read rest of file, and get additional comment and time chunks in info_ptr
     png_read_end(fPng_ptr, fInfo_ptr);
+
     return kSuccess;
 }
 
-class SkPngScanlineDecoder : public SkScanlineDecoder {
+bool SkPngCodec::onReallyHasAlpha() const {
+    switch (fAlphaState) {
+        case kOpaque_AlphaState:
+            return false;
+        case kUnknown_AlphaState:
+            // Maybe the subclass knows?
+            return this->alphaInScanlineDecode() == kHasAlpha_AlphaState;
+        case kHasAlpha_AlphaState:
+            switch (this->alphaInScanlineDecode()) {
+                case kUnknown_AlphaState:
+                    // Scanline decoder must not have been used. Return our knowledge.
+                    return true;
+                case kOpaque_AlphaState:
+                    // Scanline decoder was used, and did not find alpha in its subset.
+                    return false;
+                case kHasAlpha_AlphaState:
+                    return true;
+            }
+    }
+
+    // All valid AlphaStates have been covered, so this should not be reached.
+    SkASSERT(false);
+    return true;
+}
+
+// Subclass of SkPngCodec which supports scanline decoding
+class SkPngScanlineDecoder : public SkPngCodec {
 public:
-    SkPngScanlineDecoder(const SkImageInfo& srcInfo, SkPngCodec* codec)
-        : INHERITED(srcInfo)
-        , fCodec(codec)
-        , fHasAlpha(false)
+    SkPngScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream,
+            png_structp png_ptr, png_infop info_ptr, int bitDepth)
+        : INHERITED(srcInfo, stream, png_ptr, info_ptr, bitDepth, 1)
+        , fSrcRow(nullptr)
+        , fAlphaState(kUnknown_AlphaState)
     {}
 
-    SkCodec::Result onStart(const SkImageInfo& dstInfo,
-                            const SkCodec::Options& options,
-                            SkPMColor ctable[], int* ctableCount) override {
-        if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
+            SkPMColor ctable[], int* ctableCount) override {
+        if (!this->rewindIfNeeded()) {
+            return kCouldNotRewind;
         }
 
         if (!conversion_possible(dstInfo, this->getInfo())) {
-            return SkCodec::kInvalidConversion;
+            return kInvalidConversion;
         }
 
         // Check to see if scaling was requested.
         if (dstInfo.dimensions() != this->getInfo().dimensions()) {
             if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return SkCodec::kInvalidScale;   
+                return kInvalidScale;   
             }
         }
 
-        const SkCodec::Result result = fCodec->initializeSwizzler(dstInfo, options, ctable,
-                                                                  ctableCount);
-        if (result != SkCodec::kSuccess) {
+        const Result result = this->initializeSwizzler(dstInfo, options, ctable,
+                                                       ctableCount);
+        if (result != kSuccess) {
             return result;
         }
 
-        fHasAlpha = false;
-        fStorage.reset(this->getInfo().width() * SkSwizzler::BytesPerPixel(fCodec->fSrcConfig));
+        fAlphaState = kUnknown_AlphaState;
+        fStorage.reset(this->getInfo().width() * SkSwizzler::BytesPerPixel(this->srcConfig()));
         fSrcRow = static_cast<uint8_t*>(fStorage.get());
 
-        return SkCodec::kSuccess;
+        return kSuccess;
     }
 
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        if (setjmp(png_jmpbuf(fCodec->fPng_ptr))) {
+    Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
+        if (setjmp(png_jmpbuf(this->png_ptr()))) {
             SkCodecPrintf("setjmp long jump!\n");
-            return SkCodec::kInvalidInput;
+            return kInvalidInput;
         }
 
         void* dstRow = dst;
+        bool hasAlpha = false;
         for (int i = 0; i < count; i++) {
-            png_read_rows(fCodec->fPng_ptr, &fSrcRow, png_bytepp_NULL, 1);
-            fHasAlpha |= !SkSwizzler::IsOpaque(fCodec->fSwizzler->swizzle(dstRow, fSrcRow));
+            png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1);
+            hasAlpha |= !SkSwizzler::IsOpaque(this->swizzler()->swizzle(dstRow, fSrcRow));
             dstRow = SkTAddOffset<void>(dstRow, rowBytes);
         }
-        return SkCodec::kSuccess;
+
+        if (hasAlpha) {
+            fAlphaState = kHasAlpha_AlphaState;
+        } else {
+            if (kUnknown_AlphaState == fAlphaState) {
+                fAlphaState = kOpaque_AlphaState;
+            }
+            // Otherwise, the AlphaState is unchanged.
+        }
+
+        return kSuccess;
     }
 
-    SkCodec::Result onSkipScanlines(int count) override {
+    Result onSkipScanlines(int count) override {
         // FIXME: Could we use the return value of setjmp to specify the type of
         // error?
-        if (setjmp(png_jmpbuf(fCodec->fPng_ptr))) {
+        if (setjmp(png_jmpbuf(this->png_ptr()))) {
             SkCodecPrintf("setjmp long jump!\n");
-            return SkCodec::kInvalidInput;
+            return kInvalidInput;
         }
         //there is a potential tradeoff of memory vs speed created by putting this in a loop. 
         //calling png_read_rows in a loop is insignificantly slower than calling it once with count 
         //as png_read_rows has it's own loop which calls png_read_row count times.
         for (int i = 0; i < count; i++) {
-            png_read_rows(fCodec->fPng_ptr, &fSrcRow, png_bytepp_NULL, 1);
+            png_read_rows(this->png_ptr(), &fSrcRow, png_bytepp_NULL, 1);
         }
         return SkCodec::kSuccess;
     }
 
-    bool onReallyHasAlpha() const override { return fHasAlpha; }
-
-    SkEncodedFormat onGetEncodedFormat() const override { 
-        return kPNG_SkEncodedFormat;
+    AlphaState alphaInScanlineDecode() const override {
+        return fAlphaState;
     }
 
-
 private:
-    SkAutoTDelete<SkPngCodec>   fCodec;
-    bool                        fHasAlpha;
+    AlphaState                  fAlphaState;
     SkAutoMalloc                fStorage;
     uint8_t*                    fSrcRow;
 
-    typedef SkScanlineDecoder INHERITED;
+    typedef SkPngCodec INHERITED;
 };
 
 
-class SkPngInterlacedScanlineDecoder : public SkScanlineDecoder {
+class SkPngInterlacedScanlineDecoder : public SkPngCodec {
 public:
-    SkPngInterlacedScanlineDecoder(const SkImageInfo& srcInfo, SkPngCodec* codec)
-        : INHERITED(srcInfo)
-        , fCodec(codec)
-        , fHasAlpha(false)
-        , fCurrentRow(0)
-        , fHeight(srcInfo.height())
+    SkPngInterlacedScanlineDecoder(const SkImageInfo& srcInfo, SkStream* stream,
+            png_structp png_ptr, png_infop info_ptr, int bitDepth, int numberPasses)
+        : INHERITED(srcInfo, stream, png_ptr, info_ptr, bitDepth, numberPasses)
+        , fAlphaState(kUnknown_AlphaState)
+        , fHeight(-1)
         , fCanSkipRewind(false)
-    {}
-
-    SkCodec::Result onStart(const SkImageInfo& dstInfo,
-                            const SkCodec::Options& options,
-                            SkPMColor ctable[], int* ctableCount) override
     {
-        if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
+        SkASSERT(numberPasses != 1);
+    }
+
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
+            SkPMColor ctable[], int* ctableCount) override
+    {
+        if (!this->rewindIfNeeded()) {
+            return kCouldNotRewind;
         }
 
         if (!conversion_possible(dstInfo, this->getInfo())) {
-            return SkCodec::kInvalidConversion;    
+            return kInvalidConversion;    
         }
 
         // Check to see if scaling was requested.
         if (dstInfo.dimensions() != this->getInfo().dimensions()) {
             if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return SkCodec::kInvalidScale;
+                return kInvalidScale;
             }
         }
 
-        const SkCodec::Result result = fCodec->initializeSwizzler(dstInfo, options, ctable,
-                                                                  ctableCount);
-        if (result != SkCodec::kSuccess) {
+        const Result result = this->initializeSwizzler(dstInfo, options, ctable,
+                                                       ctableCount);
+        if (result != kSuccess) {
             return result;
         }
 
-        fHasAlpha = false;
-        fCurrentRow = 0;
+        fAlphaState = kUnknown_AlphaState;
         fHeight = dstInfo.height();
-        fSrcRowBytes = this->getInfo().width() * SkSwizzler::BytesPerPixel(fCodec->fSrcConfig);
+        // FIXME: This need not be called on a second call to onStartScanlineDecode.
+        fSrcRowBytes = this->getInfo().width() * SkSwizzler::BytesPerPixel(this->srcConfig());
         fGarbageRow.reset(fSrcRowBytes);
         fGarbageRowPtr = static_cast<uint8_t*>(fGarbageRow.get());
         fCanSkipRewind = true;
@@ -684,73 +722,90 @@
         return SkCodec::kSuccess;
     }
 
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override {
+    Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override {
         // rewind stream if have previously called onGetScanlines,
         // since we need entire progressive image to get scanlines
         if (fCanSkipRewind) {
-            // We already rewound in onStart, so there is no reason to rewind.
+            // We already rewound in onStartScanlineDecode, so there is no reason to rewind.
             // Next time onGetScanlines is called, we will need to rewind.
             fCanSkipRewind = false;
-        } else if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
+        } else {
+            // rewindIfNeeded resets fCurrScanline, since it assumes that start
+            // needs to be called again before scanline decoding. PNG scanline
+            // decoding is the exception, since it needs to rewind between
+            // calls to getScanlines. Keep track of fCurrScanline, to undo the
+            // reset.
+            const int currScanline = this->onNextScanline();
+            // This method would never be called if currScanline is -1
+            SkASSERT(currScanline != -1);
+
+            if (!this->rewindIfNeeded()) {
+                return kCouldNotRewind;
+            }
+            this->updateNextScanline(currScanline);
         }
 
-        if (setjmp(png_jmpbuf(fCodec->fPng_ptr))) {
+        if (setjmp(png_jmpbuf(this->png_ptr()))) {
             SkCodecPrintf("setjmp long jump!\n");
-            return SkCodec::kInvalidInput;
+            return kInvalidInput;
         }
-        const int number_passes = png_set_interlace_handling(fCodec->fPng_ptr);
         SkAutoMalloc storage(count * fSrcRowBytes);
         uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
         uint8_t* srcRow;
-        for (int i = 0; i < number_passes; i++) {
-            //read rows we planned to skip into garbage row
-            for (int y = 0; y < fCurrentRow; y++){
-                png_read_rows(fCodec->fPng_ptr, &fGarbageRowPtr, png_bytepp_NULL, 1);
+        const int startRow = this->onNextScanline();
+        for (int i = 0; i < this->numberPasses(); i++) {
+            // read rows we planned to skip into garbage row
+            for (int y = 0; y < startRow; y++){
+                png_read_rows(this->png_ptr(), &fGarbageRowPtr, png_bytepp_NULL, 1);
             }
-            //read rows we care about into buffer
+            // read rows we care about into buffer
             srcRow = storagePtr;
             for (int y = 0; y < count; y++) {
-                png_read_rows(fCodec->fPng_ptr, &srcRow, png_bytepp_NULL, 1);
+                png_read_rows(this->png_ptr(), &srcRow, png_bytepp_NULL, 1);
                 srcRow += fSrcRowBytes;
             }
-            //read rows we don't want into garbage buffer
-            for (int y = 0; y < fHeight - fCurrentRow - count; y++) {
-                png_read_rows(fCodec->fPng_ptr, &fGarbageRowPtr, png_bytepp_NULL, 1);
+            // read rows we don't want into garbage buffer
+            for (int y = 0; y < fHeight - startRow - count; y++) {
+                png_read_rows(this->png_ptr(), &fGarbageRowPtr, png_bytepp_NULL, 1);
             }
         }
         //swizzle the rows we care about
         srcRow = storagePtr;
         void* dstRow = dst;
+        bool hasAlpha = false;
         for (int y = 0; y < count; y++) {
-            fHasAlpha |= !SkSwizzler::IsOpaque(fCodec->fSwizzler->swizzle(dstRow, srcRow));
+            hasAlpha |= !SkSwizzler::IsOpaque(this->swizzler()->swizzle(dstRow, srcRow));
             dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
             srcRow += fSrcRowBytes;
         }
-        fCurrentRow += count;
-        return SkCodec::kSuccess;
+
+        if (hasAlpha) {
+            fAlphaState = kHasAlpha_AlphaState;
+        } else {
+            if (kUnknown_AlphaState == fAlphaState) {
+                fAlphaState = kOpaque_AlphaState;
+            }
+            // Otherwise, the AlphaState is unchanged.
+        }
+
+        return kSuccess;
     }
 
     SkCodec::Result onSkipScanlines(int count) override {
-        //when ongetScanlines is called it will skip to fCurrentRow
-        fCurrentRow += count;
+        // The non-virtual version will update fCurrScanline.
         return SkCodec::kSuccess;
     }
 
-    bool onReallyHasAlpha() const override { return fHasAlpha; }
+    AlphaState alphaInScanlineDecode() const override {
+        return fAlphaState;
+    }
 
     SkScanlineOrder onGetScanlineOrder() const override {
         return kNone_SkScanlineOrder;
     }
 
-    SkEncodedFormat onGetEncodedFormat() const override { 
-        return kPNG_SkEncodedFormat;
-    }
-
 private:
-    SkAutoTDelete<SkPngCodec>   fCodec;
-    bool                        fHasAlpha;
-    int                         fCurrentRow;
+    AlphaState                  fAlphaState;
     int                         fHeight;
     size_t                      fSrcRowBytes;
     SkAutoMalloc                fGarbageRow;
@@ -759,30 +814,33 @@
     // is called whenever some action is taken that reads the stream and
     // therefore the next call will require a rewind. So it modifies a boolean
     // to note that the *next* time it is called a rewind is needed.
-    // SkPngInterlacedScanlineDecoder has an extra wrinkle - calling onStart
-    // followed by onGetScanlines does *not* require a rewind. Since
-    // rewindIfNeeded does not have this flexibility, we need to add another
-    // layer.
+    // SkPngInterlacedScanlineDecoder has an extra wrinkle - calling
+    // onStartScanlineDecode followed by onGetScanlines does *not* require a
+    // rewind. Since rewindIfNeeded does not have this flexibility, we need to
+    // add another layer.
     bool                        fCanSkipRewind;
 
-    typedef SkScanlineDecoder INHERITED;
+    typedef SkPngCodec INHERITED;
 };
 
-SkScanlineDecoder* SkPngCodec::NewSDFromStream(SkStream* stream) {
-    SkAutoTDelete<SkPngCodec> codec (static_cast<SkPngCodec*>(SkPngCodec::NewFromStream(stream)));
-    if (!codec) {
+SkCodec* SkPngCodec::NewFromStream(SkStream* stream) {
+    SkAutoTDelete<SkStream> streamDeleter(stream);
+    png_structp png_ptr;
+    png_infop info_ptr;
+    SkImageInfo imageInfo;
+    int bitDepth;
+    int numberPasses;
+
+    if (!read_header(stream, &png_ptr, &info_ptr, &imageInfo, &bitDepth, &numberPasses)) {
         return nullptr;
     }
 
-    codec->fNumberPasses = png_set_interlace_handling(codec->fPng_ptr);
-    SkASSERT(codec->fNumberPasses != INVALID_NUMBER_PASSES);
-
-    const SkImageInfo& srcInfo = codec->getInfo();
-    if (codec->fNumberPasses > 1) {
-        // interlaced image
-        return new SkPngInterlacedScanlineDecoder(srcInfo, codec.detach());
+    if (1 == numberPasses) {
+        return new SkPngScanlineDecoder(imageInfo, streamDeleter.detach(), png_ptr, info_ptr,
+                                        bitDepth);
     }
 
-    return new SkPngScanlineDecoder(srcInfo, codec.detach());
+    return new SkPngInterlacedScanlineDecoder(imageInfo, streamDeleter.detach(), png_ptr,
+                                              info_ptr, bitDepth, numberPasses);
 }
 
diff --git a/src/codec/SkCodec_libpng.h b/src/codec/SkCodec_libpng.h
index e880749..8ef1ae2 100644
--- a/src/codec/SkCodec_libpng.h
+++ b/src/codec/SkCodec_libpng.h
@@ -36,7 +36,31 @@
             override;
     SkEncodedFormat onGetEncodedFormat() const override { return kPNG_SkEncodedFormat; }
     bool onRewind() override;
-    bool onReallyHasAlpha() const override { return fReallyHasAlpha; }
+    bool onReallyHasAlpha() const final;
+
+    // Helper to set up swizzler and color table. Also calls png_read_update_info.
+    Result initializeSwizzler(const SkImageInfo& requestedInfo, const Options&,
+                              SkPMColor*, int* ctableCount);
+
+    SkPngCodec(const SkImageInfo&, SkStream*, png_structp, png_infop, int, int);
+
+    png_structp png_ptr() { return fPng_ptr; }
+    SkSwizzler* swizzler() { return fSwizzler; }
+    SkSwizzler::SrcConfig srcConfig() const { return fSrcConfig; }
+    int numberPasses() const { return fNumberPasses; }
+
+    enum AlphaState {
+        // This class has done no decoding, or threw away its knowledge (in
+        // scanline decodes).
+        kUnknown_AlphaState,
+        // This class found the image (possibly partial, in the case of a
+        // scanline decode) to be opaque.
+        kOpaque_AlphaState,
+        // Ths class found the image to have alpha.
+        kHasAlpha_AlphaState,
+    };
+
+    virtual AlphaState alphaInScanlineDecode() const = 0;
 
 private:
     png_structp                 fPng_ptr;
@@ -47,21 +71,13 @@
     SkAutoTDelete<SkSwizzler>   fSwizzler;
 
     SkSwizzler::SrcConfig       fSrcConfig;
-    int                         fNumberPasses;
-    bool                        fReallyHasAlpha;
+    const int                   fNumberPasses;
     int                         fBitDepth;
 
-    SkPngCodec(const SkImageInfo&, SkStream*, png_structp, png_infop, int);
-
-    // Helper to set up swizzler and color table. Also calls png_read_update_info.
-    Result initializeSwizzler(const SkImageInfo& requestedInfo, const Options&,
-                              SkPMColor*, int* ctableCount);
+    AlphaState                  fAlphaState;
 
     bool decodePalette(bool premultiply, int* ctableCount);
     void destroyReadStruct();
 
-    friend class SkPngScanlineDecoder;
-    friend class SkPngInterlacedScanlineDecoder;
-
     typedef SkCodec INHERITED;
 };
diff --git a/src/codec/SkCodec_wbmp.cpp b/src/codec/SkCodec_wbmp.cpp
index 22f2bac..0c85ead 100644
--- a/src/codec/SkCodec_wbmp.cpp
+++ b/src/codec/SkCodec_wbmp.cpp
@@ -96,6 +96,8 @@
 SkWbmpCodec::SkWbmpCodec(const SkImageInfo& info, SkStream* stream)
     : INHERITED(info, stream)
     , fSrcRowBytes(get_src_row_bytes(this->getInfo().width()))
+    , fColorTable(nullptr)
+    , fSwizzler(nullptr)
 {}
 
 SkEncodedFormat SkWbmpCodec::onGetEncodedFormat() const {
@@ -120,7 +122,7 @@
     }
 
     if (!valid_alpha(info.alphaType(), this->getInfo().alphaType())) {
-        return SkCodec::kInvalidConversion;
+        return kInvalidConversion;
     }
 
     // Prepare a color table if necessary
@@ -163,90 +165,55 @@
     return new SkWbmpCodec(info, streamDeleter.detach());
 }
 
-class SkWbmpScanlineDecoder : public SkScanlineDecoder {
-public:
-    /*
-     * Takes ownership of all pointer paramters.
-     */
-    SkWbmpScanlineDecoder(SkWbmpCodec* codec)
-        : INHERITED(codec->getInfo())
-        , fCodec(codec)
-        , fColorTable(nullptr)
-        , fSwizzler(nullptr)
-        , fSrcBuffer(codec->fSrcRowBytes)
-    {}
-
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override {
-        void* dstRow = dst;
-        for (int y = 0; y < count; ++y) {
-            SkCodec::Result rowResult = fCodec->readRow(fSrcBuffer.get());
-            if (SkCodec::kSuccess != rowResult) {
-                return rowResult;
-            }
-            fSwizzler->swizzle(dstRow, fSrcBuffer.get());
-            dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
+SkCodec::Result SkWbmpCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) {
+    void* dstRow = dst;
+    for (int y = 0; y < count; ++y) {
+        Result rowResult = this->readRow(fSrcBuffer.get());
+        if (kSuccess != rowResult) {
+            return rowResult;
         }
-        return SkCodec::kSuccess;
+        fSwizzler->swizzle(dstRow, fSrcBuffer.get());
+        dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
     }
-
-    SkCodec::Result onStart(const SkImageInfo& dstInfo,
-            const SkCodec::Options& options, SkPMColor inputColorTable[],
-            int* inputColorCount) {
-        if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
-        }
-        if (options.fSubset) {
-            // Subsets are not supported.
-            return SkCodec::kUnimplemented;
-        }
-        if (dstInfo.dimensions() != this->getInfo().dimensions()) {
-            if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return SkCodec::kInvalidScale;
-            }
-        }
-
-        if (!valid_alpha(dstInfo.alphaType(), this->getInfo().alphaType())) {
-            return SkCodec::kInvalidConversion;
-        }
-
-        // Fill in the color table
-        setup_color_table(dstInfo.colorType(), inputColorTable, inputColorCount);
-
-        // Copy the color table to a pointer that can be owned by the scanline decoder
-        if (kIndex_8_SkColorType == dstInfo.colorType()) {
-            fColorTable.reset(new SkColorTable(inputColorTable, 2));
-        }
-
-        // Initialize the swizzler
-        fSwizzler.reset(fCodec->initializeSwizzler(dstInfo,
-                get_color_ptr(fColorTable.get()), options));
-        if (nullptr == fSwizzler.get()) {
-            return SkCodec::kInvalidConversion;
-        }
-
-        return SkCodec::kSuccess;
-    }
-
-    SkEncodedFormat onGetEncodedFormat() const {
-        return kWBMP_SkEncodedFormat;
-    }
-
-private:
-    SkAutoTDelete<SkWbmpCodec>   fCodec;
-    SkAutoTUnref<SkColorTable>   fColorTable;
-    SkAutoTDelete<SkSwizzler>    fSwizzler;
-    SkAutoTMalloc<uint8_t>       fSrcBuffer;
-
-    typedef SkScanlineDecoder INHERITED;
-};
-
-SkScanlineDecoder* SkWbmpCodec::NewSDFromStream(SkStream* stream) {
-    SkAutoTDelete<SkWbmpCodec> codec(static_cast<SkWbmpCodec*>(
-            SkWbmpCodec::NewFromStream(stream)));
-    if (!codec) {
-        return nullptr;
-    }
-
-    // Return the new scanline decoder
-    return new SkWbmpScanlineDecoder(codec.detach());
+    return kSuccess;
 }
+
+SkCodec::Result SkWbmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
+        const Options& options, SkPMColor inputColorTable[], int* inputColorCount) {
+    if (!this->rewindIfNeeded()) {
+        return kCouldNotRewind;
+    }
+    if (options.fSubset) {
+        // Subsets are not supported.
+        return kUnimplemented;
+    }
+    if (dstInfo.dimensions() != this->getInfo().dimensions()) {
+        if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
+                return kInvalidScale;
+        }
+    }
+
+    if (!valid_alpha(dstInfo.alphaType(), this->getInfo().alphaType())) {
+        return kInvalidConversion;
+    }
+
+    // Fill in the color table
+    setup_color_table(dstInfo.colorType(), inputColorTable, inputColorCount);
+
+    // Copy the color table to a pointer that can be owned by the scanline decoder
+    if (kIndex_8_SkColorType == dstInfo.colorType()) {
+        fColorTable.reset(new SkColorTable(inputColorTable, 2));
+    }
+
+    // Initialize the swizzler
+    fSwizzler.reset(this->initializeSwizzler(dstInfo,
+            get_color_ptr(fColorTable.get()), options));
+    if (nullptr == fSwizzler.get()) {
+        return kInvalidConversion;
+    }
+
+    fSrcBuffer.reset(fSrcRowBytes);
+
+    return kSuccess;
+}
+
diff --git a/src/codec/SkCodec_wbmp.h b/src/codec/SkCodec_wbmp.h
index 0891eb8..220570b 100644
--- a/src/codec/SkCodec_wbmp.h
+++ b/src/codec/SkCodec_wbmp.h
@@ -9,7 +9,6 @@
 #define SkCodec_wbmp_DEFINED
 
 #include "SkCodec.h"
-#include "SkScanlineDecoder.h"
 #include "SkSwizzler.h"
 
 class SkWbmpCodec final : public SkCodec {
@@ -23,12 +22,6 @@
      */
     static SkCodec* NewFromStream(SkStream*);
 
-    /*
-     * Assumes IsWbmp was called and returned true
-     * Creates a wbmp scanline decoder
-     * Takes ownership of the stream
-     */
-    static SkScanlineDecoder* NewSDFromStream(SkStream*);
 protected:
     SkEncodedFormat onGetEncodedFormat() const override;
     Result onGetPixels(const SkImageInfo&, void*, size_t,
@@ -50,7 +43,16 @@
 
     const size_t fSrcRowBytes;
 
-    friend class SkWbmpScanlineDecoder;
+    // Used for scanline decodes:
+    SkAutoTUnref<SkColorTable>   fColorTable;
+    SkAutoTDelete<SkSwizzler>    fSwizzler;
+    SkAutoTMalloc<uint8_t>       fSrcBuffer;
+
+    // FIXME: Override onSkipScanlines to avoid swizzling.
+    Result onGetScanlines(void* dst, int count, size_t dstRowBytes) override;
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
+            SkPMColor inputColorTable[], int* inputColorCount) override;
+
     typedef SkCodec INHERITED;
 };
 
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 4557e45..dac0c17 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -12,7 +12,6 @@
 #include "SkCodecPriv.h"
 #include "SkColorPriv.h"
 #include "SkScaledCodec.h"
-#include "SkScanlineDecoder.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkTypes.h"
@@ -382,160 +381,136 @@
     return kSuccess;
 }
 
-/*
- * Enable scanline decoding for jpegs
- */
-class SkJpegScanlineDecoder : public SkScanlineDecoder {
-public:
-    SkJpegScanlineDecoder(const SkImageInfo& srcInfo, SkJpegCodec* codec)
-        : INHERITED(srcInfo)
-        , fCodec(codec)
-        , fOpts()
-    {}
-
-    /*
-    * Return a valid set of output dimensions for this decoder, given an input scale
-    */  
-    SkISize onGetScaledDimensions(float desiredScale) override {
-        return fCodec->onGetScaledDimensions(desiredScale);
+SkCodec::Result SkJpegCodec::initializeSwizzler(const SkImageInfo& info, const Options& options) {
+    SkSwizzler::SrcConfig srcConfig;
+    switch (info.colorType()) {
+        case kGray_8_SkColorType:
+            srcConfig = SkSwizzler::kGray;
+            break;
+        case kRGBA_8888_SkColorType:
+            srcConfig = SkSwizzler::kRGBX;
+            break;
+        case kBGRA_8888_SkColorType:
+            srcConfig = SkSwizzler::kBGRX;
+            break;
+        case kRGB_565_SkColorType:
+            srcConfig = SkSwizzler::kRGB_565;
+            break;
+        default:
+            // This function should only be called if the colorType is supported by jpeg
+            SkASSERT(false);
     }
 
-    /*
-     * Create the swizzler based on the encoded format.
-     * The swizzler is only used for sampling in the x direction.
-     */
-
-    SkCodec::Result initializeSwizzler(const SkImageInfo& info, const SkCodec::Options& options) {
-        SkSwizzler::SrcConfig srcConfig;
-        switch (info.colorType()) {
-            case kGray_8_SkColorType:
-                srcConfig = SkSwizzler::kGray;
-                break;
-            case kRGBA_8888_SkColorType:
-                srcConfig = SkSwizzler::kRGBX;
-                break;
-            case kBGRA_8888_SkColorType:
-                srcConfig = SkSwizzler::kBGRX;
-                break;
-            case kRGB_565_SkColorType:
-                srcConfig = SkSwizzler::kRGB_565;
-                break;
-            default:
-                //would have exited before now if the colorType was supported by jpeg
-                SkASSERT(false);
-        }
-
-        fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, nullptr, info, options.fZeroInitialized, 
-                                                   this->getInfo()));
-        if (!fSwizzler) {
-            // FIXME: CreateSwizzler could fail for another reason.
-            return SkCodec::kUnimplemented;
-        }
-        return SkCodec::kSuccess;
+    fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, nullptr, info, options.fZeroInitialized, 
+                                               this->getInfo()));
+    if (!fSwizzler) {
+        return SkCodec::kUnimplemented;
     }
 
-    SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options& options,
-                            SkPMColor ctable[], int* ctableCount) override {
+    return kSuccess;
+}
 
-        // Rewind the stream if needed
-        if (!fCodec->rewindIfNeeded()) {
-            return SkCodec::kCouldNotRewind;
+SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
+        const Options& options, SkPMColor ctable[], int* ctableCount) {
+    // Rewind the stream if needed
+    if (!this->rewindIfNeeded()) {
+        return kCouldNotRewind;
+    }
+
+    // Set the jump location for libjpeg errors
+    if (setjmp(fDecoderMgr->getJmpBuf())) {
+        SkCodecPrintf("setjmp: Error from libjpeg\n");
+        return kInvalidInput;
+    }
+
+    // Check if we can decode to the requested destination and set the output color space
+    if (!this->setOutputColorSpace(dstInfo)) {
+        return kInvalidConversion;
+    }
+
+    // Perform the necessary scaling
+    if (!this->nativelyScaleToDimensions(dstInfo.width(), dstInfo.height())) {
+        // full native scaling to dstInfo dimensions not supported
+
+        if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
+            return kInvalidScale;
+        }
+        // create swizzler for sampling
+        Result result = this->initializeSwizzler(dstInfo, options);
+        if (kSuccess != result) {
+            SkCodecPrintf("failed to initialize the swizzler.\n");
+            return result;
+        }
+        fStorage.reset(get_row_bytes(fDecoderMgr->dinfo()));
+        fSrcRow = static_cast<uint8_t*>(fStorage.get());
+    } else {
+        fSrcRow = nullptr;
+        fSwizzler.reset(nullptr);
+    }
+
+    // Now, given valid output dimensions, we can start the decompress
+    if (!jpeg_start_decompress(fDecoderMgr->dinfo())) {
+        SkCodecPrintf("start decompress failed\n");
+        return kInvalidInput;
+    }
+
+    return kSuccess;
+}
+
+SkJpegCodec::~SkJpegCodec() {
+    // FIXME: This probably does not need to be called after a full decode
+    // FIXME: Is it safe to call when it doesn't need to be called?
+    if (setjmp(fDecoderMgr->getJmpBuf())) {
+        SkCodecPrintf("setjmp: Error in libjpeg finish_decompress\n");
+        return;
+    }
+
+    // We may not have decoded the entire image.  Prevent libjpeg-turbo from failing on a
+    // partial decode.
+    fDecoderMgr->dinfo()->output_scanline = this->getInfo().height();
+    jpeg_finish_decompress(fDecoderMgr->dinfo());
+}
+
+SkCodec::Result SkJpegCodec::onGetScanlines(void* dst, int count, size_t rowBytes) {
+    // Set the jump location for libjpeg errors
+    if (setjmp(fDecoderMgr->getJmpBuf())) {
+        return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
+    }
+    // Read rows one at a time
+    JSAMPLE* dstRow;
+    if (fSwizzler) {
+        // write data to storage row, then sample using swizzler         
+        dstRow = fSrcRow;
+    } else {
+        // write data directly to dst
+        dstRow = (JSAMPLE*) dst;
+    }
+
+    for (int y = 0; y < count; y++) {
+        // Read row of the image
+        uint32_t rowsDecoded = jpeg_read_scanlines(fDecoderMgr->dinfo(), &dstRow, 1);
+        if (rowsDecoded != 1) {
+            SkSwizzler::Fill(dstRow, this->dstInfo(), rowBytes, count - y,
+                        SK_ColorBLACK, nullptr, this->options().fZeroInitialized);
+            fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height();
+            return kIncompleteInput;
         }
 
-        // Set the jump location for libjpeg errors
-        if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
-            SkCodecPrintf("setjmp: Error from libjpeg\n");
-            return SkCodec::kInvalidInput;
+        // Convert to RGBA if necessary
+        if (JCS_CMYK == fDecoderMgr->dinfo()->out_color_space) {
+            convert_CMYK_to_RGBA(dstRow, fDecoderMgr->dinfo()->output_width);
         }
 
-        // Check if we can decode to the requested destination and set the output color space
-        if (!fCodec->setOutputColorSpace(dstInfo)) {
-            return SkCodec::kInvalidConversion;
-        }
-
-        // Perform the necessary scaling
-        if (!fCodec->nativelyScaleToDimensions(dstInfo.width(), dstInfo.height())) {
-            // full native scaling to dstInfo dimensions not supported
-
-            if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) {
-                return SkCodec::kInvalidScale;
-            }
-            // create swizzler for sampling
-            SkCodec::Result result = this->initializeSwizzler(dstInfo, options);
-            if (SkCodec::kSuccess != result) {
-                SkCodecPrintf("failed to initialize the swizzler.\n");
-                return result;
-            }
-            fStorage.reset(get_row_bytes(fCodec->fDecoderMgr->dinfo()));
-            fSrcRow = static_cast<uint8_t*>(fStorage.get());
+        if(fSwizzler) {
+            // use swizzler to sample row
+            fSwizzler->swizzle(dst, dstRow);
+            dst = SkTAddOffset<JSAMPLE>(dst, rowBytes);
         } else {
-            fSrcRow = nullptr;
-            fSwizzler.reset(nullptr);
+            dstRow = SkTAddOffset<JSAMPLE>(dstRow, rowBytes);
         }
-
-        // Now, given valid output dimensions, we can start the decompress
-        if (!jpeg_start_decompress(fCodec->fDecoderMgr->dinfo())) {
-            SkCodecPrintf("start decompress failed\n");
-            return SkCodec::kInvalidInput;
-        }
-
-        fOpts = options;
-
-        return SkCodec::kSuccess;
     }
-
-    virtual ~SkJpegScanlineDecoder() {
-        if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
-            SkCodecPrintf("setjmp: Error in libjpeg finish_decompress\n");
-            return;
-        }
-
-        // We may not have decoded the entire image.  Prevent libjpeg-turbo from failing on a
-        // partial decode.
-        fCodec->fDecoderMgr->dinfo()->output_scanline = fCodec->getInfo().height();
-        jpeg_finish_decompress(fCodec->fDecoderMgr->dinfo());
-    }
-
-    SkCodec::Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        // Set the jump location for libjpeg errors
-        if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
-            return fCodec->fDecoderMgr->returnFailure("setjmp", SkCodec::kInvalidInput);
-        }
-        // Read rows one at a time
-        JSAMPLE* dstRow;
-        if (fSwizzler) {
-            // write data to storage row, then sample using swizzler         
-            dstRow = fSrcRow;
-        } else {
-            // write data directly to dst
-            dstRow = (JSAMPLE*) dst;
-        }
-
-        for (int y = 0; y < count; y++) {
-            // Read row of the image
-            uint32_t rowsDecoded = jpeg_read_scanlines(fCodec->fDecoderMgr->dinfo(), &dstRow, 1);
-            if (rowsDecoded != 1) {
-                SkSwizzler::Fill(dstRow, this->dstInfo(), rowBytes, count - y,
-                        SK_ColorBLACK, nullptr, fOpts.fZeroInitialized);
-                fCodec->fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height();
-                return SkCodec::kIncompleteInput;
-            }
-
-            // Convert to RGBA if necessary
-            if (JCS_CMYK == fCodec->fDecoderMgr->dinfo()->out_color_space) {
-                convert_CMYK_to_RGBA(dstRow, fCodec->fDecoderMgr->dinfo()->output_width);
-            }
-
-            if(fSwizzler) {
-                // use swizzler to sample row
-                fSwizzler->swizzle(dst, dstRow);
-                dst = SkTAddOffset<JSAMPLE>(dst, rowBytes);
-            } else {
-                dstRow = SkTAddOffset<JSAMPLE>(dstRow, rowBytes);
-            }
-        }
-        return SkCodec::kSuccess;
-    }
+    return kSuccess;
+}
 
 #ifndef TURBO_HAS_SKIP
 // TODO (msarett): Make this a member function and avoid reallocating the
@@ -548,39 +523,14 @@
     }
 #endif
 
-    SkCodec::Result onSkipScanlines(int count) override {
-        // Set the jump location for libjpeg errors
-        if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
-            return fCodec->fDecoderMgr->returnFailure("setjmp", SkCodec::kInvalidInput);
-        }
-
-        jpeg_skip_scanlines(fCodec->fDecoderMgr->dinfo(), count);
-
-        return SkCodec::kSuccess;
+SkCodec::Result SkJpegCodec::onSkipScanlines(int count) {
+    // Set the jump location for libjpeg errors
+    if (setjmp(fDecoderMgr->getJmpBuf())) {
+        return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
     }
 
-    SkEncodedFormat onGetEncodedFormat() const override {
-        return kJPEG_SkEncodedFormat;
-    }
+    jpeg_skip_scanlines(fDecoderMgr->dinfo(), count);
 
-private:
-    SkAutoTDelete<SkJpegCodec> fCodec;
-    SkAutoMalloc               fStorage;    // Only used if sampling is needed
-    uint8_t*                   fSrcRow;     // Only used if sampling is needed
-    SkCodec::Options           fOpts;
-    SkAutoTDelete<SkSwizzler>  fSwizzler;
-
-    typedef SkScanlineDecoder INHERITED;
-};
-
-SkScanlineDecoder* SkJpegCodec::NewSDFromStream(SkStream* stream) {
-    SkAutoTDelete<SkJpegCodec> codec(static_cast<SkJpegCodec*>(SkJpegCodec::NewFromStream(stream)));
-    if (!codec) {
-        return nullptr;
-    }
-
-    const SkImageInfo& srcInfo = codec->getInfo();
-
-    // Return the new scanline decoder
-    return new SkJpegScanlineDecoder(srcInfo, codec.detach());
+    return kSuccess;
 }
+
diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h
index 38e41e7..b863963 100644
--- a/src/codec/SkJpegCodec.h
+++ b/src/codec/SkJpegCodec.h
@@ -18,8 +18,6 @@
     #include "jpeglib.h"
 }
 
-class SkScanlineDecoder;
-
 /*
  *
  * This class implements the decoding for jpeg images
@@ -41,13 +39,6 @@
      */
     static SkCodec* NewFromStream(SkStream*);
 
-    /*
-     * Assumes IsJpeg was called and returned true
-     * Creates a jpeg scanline decoder
-     * Takes ownership of the stream
-     */
-    static SkScanlineDecoder* NewSDFromStream(SkStream*);
-
 protected:
 
     /*
@@ -102,6 +93,8 @@
      */
     SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream, JpegDecoderMgr* decoderMgr);
 
+    ~SkJpegCodec() override;
+
     /*
      * Checks if the conversion between the input image and the requested output
      * image has been implemented
@@ -115,13 +108,23 @@
      */
     bool nativelyScaleToDimensions(uint32_t width, uint32_t height); 
 
+    // scanline decoding
+    Result initializeSwizzler(const SkImageInfo&, const SkCodec::Options&);
+    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
+                   SkPMColor ctable[], int* ctableCount) override;
+    Result onGetScanlines(void* dst, int count, size_t rowBytes) override;
+    Result onSkipScanlines(int count) override;
+
     SkAutoTDelete<JpegDecoderMgr> fDecoderMgr;
     // We will save the state of the decompress struct after reading the header.
     // This allows us to safely call onGetScaledDimensions() at any time.
     const int                     fReadyState;
-    
-    friend class SkJpegScanlineDecoder;
 
+    // scanline decoding
+    SkAutoMalloc               fStorage;    // Only used if sampling is needed
+    uint8_t*                   fSrcRow;     // Only used if sampling is needed
+    SkAutoTDelete<SkSwizzler>  fSwizzler;
+    
     typedef SkCodec INHERITED;
 };
 
diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp
index 951ab27..36aeda9 100644
--- a/src/codec/SkScaledCodec.cpp
+++ b/src/codec/SkScaledCodec.cpp
@@ -21,13 +21,13 @@
         return SkWebpCodec::NewFromStream(stream);  
     }
 
-    SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(SkScanlineDecoder::NewFromStream(stream));
-    if (nullptr == scanlineDecoder) {
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
+    if (nullptr == codec) {
         return nullptr;
     }
 
     // wrap in new SkScaledCodec
-    return new SkScaledCodec(scanlineDecoder.detach());
+    return new SkScaledCodec(codec.detach());
 }
 
 SkCodec* SkScaledCodec::NewFromData(SkData* data) {
@@ -37,9 +37,9 @@
     return NewFromStream(new SkMemoryStream(data));
 }
 
-SkScaledCodec::SkScaledCodec(SkScanlineDecoder* scanlineDecoder)
-    : INHERITED(scanlineDecoder->getInfo(), nullptr)
-    , fScanlineDecoder(scanlineDecoder)
+SkScaledCodec::SkScaledCodec(SkCodec* codec)
+    : INHERITED(codec->getInfo(), nullptr)
+    , fCodec(codec)
 {}
 
 SkScaledCodec::~SkScaledCodec() {}
@@ -78,12 +78,12 @@
  * Return a valid set of output dimensions for this decoder, given an input scale
  */
 SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const {
-    SkISize nativeDimensions = fScanlineDecoder->getScaledDimensions(desiredScale);
+    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 = fScanlineDecoder->getInfo().dimensions();
+        scaledCodecDimensions = fCodec->getInfo().dimensions();
     }
     // sampleSize determines the step size between samples
     // Ex: sampleSize = 2, sample every second pixel in x and y directions
@@ -185,19 +185,20 @@
         return kUnimplemented;
     } 
 
-    Result result = fScanlineDecoder->start(requestedInfo, &options, ctable, ctableCount);
+    // FIXME: If no scaling/subsets are requested, we can call fCodec->getPixels.
+    Result result = fCodec->startScanlineDecode(requestedInfo, &options, ctable, ctableCount);
     if (kSuccess == result) {
         // native decode supported
-        switch (fScanlineDecoder->getScanlineOrder()) {
-            case SkScanlineDecoder::kTopDown_SkScanlineOrder:
-            case SkScanlineDecoder::kBottomUp_SkScanlineOrder:
-            case SkScanlineDecoder::kNone_SkScanlineOrder:
-                return fScanlineDecoder->getScanlines(dst, requestedInfo.height(), rowBytes);
-            case SkScanlineDecoder::kOutOfOrder_SkScanlineOrder: {
+        switch (fCodec->getScanlineOrder()) {
+            case SkCodec::kTopDown_SkScanlineOrder:
+            case SkCodec::kBottomUp_SkScanlineOrder:
+            case SkCodec::kNone_SkScanlineOrder:
+                return fCodec->getScanlines(dst, requestedInfo.height(), rowBytes);
+            case SkCodec::kOutOfOrder_SkScanlineOrder: {
                 for (int y = 0; y < requestedInfo.height(); y++) {
-                    int dstY = fScanlineDecoder->getY();
+                    int dstY = fCodec->nextScanline();
                     void* dstPtr = SkTAddOffset<void>(dst, rowBytes * dstY);
-                    result = fScanlineDecoder->getScanlines(dstPtr, 1, rowBytes);
+                    result = fCodec->getScanlines(dstPtr, 1, rowBytes);
                     // FIXME (msarett): Make the SkCodec base class take care of filling
                     // uninitialized pixels so we can return immediately on kIncompleteInput.
                     if (kSuccess != result && kIncompleteInput != result) {
@@ -213,42 +214,44 @@
         // no scaling requested
         return result;
     }
-    
+
     // scaling requested
     int sampleX;
     int sampleY;
-    if (!scaling_supported(requestedInfo, fScanlineDecoder->getInfo(), &sampleX, &sampleY)) {
+    if (!scaling_supported(requestedInfo, fCodec->getInfo(), &sampleX, &sampleY)) {
         return kInvalidScale;
     }
     // set first sample pixel in y direction
     int Y0 = get_start_coord(sampleY);
 
     int dstHeight = requestedInfo.height();
-    int srcHeight = fScanlineDecoder->getInfo().height();
+    int srcHeight = fCodec->getInfo().height();
     
     SkImageInfo info = requestedInfo;
-    // use original height as scanlineDecoder does not support y sampling natively
+    // use original height as codec does not support y sampling natively
     info = info.makeWH(requestedInfo.width(), srcHeight);
 
-    // update scanlineDecoder with new info
-    result = fScanlineDecoder->start(info, &options, ctable, ctableCount);
+    // update codec with new info
+    // FIXME: The previous call to start returned kInvalidScale. This call may
+    // require a rewind. (skbug.com/4284)
+    result = fCodec->startScanlineDecode(info, &options, ctable, ctableCount);
     if (kSuccess != result) {
         return result;
     }
 
-    switch(fScanlineDecoder->getScanlineOrder()) {
-        case SkScanlineDecoder::kTopDown_SkScanlineOrder: {
-            result = fScanlineDecoder->skipScanlines(Y0);
+    switch(fCodec->getScanlineOrder()) {
+        case SkCodec::kTopDown_SkScanlineOrder: {
+            result = fCodec->skipScanlines(Y0);
             if (kSuccess != result && kIncompleteInput != result) {
                 return result;
             }
             for (int y = 0; y < dstHeight; y++) {
-                result = fScanlineDecoder->getScanlines(dst, 1, rowBytes);
+                result = fCodec->getScanlines(dst, 1, rowBytes);
                 if (kSuccess != result && kIncompleteInput != result) {
                     return result;
                 }
                 if (y < dstHeight - 1) {
-                    result = fScanlineDecoder->skipScanlines(sampleY - 1);
+                    result = fCodec->skipScanlines(sampleY - 1);
                     if (kSuccess != result && kIncompleteInput != result) {
                         return result;
                     }
@@ -257,18 +260,18 @@
             }
             return result;
         }
-        case SkScanlineDecoder::kBottomUp_SkScanlineOrder:
-        case SkScanlineDecoder::kOutOfOrder_SkScanlineOrder: {
+        case SkCodec::kBottomUp_SkScanlineOrder:
+        case SkCodec::kOutOfOrder_SkScanlineOrder: {
             for (int y = 0; y < srcHeight; y++) {
-                int srcY = fScanlineDecoder->getY();
+                int srcY = fCodec->nextScanline();
                 if (is_coord_necessary(srcY, sampleY, dstHeight)) {
                     void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY));
-                    result = fScanlineDecoder->getScanlines(dstPtr, 1, rowBytes);
+                    result = fCodec->getScanlines(dstPtr, 1, rowBytes);
                     if (kSuccess != result && kIncompleteInput != result) {
                         return result;
                     }
                 } else {
-                    result = fScanlineDecoder->skipScanlines(1);
+                    result = fCodec->skipScanlines(1);
                     if (kSuccess != result && kIncompleteInput != result) {
                         return result;
                     }
@@ -276,10 +279,10 @@
             }
             return result;
         }
-        case SkScanlineDecoder::kNone_SkScanlineOrder: {
+        case SkCodec::kNone_SkScanlineOrder: {
             SkAutoMalloc storage(srcHeight * rowBytes);
             uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
-            result = fScanlineDecoder->getScanlines(storagePtr, srcHeight, rowBytes);
+            result = fCodec->getScanlines(storagePtr, srcHeight, rowBytes);
             if (kSuccess != result && kIncompleteInput != result) {
                 return result;
             }
diff --git a/src/codec/SkScanlineDecoder.cpp b/src/codec/SkScanlineDecoder.cpp
deleted file mode 100644
index e011507..0000000
--- a/src/codec/SkScanlineDecoder.cpp
+++ /dev/null
@@ -1,108 +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.
- */
-
-#include "SkScanlineDecoder.h"
-#include "SkBmpCodec.h"
-#include "SkCodec_libgif.h"
-#include "SkCodec_libpng.h"
-#include "SkCodec_wbmp.h"
-#include "SkCodecPriv.h"
-#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
-#include "SkJpegCodec.h"
-#endif
-
-struct DecoderProc {
-    bool (*IsFormat)(SkStream*);
-    SkScanlineDecoder* (*NewFromStream)(SkStream*);
-};
-
-static const DecoderProc gDecoderProcs[] = {
-    { SkPngCodec::IsPng, SkPngCodec::NewSDFromStream },
-#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
-    { SkJpegCodec::IsJpeg, SkJpegCodec::NewSDFromStream },
-#endif
-    { SkGifCodec::IsGif, SkGifCodec::NewSDFromStream },
-    { SkBmpCodec::IsBmp, SkBmpCodec::NewSDFromStream },
-    { SkWbmpCodec::IsWbmp, SkWbmpCodec::NewSDFromStream },
-};
-
-SkScanlineDecoder* SkScanlineDecoder::NewFromStream(SkStream* stream) {
-    if (!stream) {
-        return nullptr;
-    }
-
-    SkAutoTDelete<SkStream> streamDeleter(stream);
-
-    SkAutoTDelete<SkScanlineDecoder> codec(nullptr);
-    for (uint32_t i = 0; i < SK_ARRAY_COUNT(gDecoderProcs); i++) {
-        DecoderProc proc = gDecoderProcs[i];
-        const bool correctFormat = proc.IsFormat(stream);
-        if (!stream->rewind()) {
-            return nullptr;
-        }
-        if (correctFormat) {
-            codec.reset(proc.NewFromStream(streamDeleter.detach()));
-            break;
-        }
-    }
-
-    // Set the max size at 128 megapixels (512 MB for kN32).
-    // This is about 4x smaller than a test image that takes a few minutes for
-    // dm to decode and draw.
-    const int32_t maxSize = 1 << 27;
-    if (codec && codec->getInfo().width() * codec->getInfo().height() > maxSize) {
-        SkCodecPrintf("Error: Image size too large, cannot decode.\n");
-        return nullptr;
-    } else {
-        return codec.detach();
-    }
-}
-
-SkScanlineDecoder* SkScanlineDecoder::NewFromData(SkData* data) {
-    if (!data) {
-        return nullptr;
-    }
-    return NewFromStream(new SkMemoryStream(data));
-}
-
-
-SkCodec::Result SkScanlineDecoder::start(const SkImageInfo& dstInfo,
-        const SkCodec::Options* options, SkPMColor ctable[], int* ctableCount) {
-    // Ensure that valid color ptrs are passed in for kIndex8 color type
-    if (kIndex_8_SkColorType == dstInfo.colorType()) {
-        if (nullptr == ctable || nullptr == ctableCount) {
-            return SkCodec::kInvalidParameters;
-        }
-    } else {
-        if (ctableCount) {
-            *ctableCount = 0;
-        }
-        ctableCount = nullptr;
-        ctable = nullptr;
-    }
-
-    // Set options.
-    SkCodec::Options optsStorage;
-    if (nullptr == options) {
-        options = &optsStorage;
-    }
-
-    const SkCodec::Result result = this->onStart(dstInfo, *options, ctable, ctableCount);
-    if (result != SkCodec::kSuccess) {
-        return result;
-    }
-
-    fCurrScanline = 0;
-    fDstInfo = dstInfo;
-    fOptions = *options;
-    return SkCodec::kSuccess;
-}
-
-SkCodec::Result SkScanlineDecoder::start(const SkImageInfo& dstInfo) {
-    return this->start(dstInfo, nullptr, nullptr, nullptr);
-}
-
diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp
index 2ea6eda..f1c5bba 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -11,7 +11,6 @@
 #include "SkMD5.h"
 #include "SkRandom.h"
 #include "SkScaledCodec.h"
-#include "SkScanlineDecoder.h"
 #include "Test.h"
 
 static SkStreamAsset* resource(const char path[]) {
@@ -147,25 +146,44 @@
 
     // Scanline decoding follows.
 
-    stream.reset(resource(path));
-    SkAutoTDelete<SkScanlineDecoder> scanlineDecoder(
-            SkScanlineDecoder::NewFromStream(stream.detach()));
+    // Need to call start() first.
+    REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
+            == SkCodec::kScanlineDecodingNotStarted);
+    REPORTER_ASSERT(r, codec->skipScanlines(1)
+            == SkCodec::kScanlineDecodingNotStarted);
+
+    const SkCodec::Result startResult = codec->startScanlineDecode(info);
     if (supportsScanlineDecoding) {
         bm.eraseColor(SK_ColorYELLOW);
-        REPORTER_ASSERT(r, scanlineDecoder);
 
-        REPORTER_ASSERT(r, scanlineDecoder->start(info) == SkCodec::kSuccess);
+        REPORTER_ASSERT(r, startResult == SkCodec::kSuccess);
 
         for (int y = 0; y < info.height(); y++) {
-            result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0);
+            result = codec->getScanlines(bm.getAddr(0, y), 1, 0);
             REPORTER_ASSERT(r, result == SkCodec::kSuccess);
         }
         // verify that scanline decoding gives the same result.
-        if (SkScanlineDecoder::kTopDown_SkScanlineOrder == scanlineDecoder->getScanlineOrder()) {
+        if (SkCodec::kTopDown_SkScanlineOrder == codec->getScanlineOrder()) {
             compare_to_good_digest(r, digest, bm);
         }
+
+        // Cannot continue to decode scanlines beyond the end
+        REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
+                == SkCodec::kInvalidParameters);
+
+        // Interrupting a scanline decode with a full decode starts from
+        // scratch
+        REPORTER_ASSERT(r, codec->startScanlineDecode(info) == SkCodec::kSuccess);
+        REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
+                == SkCodec::kSuccess);
+        REPORTER_ASSERT(r, codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes())
+                == SkCodec::kSuccess);
+        REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
+                == SkCodec::kScanlineDecodingNotStarted);
+        REPORTER_ASSERT(r, codec->skipScanlines(1)
+                == SkCodec::kScanlineDecodingNotStarted);
     } else {
-        REPORTER_ASSERT(r, !scanlineDecoder);
+        REPORTER_ASSERT(r, startResult == SkCodec::kUnimplemented);
     }
 
     // The rest of this function tests decoding subsets, and will decode an arbitrary number of
@@ -247,10 +265,102 @@
     check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false);
     check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false);
     check(r, "plane.png", SkISize::Make(250, 126), true, false);
+    check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false);
     check(r, "randPixels.png", SkISize::Make(8, 8), true, false);
     check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false);
 }
 
+// Test interlaced PNG in stripes, similar to DM's kStripe_Mode
+DEF_TEST(Codec_stripes, r) {
+    const char * path = "plane_interlaced.png";
+    SkAutoTDelete<SkStream> stream(resource(path));
+    if (!stream) {
+        SkDebugf("Missing resource '%s'\n", path);
+    }
+
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
+    REPORTER_ASSERT(r, codec);
+
+    if (!codec) {
+        return;
+    }
+
+    switch (codec->getScanlineOrder()) {
+        case SkCodec::kBottomUp_SkScanlineOrder:
+        case SkCodec::kOutOfOrder_SkScanlineOrder:
+            ERRORF(r, "This scanline order will not match the original.");
+            return;
+        default:
+            break;
+    }
+
+    // Baseline for what the image should look like, using N32.
+    const SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
+
+    SkBitmap bm;
+    bm.allocPixels(info);
+    SkAutoLockPixels autoLockPixels(bm);
+    SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes());
+    REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+    SkMD5::Digest digest;
+    md5(bm, &digest);
+
+    // Now decode in stripes
+    const int height = info.height();
+    const int numStripes = 4;
+    int stripeHeight;
+    int remainingLines;
+    SkTDivMod(height, numStripes, &stripeHeight, &remainingLines);
+
+    bm.eraseColor(SK_ColorYELLOW);
+
+    result = codec->startScanlineDecode(info);
+    REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+    // Odd stripes
+    for (int i = 1; i < numStripes; i += 2) {
+        // Skip the even stripes
+        result = codec->skipScanlines(stripeHeight);
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+        result = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
+                                     bm.rowBytes());
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+    }
+
+    // Even stripes
+    result = codec->startScanlineDecode(info);
+    REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+    for (int i = 0; i < numStripes; i += 2) {
+        result = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
+                                     bm.rowBytes());
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+        // Skip the odd stripes
+        if (i + 1 < numStripes) {
+            result = codec->skipScanlines(stripeHeight);
+            REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+        }
+    }
+
+    // Remainder at the end
+    if (remainingLines > 0) {
+        result = codec->startScanlineDecode(info);
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+        result = codec->skipScanlines(height - remainingLines);
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+
+        result = codec->getScanlines(bm.getAddr(0, height - remainingLines),
+                                     remainingLines, bm.rowBytes());
+        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+    }
+
+    compare_to_good_digest(r, digest, bm);
+}
+
 static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {
     SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false));
     // We should not have gotten a codec. Bots should catch us if we leaked anything.
@@ -369,13 +479,12 @@
         SkDebugf("Missing resource '%s'\n", path);
         return;
     }
-    SkAutoTDelete<SkScanlineDecoder> decoder(SkScanlineDecoder::NewFromStream(
-        stream.detach()));
+    SkAutoTDelete<SkCodec> decoder(SkCodec::NewFromStream(stream.detach()));
     
     // This should return kSuccess because kIndex8 is supported.
     SkPMColor colorStorage[256];
     int colorCount;
-    SkCodec::Result result = decoder->start(
+    SkCodec::Result result = decoder->startScanlineDecode(
         decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, colorStorage, &colorCount);
     REPORTER_ASSERT(r, SkCodec::kSuccess == result);
     // The rest of the test is uninteresting if kIndex8 is not supported
@@ -385,10 +494,10 @@
 
     // This should return kInvalidParameters because, in kIndex_8 mode, we must pass in a valid
     // colorPtr and a valid colorCountPtr.
-    result = decoder->start(
+    result = decoder->startScanlineDecode(
         decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, nullptr, nullptr);
     REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
-    result = decoder->start(
+    result = decoder->startScanlineDecode(
         decoder->getInfo().makeColorType(kIndex_8_SkColorType));
     REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
 }
diff --git a/tools/SkBitmapRegionCanvas.cpp b/tools/SkBitmapRegionCanvas.cpp
index 2344d90..c54d936 100644
--- a/tools/SkBitmapRegionCanvas.cpp
+++ b/tools/SkBitmapRegionCanvas.cpp
@@ -7,9 +7,8 @@
 
 #include "SkBitmapRegionCanvas.h"
 #include "SkCanvas.h"
-#include "SkScanlineDecoder.h"
 
-SkBitmapRegionCanvas::SkBitmapRegionCanvas(SkScanlineDecoder* decoder)
+SkBitmapRegionCanvas::SkBitmapRegionCanvas(SkCodec* decoder)
     : INHERITED(decoder->getInfo().width(), decoder->getInfo().height())
     , fDecoder(decoder)
 {}
@@ -114,7 +113,7 @@
             dstColorType, dstAlphaType);
     
     // Start the scanline decoder
-    SkCodec::Result r = fDecoder->start(decodeInfo);
+    SkCodec::Result r = fDecoder->startScanlineDecode(decodeInfo);
     if (SkCodec::kSuccess != r) {
         SkDebugf("Error: Could not start scanline decoder.\n");
         return nullptr;
diff --git a/tools/SkBitmapRegionCanvas.h b/tools/SkBitmapRegionCanvas.h
index 96631d7..05eca8b 100644
--- a/tools/SkBitmapRegionCanvas.h
+++ b/tools/SkBitmapRegionCanvas.h
@@ -7,10 +7,10 @@
 
 #include "SkBitmap.h"
 #include "SkBitmapRegionDecoderInterface.h"
-#include "SkScanlineDecoder.h"
+#include "SkCodec.h"
 
 /*
- * This class implements SkBitmapRegionDecoder using an SkScanlineDecoder and
+ * This class implements SkBitmapRegionDecoder using an SkCodec and
  * an SkCanvas.  It uses the scanline decoder to subset the height.  It then
  * will subset the width and scale by drawing to an SkCanvas.
  */
@@ -22,7 +22,7 @@
     /*
      * Takes ownership of pointer to decoder
      */
-    SkBitmapRegionCanvas(SkScanlineDecoder* decoder);
+    SkBitmapRegionCanvas(SkCodec* decoder);
 
     /*
      * Three differences from the Android version:
@@ -36,7 +36,7 @@
 
 private:
 
-    SkAutoTDelete<SkScanlineDecoder> fDecoder;
+    SkAutoTDelete<SkCodec> fDecoder;
 
     typedef SkBitmapRegionDecoderInterface INHERITED;
 
diff --git a/tools/SkBitmapRegionDecoderInterface.cpp b/tools/SkBitmapRegionDecoderInterface.cpp
index 090f042..5c769d6 100644
--- a/tools/SkBitmapRegionDecoderInterface.cpp
+++ b/tools/SkBitmapRegionDecoderInterface.cpp
@@ -8,7 +8,7 @@
 #include "SkBitmapRegionCanvas.h"
 #include "SkBitmapRegionDecoderInterface.h"
 #include "SkBitmapRegionSampler.h"
-#include "SkScanlineDecoder.h"
+#include "SkCodec.h"
 #include "SkImageDecoder.h"
 
 SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(
@@ -29,14 +29,14 @@
             return new SkBitmapRegionSampler(decoder, width, height);
         }
         case kCanvas_Strategy: {
-            SkScanlineDecoder* decoder = SkScanlineDecoder::NewFromStream(stream);
+            SkCodec* decoder = SkCodec::NewFromStream(stream);
             if (nullptr == decoder) {
                 SkDebugf("Error: Failed to create decoder.\n");
                 return nullptr;
             }
             switch (decoder->getScanlineOrder()) {
-                case SkScanlineDecoder::kTopDown_SkScanlineOrder:
-                case SkScanlineDecoder::kNone_SkScanlineOrder:
+                case SkCodec::kTopDown_SkScanlineOrder:
+                case SkCodec::kNone_SkScanlineOrder:
                     break;
                 default:
                     SkDebugf("Error: Scanline ordering not supported.\n");