Make SkPngCodec decode progressively.
authorscroggo <scroggo@chromium.org>
Fri, 16 Sep 2016 15:20:38 +0000 (08:20 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 16 Sep 2016 15:20:38 +0000 (08:20 -0700)
This is a step towards using SkCodec in Chromium, where progressive
decoding is necessary.

Switch from using png_read_row (which expects all the data to be
available) to png_process_data, which uses callbacks when rows are
available.

Create a new API for SkCodec, which supports progressive decoding and
scanline decoding. Future changes will switch the other clients off of
startScanlineDecode and get/skip-Scanlines to the new API.

Remove SkCodec::kNone_ScanlineOrder, which was only used for interlaced
PNG images. In the new API, interlaced PNG fits kTopDown. Also remove
updateCurrScanline(), which was only used by the old implementation for
interlaced PNG.

DMSrcSink:
- In CodecSrc::kScanline_Mode, use the new method for scanline decoding
for the supported formats (just PNG and PNG-in-ICO for now).

fuzz.cpp:
- Remove reference to kNone_ScanlineOrder

SkCodec:
- Add new APIs:
    - startIncrementalDecode
    - incrementalDecode
- Remove kNone_SkScanlineOrder and updateCurrScanline()
- Set fDstInfo and fOptions in getPixels(). This may not be necessary
  for all implementations, but it simplifies things for SkPngCodec.

SkPngCodec:
- Implement new APIs
- Switch from sk_read_fn/png_read_row etc to png_process_data
- Expand AutoCleanPng's role to decode the header and create the
  SkPngCodec
- Make the interlaced PNG decoder report how many lines were
  initialized during an incomplete decode

SkIcoCodec:
- Implement the new APIs; supported for PNG in ICO

SkSampledCodec:
- Call the new method for decoding scanlines, and fall back to the old
  method if the new version is unimplemented
- Remove references to kNone_SkScanlineOrder

tests/CodecPartial:
- Add a test which decodes part of an image, then finishes the decode,
  and compares it to the straightforward method

tests/CodecTest:
- Add a test which decodes all scanlines using the new method
- Repurpose the Codec_stripes test to decode using the new method in
  sections rather than all at once
- In the method check(), add a parameter for whether the image supports
  the new method of scanline decoding, and be explicit about whether an
  image supports incomplete
- Test incomplete PNG decodes. We should have been doing it anyway for
  non-interlaced (except for an image that is too small - one row), but
  the new method supports interlaced incomplete as well
- Make test_invalid_parameters test the new method
- Add a test to ensure that it's safe to fall back to scanline decoding without
  rewinding

BUG=skia:4211

The new version was generally faster than the old version (but not significantly so).

Some raw performance differences can be found at https://docs.google.com/a/google.com/spreadsheets/d/1Gis3aRCEa72qBNDRMgGDg3jD-pMgO-FXldlNF9ejo4o/

Design doc can be found at https://docs.google.com/a/google.com/document/d/11Mn8-ePDKwVEMCjs3nWwSjxcSpJ_Cu8DF57KNtUmgLM/

GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1997703003

Review-Url: https://codereview.chromium.org/1997703003

14 files changed:
dm/DMSrcSink.cpp
fuzz/fuzz.cpp
gm/factory.cpp
include/codec/SkCodec.h
src/codec/SkCodec.cpp
src/codec/SkIcoCodec.cpp
src/codec/SkIcoCodec.h
src/codec/SkPngCodec.cpp
src/codec/SkPngCodec.h
src/codec/SkSampledCodec.cpp
src/codec/SkSampler.h
tests/CodecPartialTest.cpp [new file with mode: 0644]
tests/CodecTest.cpp
tests/ImageTest.cpp

index e35bbad..ae21a5b 100644 (file)
@@ -446,31 +446,55 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
             break;
         }
         case kScanline_Mode: {
-            if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
-                                                                &colorCount)) {
-                return "Could not start scanline decoder";
-            }
-
             void* dst = pixels.get();
             uint32_t height = decodeInfo.height();
-            switch (codec->getScanlineOrder()) {
-                case SkCodec::kTopDown_SkScanlineOrder:
-                case SkCodec::kBottomUp_SkScanlineOrder:
-                case SkCodec::kNone_SkScanlineOrder:
-                    // We do not need to check the return value.  On an incomplete
-                    // image, memory will be filled with a default value.
-                    codec->getScanlines(dst, height, rowBytes);
-                    break;
-                case SkCodec::kOutOfOrder_SkScanlineOrder: {
-                    for (int y = 0; y < decodeInfo.height(); y++) {
-                        int dstY = codec->outputScanline(y);
-                        void* dstPtr = SkTAddOffset<void>(dst, rowBytes * dstY);
-                        // We complete the loop, even if this call begins to fail
-                        // due to an incomplete image.  This ensures any uninitialized
-                        // memory will be filled with the proper value.
-                        codec->getScanlines(dstPtr, 1, rowBytes);
+            const bool png = fPath.endsWith("png");
+            const bool ico = fPath.endsWith("ico");
+            bool useOldScanlineMethod = !png && !ico;
+            if (png || ico) {
+                if (SkCodec::kSuccess == codec->startIncrementalDecode(decodeInfo, dst,
+                        rowBytes, nullptr, colorPtr, &colorCount)) {
+                    int rowsDecoded;
+                    if (SkCodec::kIncompleteInput == codec->incrementalDecode(&rowsDecoded)) {
+                        codec->fillIncompleteImage(decodeInfo, dst, rowBytes,
+                                                   SkCodec::kNo_ZeroInitialized, height,
+                                                   rowsDecoded);
+                    }
+                } else {
+                    if (png) {
+                        // Error: PNG should support incremental decode.
+                        return "Could not start incremental decode";
+                    }
+                    // Otherwise, this is an ICO. Since incremental failed, it must contain a BMP,
+                    // which should work via startScanlineDecode
+                    useOldScanlineMethod = true;
+                }
+            }
+
+            if (useOldScanlineMethod) {
+                if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo, NULL, colorPtr,
+                                                                    &colorCount)) {
+                    return "Could not start scanline decoder";
+                }
+
+                switch (codec->getScanlineOrder()) {
+                    case SkCodec::kTopDown_SkScanlineOrder:
+                    case SkCodec::kBottomUp_SkScanlineOrder:
+                        // We do not need to check the return value.  On an incomplete
+                        // image, memory will be filled with a default value.
+                        codec->getScanlines(dst, height, rowBytes);
+                        break;
+                    case SkCodec::kOutOfOrder_SkScanlineOrder: {
+                        for (int y = 0; y < decodeInfo.height(); y++) {
+                            int dstY = codec->outputScanline(y);
+                            void* dstPtr = SkTAddOffset<void>(dst, rowBytes * dstY);
+                            // We complete the loop, even if this call begins to fail
+                            // due to an incomplete image.  This ensures any uninitialized
+                            // memory will be filled with the proper value.
+                            codec->getScanlines(dstPtr, 1, rowBytes);
+                        }
+                        break;
                     }
-                    break;
                 }
             }
 
index ec47aa2..5491946 100644 (file)
@@ -196,7 +196,6 @@ int fuzz_img(sk_sp<SkData> bytes, uint8_t scale, uint8_t mode) {
             switch (codec->getScanlineOrder()) {
                 case SkCodec::kTopDown_SkScanlineOrder:
                 case SkCodec::kBottomUp_SkScanlineOrder:
-                case SkCodec::kNone_SkScanlineOrder:
                     // We do not need to check the return value.  On an incomplete
                     // image, memory will be filled with a default value.
                     codec->getScanlines(dst, height, rowBytes);
index dfecd16..77da3b8 100644 (file)
@@ -35,9 +35,8 @@ protected:
             // bitmap is unlocked.
             SkAutoTUnref<SkDiscardableMemoryPool> pool(
                 SkDiscardableMemoryPool::Create(1));
-            SkAssertResult(SkDEPRECATED_InstallDiscardablePixelRef(
-                                                       SkImageGenerator::NewFromEncoded(data.get()),
-                                                       nullptr, &fBitmap, pool));
+            SkDEPRECATED_InstallDiscardablePixelRef(SkImageGenerator::NewFromEncoded(data.get()),
+                                                    nullptr, &fBitmap, pool);
         }
     }
 
index b0647ed..363347d 100644 (file)
@@ -24,11 +24,11 @@ class SkPngChunkReader;
 class SkSampler;
 
 namespace DM {
+class CodecSrc;
 class ColorCodecSrc;
 }
 class ColorCodecBench;
 
-
 /**
  *  Abstraction layer directly on top of an image codec.
  */
@@ -253,8 +253,8 @@ public:
          *  If the EncodedFormat is kWEBP_SkEncodedFormat (the only one which
          *  currently supports subsets), the top and left values must be even.
          *
-         *  In getPixels, we will attempt to decode the exact rectangular
-         *  subset specified by fSubset.
+         *  In getPixels and incremental decode, we will attempt to decode the
+         *  exact rectangular subset specified by fSubset.
          *
          *  In a scanline decode, it does not make sense to specify a subset
          *  top or subset height, since the client already controls which rows
@@ -355,6 +355,67 @@ public:
     }
 
     /**
+     *  Prepare for an incremental decode with the specified options.
+     *
+     *  This may require a rewind.
+     *
+     *  @param dstInfo Info of the destination. If the dimensions do not match
+     *      those of getInfo, this implies a scale.
+     *  @param dst Memory to write to. Needs to be large enough to hold the subset,
+     *      if present, or the full image as described in dstInfo.
+     *  @param options Contains decoding options, including if memory is zero
+     *      initialized and whether to decode a subset.
+     *  @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 startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
+            const SkCodec::Options*, SkPMColor* ctable, int* ctableCount);
+
+    Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
+            const SkCodec::Options* options) {
+        return this->startIncrementalDecode(dstInfo, dst, rowBytes, options, nullptr, nullptr);
+    }
+
+    Result startIncrementalDecode(const SkImageInfo& dstInfo, void* dst, size_t rowBytes) {
+        return this->startIncrementalDecode(dstInfo, dst, rowBytes, nullptr, nullptr, nullptr);
+    }
+
+    /**
+     *  Start/continue the incremental decode.
+     *
+     *  Not valid to call before calling startIncrementalDecode().
+     *
+     *  After the first call, should only be called again if more data has been
+     *  provided to the source SkStream.
+     *
+     *  Unlike getPixels and getScanlines, this does not do any filling. This is
+     *  left up to the caller, since they may be skipping lines or continuing the
+     *  decode later. In the latter case, they may choose to initialize all lines
+     *  first, or only initialize the remaining lines after the first call.
+     *
+     *  @param rowsDecoded Optional output variable returning the total number of
+     *      lines initialized. Only meaningful if this method returns kIncompleteInput.
+     *      Otherwise the implementation may not set it.
+     *      Note that some implementations may have initialized this many rows, but
+     *      not necessarily finished those rows (e.g. interlaced PNG). This may be
+     *      useful for determining what rows the client needs to initialize.
+     *  @return kSuccess if all lines requested in startIncrementalDecode have
+     *      been completely decoded. kIncompleteInput otherwise.
+     */
+    Result incrementalDecode(int* rowsDecoded = nullptr) {
+        if (!fStartedIncrementalDecode) {
+            return kInvalidParameters;
+        }
+        return this->onIncrementalDecode(rowsDecoded);
+    }
+
+    /**
      * The remaining functions revolve around decoding scanlines.
      */
 
@@ -474,17 +535,6 @@ public:
          * 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,
     };
 
     /**
@@ -642,11 +692,6 @@ protected:
      */
     virtual SkScanlineOrder onGetScanlineOrder() const { return kTopDown_SkScanlineOrder; }
 
-    /**
-     *  Update the current scanline. Used by interlaced png.
-     */
-    void updateCurrScanline(int newY) { fCurrScanline = newY; }
-
     const SkImageInfo& dstInfo() const { return fDstInfo; }
 
     const SkCodec::Options& options() const { return fOptions; }
@@ -673,11 +718,14 @@ private:
     bool                        fNeedsRewind;
     const Origin                fOrigin;
 
-    // These fields are only meaningful during scanline decodes.
     SkImageInfo                 fDstInfo;
     SkCodec::Options            fOptions;
+
+    // Only meaningful during scanline decodes.
     int                         fCurrScanline;
 
+    bool                        fStartedIncrementalDecode;
+
     /**
      *  Return whether these dimensions are supported as a scale.
      *
@@ -697,6 +745,16 @@ private:
         return kUnimplemented;
     }
 
+    virtual Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t,
+            const SkCodec::Options&, SkPMColor*, int*) {
+        return kUnimplemented;
+    }
+
+    virtual Result onIncrementalDecode(int*) {
+        return kUnimplemented;
+    }
+
+
     virtual bool onSkipScanlines(int /*countLines*/) { return false; }
 
     virtual int onGetScanlines(void* /*dst*/, int /*countLines*/, size_t /*rowBytes*/) { return 0; }
@@ -733,6 +791,7 @@ private:
     friend class DM::ColorCodecSrc;
     friend class ColorCodecBench;
 
+    friend class DM::CodecSrc;  // for fillIncompleteImage
     friend class SkSampledCodec;
     friend class SkIcoCodec;
 };
index 6f29870..84afc2b 100644 (file)
@@ -158,6 +158,8 @@ bool SkCodec::rewindIfNeeded() {
 
     // startScanlineDecode will need to be called before decoding scanlines.
     fCurrScanline = -1;
+    // startIncrementalDecode will need to be called before incrementalDecode.
+    fStartedIncrementalDecode = false;
 
     if (!fStream->rewind()) {
         return false;
@@ -166,6 +168,20 @@ bool SkCodec::rewindIfNeeded() {
     return this->onRewind();
 }
 
+#define CHECK_COLOR_TABLE                                   \
+    if (kIndex_8_SkColorType == info.colorType()) {         \
+        if (nullptr == ctable || nullptr == ctableCount) {  \
+            return SkCodec::kInvalidParameters;             \
+        }                                                   \
+    } else {                                                \
+        if (ctableCount) {                                  \
+            *ctableCount = 0;                               \
+        }                                                   \
+        ctableCount = nullptr;                              \
+        ctable = nullptr;                                   \
+    }
+
+
 SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes,
                                    const Options* options, SkPMColor ctable[], int* ctableCount) {
     if (kUnknown_SkColorType == info.colorType()) {
@@ -178,17 +194,7 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
         return kInvalidParameters;
     }
 
-    if (kIndex_8_SkColorType == info.colorType()) {
-        if (nullptr == ctable || nullptr == ctableCount) {
-            return kInvalidParameters;
-        }
-    } else {
-        if (ctableCount) {
-            *ctableCount = 0;
-        }
-        ctableCount = nullptr;
-        ctable = nullptr;
-    }
+    CHECK_COLOR_TABLE;
 
     if (!this->rewindIfNeeded()) {
         return kCouldNotRewind;
@@ -213,6 +219,11 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
         return kInvalidScale;
     }
 
+    fDstInfo = info;
+    // FIXME: fOptions should be updated to options here, since fillIncompleteImage (called below
+    // in this method) accesses it. Without updating, it uses the old value.
+    //fOptions = *options;
+
     // On an incomplete decode, the subclass will specify the number of scanlines that it decoded
     // successfully.
     int rowsDecoded = 0;
@@ -240,23 +251,78 @@ SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t
     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;
+SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& info, void* pixels,
+        size_t rowBytes, const SkCodec::Options* options, SkPMColor* ctable, int* ctableCount) {
+    fStartedIncrementalDecode = false;
+
+    if (kUnknown_SkColorType == info.colorType()) {
+        return kInvalidConversion;
+    }
+    if (nullptr == pixels) {
+        return kInvalidParameters;
+    }
+
     // 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;
+    CHECK_COLOR_TABLE;
+
+    // FIXME: If the rows come after the rows of a previous incremental decode,
+    // we might be able to skip the rewind, but only the implementation knows
+    // that. (e.g. PNG will always need to rewind, since we called longjmp, but
+    // a bottom-up BMP could skip rewinding if the new rows are above the old
+    // rows.)
+    if (!this->rewindIfNeeded()) {
+        return kCouldNotRewind;
+    }
+
+    // Set options.
+    Options optsStorage;
+    if (nullptr == options) {
+        options = &optsStorage;
+    } else if (options->fSubset) {
+        SkIRect size = SkIRect::MakeSize(info.dimensions());
+        if (!size.contains(*options->fSubset)) {
+            return kInvalidParameters;
         }
-    } else {
-        if (ctableCount) {
-            *ctableCount = 0;
+
+        const int top = options->fSubset->top();
+        const int bottom = options->fSubset->bottom();
+        if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) {
+            return kInvalidParameters;
         }
-        ctableCount = nullptr;
-        ctable = nullptr;
     }
 
+    if (!this->dimensionsSupported(info.dimensions())) {
+        return kInvalidScale;
+    }
+
+    fDstInfo = info;
+    fOptions = *options;
+
+    const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes,
+            fOptions, ctable, ctableCount);
+    if (kSuccess == result) {
+        fStartedIncrementalDecode = true;
+    } else if (kUnimplemented == result) {
+        // FIXME: This is temporarily necessary, until we transition SkCodec
+        // implementations from scanline decoding to incremental decoding.
+        // SkAndroidCodec will first attempt to use incremental decoding, but
+        // will fall back to scanline decoding if incremental returns
+        // kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true
+        // (after potentially rewinding), but we do not want the next call to
+        // startScanlineDecode() to do a rewind.
+        fNeedsRewind = false;
+    }
+    return result;
+}
+
+
+SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info,
+        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
+    CHECK_COLOR_TABLE;
+
     if (!this->rewindIfNeeded()) {
         return kCouldNotRewind;
     }
@@ -266,36 +332,38 @@ SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo,
     if (nullptr == options) {
         options = &optsStorage;
     } else if (options->fSubset) {
-        SkIRect size = SkIRect::MakeSize(dstInfo.dimensions());
+        SkIRect size = SkIRect::MakeSize(info.dimensions());
         if (!size.contains(*options->fSubset)) {
             return kInvalidInput;
         }
 
         // We only support subsetting in the x-dimension for scanline decoder.
         // Subsetting in the y-dimension can be accomplished using skipScanlines().
-        if (options->fSubset->top() != 0 || options->fSubset->height() != dstInfo.height()) {
+        if (options->fSubset->top() != 0 || options->fSubset->height() != info.height()) {
             return kInvalidInput;
         }
     }
 
     // FIXME: Support subsets somehow?
-    if (!this->dimensionsSupported(dstInfo.dimensions())) {
+    if (!this->dimensionsSupported(info.dimensions())) {
         return kInvalidScale;
     }
 
-    const Result result = this->onStartScanlineDecode(dstInfo, *options, ctable, ctableCount);
+    const Result result = this->onStartScanlineDecode(info, *options, ctable, ctableCount);
     if (result != SkCodec::kSuccess) {
         return result;
     }
 
     fCurrScanline = 0;
-    fDstInfo = dstInfo;
+    fDstInfo = info;
     fOptions = *options;
     return kSuccess;
 }
 
-SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo) {
-    return this->startScanlineDecode(dstInfo, nullptr, nullptr, nullptr);
+#undef CHECK_COLOR_TABLE
+
+SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& info) {
+    return this->startScanlineDecode(info, nullptr, nullptr, nullptr);
 }
 
 int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) {
@@ -343,7 +411,6 @@ int SkCodec::outputScanline(int inputScanline) const {
 int SkCodec::onOutputScanline(int inputScanline) const {
     switch (this->getScanlineOrder()) {
         case kTopDown_SkScanlineOrder:
-        case kNone_SkScanlineOrder:
             return inputScanline;
         case kBottomUp_SkScanlineOrder:
             return this->getInfo().height() - inputScanline - 1;
@@ -393,8 +460,7 @@ void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t row
     }
 
     switch (this->getScanlineOrder()) {
-        case kTopDown_SkScanlineOrder:
-        case kNone_SkScanlineOrder: {
+        case kTopDown_SkScanlineOrder: {
             const SkImageInfo fillInfo = info.makeWH(fillWidth, linesRemaining);
             fillDst = SkTAddOffset<void>(dst, linesDecoded * rowBytes);
             fill_proc(fillInfo, fillDst, rowBytes, fillValue, zeroInit, sampler);
index 45000b6..63b72c4 100644 (file)
@@ -185,6 +185,7 @@ SkIcoCodec::SkIcoCodec(int width, int height, const SkEncodedInfo& info,
     : INHERITED(width, height, info, nullptr)
     , fEmbeddedCodecs(codecs)
     , fCurrScanlineCodec(nullptr)
+    , fCurrIncrementalCodec(nullptr)
 {}
 
 /*
@@ -288,6 +289,7 @@ SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo,
         result = embeddedCodec->startScanlineDecode(dstInfo, &options, colorTable, colorCount);
         if (kSuccess == result) {
             fCurrScanlineCodec = embeddedCodec;
+            fCurrIncrementalCodec = nullptr;
             return result;
         }
 
@@ -308,13 +310,82 @@ bool SkIcoCodec::onSkipScanlines(int count) {
     return fCurrScanlineCodec->skipScanlines(count);
 }
 
+SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
+        void* pixels, size_t rowBytes, const SkCodec::Options& options,
+        SkPMColor* colorTable, int* colorCount) {
+    int index = 0;
+    while (true) {
+        index = this->chooseCodec(dstInfo.dimensions(), index);
+        if (index < 0) {
+            break;
+        }
+
+        SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index);
+        switch (embeddedCodec->startIncrementalDecode(dstInfo,
+                pixels, rowBytes, &options, colorTable, colorCount)) {
+            case kSuccess:
+                fCurrIncrementalCodec = embeddedCodec;
+                fCurrScanlineCodec = nullptr;
+                return kSuccess;
+            case kUnimplemented:
+                // FIXME: embeddedCodec is a BMP. If scanline decoding would work,
+                // return kUnimplemented so that SkSampledCodec will fall through
+                // to use the scanline decoder.
+                // Note that calling startScanlineDecode will require an extra
+                // rewind. The embedded codec has an SkMemoryStream, which is
+                // cheap to rewind, though it will do extra work re-reading the
+                // header.
+                // Also note that we pass nullptr for Options. This is because
+                // Options that are valid for incremental decoding may not be
+                // valid for scanline decoding.
+                // Once BMP supports incremental decoding this workaround can go
+                // away.
+                if (embeddedCodec->startScanlineDecode(dstInfo, nullptr,
+                        colorTable, colorCount) == kSuccess) {
+                    return kUnimplemented;
+                }
+                // Move on to the next embedded codec.
+                break;
+            default:
+                break;
+        }
+
+        index++;
+    }
+
+    SkCodecPrintf("Error: No matching candidate image in ico.\n");
+    return kInvalidScale;
+}
+
+SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) {
+    SkASSERT(fCurrIncrementalCodec);
+    return fCurrIncrementalCodec->incrementalDecode(rowsDecoded);
+}
+
 SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const {
     // FIXME: This function will possibly return the wrong value if it is called
-    //        before startScanlineDecode().
-    return fCurrScanlineCodec ? fCurrScanlineCodec->getScanlineOrder() :
-            INHERITED::onGetScanlineOrder();
+    //        before startScanlineDecode()/startIncrementalDecode().
+    if (fCurrScanlineCodec) {
+        SkASSERT(!fCurrIncrementalCodec);
+        return fCurrScanlineCodec->getScanlineOrder();
+    }
+
+    if (fCurrIncrementalCodec) {
+        return fCurrIncrementalCodec->getScanlineOrder();
+    }
+
+    return INHERITED::onGetScanlineOrder();
 }
 
 SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) {
-    return fCurrScanlineCodec ? fCurrScanlineCodec->getSampler(createIfNecessary) : nullptr;
+    if (fCurrScanlineCodec) {
+        SkASSERT(!fCurrIncrementalCodec);
+        return fCurrScanlineCodec->getSampler(createIfNecessary);
+    }
+
+    if (fCurrIncrementalCodec) {
+        return fCurrIncrementalCodec->getSampler(createIfNecessary);
+    }
+
+    return nullptr;
 }
index 8c56aee..a227d8c 100644 (file)
@@ -54,6 +54,11 @@ private:
 
     bool onSkipScanlines(int count) override;
 
+    Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
+            const SkCodec::Options&, SkPMColor*, int*) override;
+
+    Result onIncrementalDecode(int* rowsDecoded) override;
+
     SkSampler* getSampler(bool createIfNecessary) override;
 
     /*
@@ -84,5 +89,13 @@ private:
     // SkAutoTDelete.  It will be deleted by the destructor of fEmbeddedCodecs.
     SkCodec* fCurrScanlineCodec;
 
+    // Only used by incremental decoder.  onStartIncrementalDecode() will set
+    // fCurrIncrementalCodec to one of the fEmbeddedCodecs, if it can find a
+    // codec of the appropriate size.  We will use fCurrIncrementalCodec for
+    // subsequent calls to incrementalDecode().
+    // fCurrIncrementalCodec is owned by this class, but should not be an
+    // SkAutoTDelete.  It will be deleted by the destructor of fEmbeddedCodecs.
+    SkCodec* fCurrIncrementalCodec;
+
     typedef SkCodec INHERITED;
 };
index 0002bc5..04c8821 100644 (file)
     #pragma GCC diagnostic ignored "-Wclobbered"
 #endif
 
+#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
+    // This is not needed with version 1.5
+    #undef SK_GOOGLE3_PNG_HACK
+#endif
+
+// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6
+#define PNG_JMPBUF(x) png_jmpbuf((png_structp) x)
+
 ///////////////////////////////////////////////////////////////////////////////
 // Callback functions
 ///////////////////////////////////////////////////////////////////////////////
 
+// When setjmp is first called, it returns 0, meaning longjmp was not called.
+constexpr int kSetJmpOkay   = 0;
+// An error internal to libpng.
+constexpr int kPngError     = 1;
+// Passed to longjmp when we have decoded as many lines as we need.
+constexpr int kStopDecoding = 2;
+
 static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
     SkCodecPrintf("------ png error %s\n", msg);
-    longjmp(png_jmpbuf(png_ptr), 1);
+    longjmp(PNG_JMPBUF(png_ptr), kPngError);
 }
 
 void sk_warning_fn(png_structp, png_const_charp msg) {
     SkCodecPrintf("----- png warning %s\n", msg);
 }
 
-static void sk_read_fn(png_structp png_ptr, png_bytep data,
-                       png_size_t length) {
-    SkStream* stream = static_cast<SkStream*>(png_get_io_ptr(png_ptr));
-    const size_t bytes = stream->read(data, length);
-    if (bytes != length) {
-        // FIXME: We want to report the fact that the stream was truncated.
-        // One way to do that might be to pass a enum to longjmp so setjmp can
-        // specify the failure.
-        png_error(png_ptr, "Read Error!");
-    }
-}
-
 #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
 static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
     SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
@@ -66,9 +69,22 @@ static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
 
 class AutoCleanPng : public SkNoncopyable {
 public:
-    AutoCleanPng(png_structp png_ptr)
+    /*
+     *  This class does not take ownership of stream or reader, but if codecPtr
+     *  is non-NULL, and decodeBounds succeeds, it will have created a new
+     *  SkCodec (pointed to by *codecPtr) which will own/ref them, as well as
+     *  the png_ptr and info_ptr.
+     */
+    AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader,
+            SkCodec** codecPtr)
         : fPng_ptr(png_ptr)
-        , fInfo_ptr(nullptr) {}
+        , fInfo_ptr(nullptr)
+        , fDecodedBounds(false)
+        , fReadHeader(false)
+        , fStream(stream)
+        , fChunkReader(reader)
+        , fOutCodec(codecPtr)
+    {}
 
     ~AutoCleanPng() {
         // fInfo_ptr will never be non-nullptr unless fPng_ptr is.
@@ -83,17 +99,119 @@ public:
         fInfo_ptr = info_ptr;
     }
 
-    void release() {
+    /**
+     *  Reads enough of the input stream to decode the bounds.
+     *  @return false if the stream is not a valid PNG (or too short).
+     *          true if it read enough of the stream to determine the bounds.
+     *          In the latter case, the stream may have been read beyond the
+     *          point to determine the bounds, and the png_ptr will have saved
+     *          any extra data. Further, if the codecPtr supplied to the
+     *          constructor was not NULL, it will now point to a new SkCodec,
+     *          which owns (or refs, in the case of the SkPngChunkReader) the
+     *          inputs. If codecPtr was NULL, the png_ptr and info_ptr are
+     *          unowned, and it is up to the caller to destroy them.
+     */
+    bool decodeBounds();
+
+private:
+    png_structp         fPng_ptr;
+    png_infop           fInfo_ptr;
+    bool                fDecodedBounds;
+    bool                fReadHeader;
+    SkStream*           fStream;
+    SkPngChunkReader*   fChunkReader;
+    SkCodec**           fOutCodec;
+
+    /**
+     *  Supplied to libpng to call when it has read enough data to determine
+     *  bounds.
+     */
+    static void InfoCallback(png_structp png_ptr, png_infop) {
+        // png_get_progressive_ptr returns the pointer we set on the png_ptr with
+        // png_set_progressive_read_fn
+        static_cast<AutoCleanPng*>(png_get_progressive_ptr(png_ptr))->infoCallback();
+    }
+
+    void infoCallback();
+
+#ifdef SK_GOOGLE3_PNG_HACK
+// public so it can be called by SkPngCodec::rereadHeaderIfNecessary().
+public:
+#endif
+    void releasePngPtrs() {
         fPng_ptr = nullptr;
         fInfo_ptr = nullptr;
     }
-
-private:
-    png_structp     fPng_ptr;
-    png_infop       fInfo_ptr;
 };
 #define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng)
 
+bool AutoCleanPng::decodeBounds() {
+    if (setjmp(PNG_JMPBUF(fPng_ptr))) {
+        return false;
+    }
+
+    png_set_progressive_read_fn(fPng_ptr, this, InfoCallback, nullptr, nullptr);
+
+    // Arbitrary buffer size, though note that it matches (below)
+    // SkPngCodec::processData(). FIXME: Can we better suit this to the size of
+    // the PNG header?
+    constexpr size_t kBufferSize = 4096;
+    char buffer[kBufferSize];
+
+    while (true) {
+        const size_t bytesRead = fStream->read(buffer, kBufferSize);
+        if (!bytesRead) {
+            // We have read to the end of the input without decoding bounds.
+            break;
+        }
+
+        png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
+        if (fReadHeader) {
+            break;
+        }
+    }
+
+    // For safety, clear the pointer to this object.
+    png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr);
+    return fDecodedBounds;
+}
+
+void SkPngCodec::processData() {
+    switch (setjmp(PNG_JMPBUF(fPng_ptr))) {
+        case kPngError:
+            // There was an error. Stop processing data.
+            // FIXME: Do we need to discard png_ptr?
+            return;
+        case kStopDecoding:
+            // We decoded all the lines we want.
+            return;
+        case kSetJmpOkay:
+            // Everything is okay.
+            break;
+        default:
+            // No other values should be passed to longjmp.
+            SkASSERT(false);
+    }
+
+    // Arbitrary buffer size
+    constexpr size_t kBufferSize = 4096;
+    char buffer[kBufferSize];
+
+    while (true) {
+        const size_t bytesRead = this->stream()->read(buffer, kBufferSize);
+        png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead);
+
+        if (!bytesRead) {
+            // We have read to the end of the input. Note that we quit *after*
+            // calling png_process_data, because decodeBounds may have told
+            // libpng to save the remainder of the buffer, in which case
+            // png_process_data will process the saved buffer, though the
+            // stream has no more to read.
+            break;
+        }
+    }
+}
+
 // Note: SkColorTable claims to store SkPMColors, which is not necessarily the case here.
 bool SkPngCodec::createColorTable(const SkImageInfo& dstInfo, int* ctableCount) {
 
@@ -181,6 +299,8 @@ bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) {
     return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead);
 }
 
+#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6)
+
 static float png_fixed_point_to_float(png_fixed_point x) {
     // We multiply by the same factor that libpng used to convert
     // fixed point -> double.  Since we want floats, we choose to
@@ -257,6 +377,8 @@ static bool convert_to_D50(SkMatrix44* toXYZD50, float toXYZ[9], float whitePoin
     return true;
 }
 
+#endif // LIBPNG >= 1.6
+
 // Returns a colorSpace object that represents any color space information in
 // the encoded data.  If the encoded data contains no color space, this will
 // return NULL.
@@ -352,226 +474,372 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) {
     return nullptr;
 }
 
-static int bytes_per_pixel(int bitsPerPixel) {
-    // Note that we will have to change this implementation if we start
-    // supporting outputs from libpng that are less than 8-bits per component.
-    return bitsPerPixel / 8;
-}
-
 void SkPngCodec::allocateStorage(const SkImageInfo& dstInfo) {
     switch (fXformMode) {
         case kSwizzleOnly_XformMode:
-            fStorage.reset(SkAlign4(fSrcRowBytes));
-            fSwizzlerSrcRow = fStorage.get();
             break;
         case kColorOnly_XformMode:
             // Intentional fall through.  A swizzler hasn't been created yet, but one will
             // be created later if we are sampling.  We'll go ahead and allocate
             // enough memory to swizzle if necessary.
         case kSwizzleColor_XformMode: {
-            size_t colorXformBytes = dstInfo.width() * sizeof(uint32_t);
-            fStorage.reset(SkAlign4(fSrcRowBytes) + colorXformBytes);
-            fSwizzlerSrcRow = fStorage.get();
-            fColorXformSrcRow = SkTAddOffset<uint32_t>(fSwizzlerSrcRow, SkAlign4(fSrcRowBytes));
+            const size_t colorXformBytes = dstInfo.width() * sizeof(uint32_t);
+            fStorage.reset(colorXformBytes);
+            fColorXformSrcRow = (uint32_t*) fStorage.get();
             break;
         }
     }
 }
 
-void SkPngCodec::applyXformRow(void* dst, const void* src, SkColorType colorType,
-                               SkAlphaType alphaType, int width) {
+void SkPngCodec::applyXformRow(void* dst, const void* src) {
+    const SkColorType colorType = this->dstInfo().colorType();
     switch (fXformMode) {
         case kSwizzleOnly_XformMode:
             fSwizzler->swizzle(dst, (const uint8_t*) src);
             break;
         case kColorOnly_XformMode:
-            fColorXform->apply(dst, (const uint32_t*) src, width, colorType, alphaType);
+            fColorXform->apply(dst, (const uint32_t*) src, fXformWidth, colorType, fXformAlphaType);
             break;
         case kSwizzleColor_XformMode:
             fSwizzler->swizzle(fColorXformSrcRow, (const uint8_t*) src);
-            fColorXform->apply(dst, fColorXformSrcRow, width, colorType, alphaType);
+            fColorXform->apply(dst, fColorXformSrcRow, fXformWidth, colorType, fXformAlphaType);
             break;
     }
 }
 
-class SkPngNormalCodec : public SkPngCodec {
+class SkPngNormalDecoder : public SkPngCodec {
 public:
-    SkPngNormalCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
-            SkStream* stream, SkPngChunkReader* chunkReader, png_structp png_ptr,
-            png_infop info_ptr, int bitDepth)
-        : INHERITED(encodedInfo, imageInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1)
+    SkPngNormalDecoder(const SkEncodedInfo& info, const SkImageInfo& imageInfo, SkStream* stream,
+            SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth)
+        : INHERITED(info, imageInfo, stream, reader, png_ptr, info_ptr, bitDepth)
+        , fLinesDecoded(0)
+        , fDst(nullptr)
+        , fRowBytes(0)
+        , fFirstRow(0)
+        , fLastRow(0)
     {}
 
-    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
-            SkPMColor ctable[], int* ctableCount) override {
-        if (!conversion_possible(dstInfo, this->getInfo()) ||
-            !this->initializeXforms(dstInfo, options, ctable, ctableCount))
-        {
-            return kInvalidConversion;
-        }
+    static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
+        GetDecoder(png_ptr)->allRowsCallback(row, rowNum);
+    }
+
+    static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) {
+        GetDecoder(png_ptr)->rowCallback(row, rowNum);
+    }
 
-        this->allocateStorage(dstInfo);
-        return kSuccess;
+#ifdef SK_GOOGLE3_PNG_HACK
+    static void RereadInfoCallback(png_structp png_ptr, png_infop) {
+        GetDecoder(png_ptr)->rereadInfoCallback();
     }
+#endif
 
-    int readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, int startRow)
-    override {
-        SkASSERT(0 == startRow);
+private:
+    int                         fLinesDecoded; // FIXME: Move to baseclass?
+    void*                       fDst;
+    size_t                      fRowBytes;
 
-        // Assume that an error in libpng indicates an incomplete input.
-        int y = 0;
-        if (setjmp(png_jmpbuf((png_struct*)fPng_ptr))) {
-            SkCodecPrintf("Failed to read row.\n");
-            return y;
-        }
+    // Variables for partial decode
+    int                         fFirstRow;  // FIXME: Move to baseclass?
+    int                         fLastRow;
 
-        SkAlphaType xformAlphaType = select_alpha_xform(dstInfo.alphaType(),
-                                                        this->getInfo().alphaType());
-        int width = fSwizzler ? fSwizzler->swizzleWidth() : dstInfo.width();
+    typedef SkPngCodec INHERITED;
 
-        for (; y < count; y++) {
-            png_read_row(fPng_ptr, fSwizzlerSrcRow, nullptr);
-            this->applyXformRow(dst, fSwizzlerSrcRow, dstInfo.colorType(), xformAlphaType, width);
-            dst = SkTAddOffset<void>(dst, rowBytes);
+    static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) {
+        return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr));
+    }
+
+    Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
+        const int height = this->getInfo().height();
+        png_progressive_info_ptr callback = nullptr;
+#ifdef SK_GOOGLE3_PNG_HACK
+        callback = RereadInfoCallback;
+#endif
+        png_set_progressive_read_fn(this->png_ptr(), this, callback, AllRowsCallback, nullptr);
+        fDst = dst;
+        fRowBytes = rowBytes;
+
+        fLinesDecoded = 0;
+
+        this->processData();
+
+        if (fLinesDecoded == height) {
+            return SkCodec::kSuccess;
+        }
+
+        if (rowsDecoded) {
+            *rowsDecoded = fLinesDecoded;
         }
 
-        return y;
+        return SkCodec::kIncompleteInput;
     }
 
-    int onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        return this->readRows(this->dstInfo(), dst, rowBytes, count, 0);
+    void allRowsCallback(png_bytep row, int rowNum) {
+        SkASSERT(rowNum - fFirstRow == fLinesDecoded);
+        fLinesDecoded++;
+        this->applyXformRow(fDst, row);
+        fDst = SkTAddOffset<void>(fDst, fRowBytes);
     }
 
-    bool onSkipScanlines(int count) override {
-        if (setjmp(png_jmpbuf((png_struct*)fPng_ptr))) {
-            SkCodecPrintf("Failed to skip row.\n");
-            return false;
+    void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
+        png_progressive_info_ptr callback = nullptr;
+#ifdef SK_GOOGLE3_PNG_HACK
+        callback = RereadInfoCallback;
+#endif
+        png_set_progressive_read_fn(this->png_ptr(), this, callback, RowCallback, nullptr);
+        fFirstRow = firstRow;
+        fLastRow = lastRow;
+        fDst = dst;
+        fRowBytes = rowBytes;
+        fLinesDecoded = 0;
+    }
+
+    SkCodec::Result decode(int* rowsDecoded) override {
+        this->processData();
+
+        if (fLinesDecoded == fLastRow - fFirstRow + 1) {
+            return SkCodec::kSuccess;
         }
 
-        for (int row = 0; row < count; row++) {
-            png_read_row(fPng_ptr, fSwizzlerSrcRow, nullptr);
+        if (rowsDecoded) {
+            *rowsDecoded = fLinesDecoded;
         }
-        return true;
+
+        return SkCodec::kIncompleteInput;
     }
 
-    typedef SkPngCodec INHERITED;
+    void rowCallback(png_bytep row, int rowNum) {
+        if (rowNum < fFirstRow) {
+            // Ignore this row.
+            return;
+        }
+
+        SkASSERT(rowNum <= fLastRow);
+
+        // If there is no swizzler, all rows are needed.
+        if (!this->swizzler() || this->swizzler()->rowNeeded(fLinesDecoded)) {
+            this->applyXformRow(fDst, row);
+            fDst = SkTAddOffset<void>(fDst, fRowBytes);
+        }
+
+        fLinesDecoded++;
+
+        if (rowNum == fLastRow) {
+            // Fake error to stop decoding scanlines.
+            longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
+        }
+    }
 };
 
-class SkPngInterlacedCodec : public SkPngCodec {
+class SkPngInterlacedDecoder : public SkPngCodec {
 public:
-    SkPngInterlacedCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
-            SkStream* stream, SkPngChunkReader* chunkReader, png_structp png_ptr,
-            png_infop info_ptr, int bitDepth, int numberPasses)
-        : INHERITED(encodedInfo, imageInfo, stream, chunkReader, png_ptr, info_ptr, bitDepth,
-                    numberPasses)
-        , fCanSkipRewind(false)
-    {
-        SkASSERT(numberPasses != 1);
+    SkPngInterlacedDecoder(const SkEncodedInfo& info, const SkImageInfo& imageInfo,
+            SkStream* stream, SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr,
+            int bitDepth, int numberPasses)
+        : INHERITED(info, imageInfo, stream, reader, png_ptr, info_ptr, bitDepth)
+        , fNumberPasses(numberPasses)
+        , fFirstRow(0)
+        , fLastRow(0)
+        , fLinesDecoded(0)
+        , fInterlacedComplete(false)
+        , fPng_rowbytes(0)
+    {}
+
+    static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) {
+        auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr));
+        decoder->interlacedRowCallback(row, rowNum, pass);
     }
 
-    Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options,
-            SkPMColor ctable[], int* ctableCount) override {
-        if (!conversion_possible(dstInfo, this->getInfo()) ||
-            !this->initializeXforms(dstInfo, options, ctable, ctableCount))
-        {
-            return kInvalidConversion;
-        }
+#ifdef SK_GOOGLE3_PNG_HACK
+    static void RereadInfoInterlacedCallback(png_structp png_ptr, png_infop) {
+        static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr))->rereadInfoInterlaced();
+    }
+#endif
+
+private:
+    const int               fNumberPasses;
+    int                     fFirstRow;
+    int                     fLastRow;
+    void*                   fDst;
+    size_t                  fRowBytes;
+    int                     fLinesDecoded;
+    bool                    fInterlacedComplete;
+    size_t                  fPng_rowbytes;
+    SkAutoTMalloc<png_byte> fInterlaceBuffer;
+
+    typedef SkPngCodec INHERITED;
 
-        this->allocateStorage(dstInfo);
-        fCanSkipRewind = true;
-        return SkCodec::kSuccess;
+#ifdef SK_GOOGLE3_PNG_HACK
+    void rereadInfoInterlaced() {
+        this->rereadInfoCallback();
+        // Note: This allocates more memory than necessary, if we are sampling/subset.
+        this->setUpInterlaceBuffer(this->getInfo().height());
     }
+#endif
 
-    int readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, int startRow)
-    override {
-        if (setjmp(png_jmpbuf((png_struct*)fPng_ptr))) {
-            SkCodecPrintf("Failed to get scanlines.\n");
-            // FIXME (msarett): Returning 0 is pessimistic.  If we can complete a single pass,
-            // we may be able to report that all of the memory has been initialized.  Even if we
-            // fail on the first pass, we can still report than some scanlines are initialized.
-            return 0;
+    // FIXME: Currently sharing interlaced callback for all rows and subset. It's not
+    // as expensive as the subset version of non-interlaced, but it still does extra
+    // work.
+    void interlacedRowCallback(png_bytep row, int rowNum, int pass) {
+        if (rowNum < fFirstRow || rowNum > fLastRow) {
+            // Ignore this row
+            return;
         }
 
-        SkAutoTMalloc<uint8_t> storage(count * fSrcRowBytes);
-        uint8_t* srcRow;
-        for (int i = 0; i < fNumberPasses; i++) {
-            // Discard rows that we planned to skip.
-            for (int y = 0; y < startRow; y++){
-                png_read_row(fPng_ptr, fSwizzlerSrcRow, nullptr);
-            }
-            // Read rows we care about into storage.
-            srcRow = storage.get();
-            for (int y = 0; y < count; y++) {
-                png_read_row(fPng_ptr, srcRow, nullptr);
-                srcRow += fSrcRowBytes;
-            }
-            // Discard rows that we don't need.
-            for (int y = 0; y < this->getInfo().height() - startRow - count; y++) {
-                png_read_row(fPng_ptr, fSwizzlerSrcRow, nullptr);
+        png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes;
+        png_progressive_combine_row(this->png_ptr(), oldRow, row);
+
+        if (0 == pass) {
+            // The first pass initializes all rows.
+            SkASSERT(row);
+            SkASSERT(fLinesDecoded == rowNum - fFirstRow);
+            fLinesDecoded++;
+        } else {
+            SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1);
+            if (fNumberPasses - 1 == pass && rowNum == fLastRow) {
+                // Last pass, and we have read all of the rows we care about. Note that
+                // we do not care about reading anything beyond the end of the image (or
+                // beyond the last scanline requested).
+                fInterlacedComplete = true;
+                // Fake error to stop decoding scanlines.
+                longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding);
             }
         }
+    }
 
-        SkAlphaType xformAlphaType = select_alpha_xform(dstInfo.alphaType(),
-                                                        this->getInfo().alphaType());
-        int width = fSwizzler ? fSwizzler->swizzleWidth() : dstInfo.width();
-        srcRow = storage.get();
-        for (int y = 0; y < count; y++) {
-            this->applyXformRow(dst, srcRow, dstInfo.colorType(), xformAlphaType, width);
-            srcRow = SkTAddOffset<uint8_t>(srcRow, fSrcRowBytes);
+    SkCodec::Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override {
+        const int height = this->getInfo().height();
+        this->setUpInterlaceBuffer(height);
+        png_progressive_info_ptr callback = nullptr;
+#ifdef SK_GOOGLE3_PNG_HACK
+        callback = RereadInfoInterlacedCallback;
+#endif
+        png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback,
+                                    nullptr);
+
+        fFirstRow = 0;
+        fLastRow = height - 1;
+        fLinesDecoded = 0;
+
+        this->processData();
+
+        png_bytep srcRow = fInterlaceBuffer.get();
+        // FIXME: When resuming, this may rewrite rows that did not change.
+        for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) {
+            this->applyXformRow(dst, srcRow);
             dst = SkTAddOffset<void>(dst, rowBytes);
+            srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes);
+        }
+        if (fInterlacedComplete) {
+            return SkCodec::kSuccess;
         }
 
-        return count;
+        if (rowsDecoded) {
+            *rowsDecoded = fLinesDecoded;
+        }
+
+        return SkCodec::kIncompleteInput;
     }
 
-    int onGetScanlines(void* dst, int count, size_t rowBytes) override {
-        // rewind stream if have previously called onGetScanlines,
-        // since we need entire progressive image to get scanlines
-        if (fCanSkipRewind) {
-            // 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 {
-            // 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->nextScanline();
-            // This method would never be called if currScanline is -1
-            SkASSERT(currScanline != -1);
-
-            if (!this->rewindIfNeeded()) {
-                return kCouldNotRewind;
-            }
-            this->updateCurrScanline(currScanline);
+    void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override {
+        // FIXME: We could skip rows in the interlace buffer that we won't put in the output.
+        this->setUpInterlaceBuffer(lastRow - firstRow + 1);
+        png_progressive_info_ptr callback = nullptr;
+#ifdef SK_GOOGLE3_PNG_HACK
+        callback = RereadInfoInterlacedCallback;
+#endif
+        png_set_progressive_read_fn(this->png_ptr(), this, callback, InterlacedRowCallback, nullptr);
+        fFirstRow = firstRow;
+        fLastRow = lastRow;
+        fDst = dst;
+        fRowBytes = rowBytes;
+        fLinesDecoded = 0;
+    }
+
+    SkCodec::Result decode(int* rowsDecoded) override {
+        this->processData();
+
+        // Now apply Xforms on all the rows that were decoded.
+        if (!fLinesDecoded) {
+            return SkCodec::kIncompleteInput;
+        }
+        const int lastRow = fLinesDecoded + fFirstRow - 1;
+        SkASSERT(lastRow <= fLastRow);
+
+        // FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it
+        // may be too tricky/expensive to handle that correctly.
+        png_bytep srcRow = fInterlaceBuffer.get();
+        const int sampleY = this->swizzler() ? this->swizzler()->sampleY() : 1;
+        void* dst = fDst;
+        for (int rowNum = fFirstRow; rowNum <= lastRow; rowNum += sampleY) {
+            this->applyXformRow(dst, srcRow);
+            dst = SkTAddOffset<void>(dst, fRowBytes);
+            srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes * sampleY);
+        }
+
+        if (fInterlacedComplete) {
+            return SkCodec::kSuccess;
         }
 
-        return this->readRows(this->dstInfo(), dst, rowBytes, count, this->nextScanline());
+        if (rowsDecoded) {
+            *rowsDecoded = fLinesDecoded;
+        }
+        return SkCodec::kIncompleteInput;
     }
 
-    bool onSkipScanlines(int count) override {
-        // The non-virtual version will update fCurrScanline.
+    void setUpInterlaceBuffer(int height) {
+        fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr());
+        fInterlaceBuffer.reset(fPng_rowbytes * height);
+        fInterlacedComplete = false;
+    }
+};
+
+#ifdef SK_GOOGLE3_PNG_HACK
+bool SkPngCodec::rereadHeaderIfNecessary() {
+    if (!fNeedsToRereadHeader) {
         return true;
     }
 
-    SkScanlineOrder onGetScanlineOrder() const override {
-        return kNone_SkScanlineOrder;
+    // On the first call, we'll need to rewind ourselves. Future calls will
+    // have already rewound in rewindIfNecessary.
+    if (this->stream()->getPosition() > 0) {
+        this->stream()->rewind();
     }
 
-private:
-    // FIXME: This imitates behavior in SkCodec::rewindIfNeeded. That function
-    // 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.
-    // SkPngInterlacedCodec 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;
+    this->destroyReadStruct();
+    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
+                                                 sk_error_fn, sk_warning_fn);
+    if (!png_ptr) {
+        return false;
+    }
 
-    typedef SkPngCodec INHERITED;
-};
+    // Only use the AutoCleanPng to delete png_ptr as necessary.
+    // (i.e. not for reading bounds etc.)
+    AutoCleanPng autoClean(png_ptr, nullptr, nullptr, nullptr);
+
+    png_infop info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == nullptr) {
+        return false;
+    }
+
+    autoClean.setInfoPtr(info_ptr);
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+    // Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
+    // This needs to be installed before we read the png header.  Android may store ninepatch
+    // chunks in the header.
+    if (fPngChunkReader.get()) {
+        png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
+        png_set_read_user_chunk_fn(png_ptr, (png_voidp) fPngChunkReader.get(), sk_read_user_chunk);
+    }
+#endif
+
+    fPng_ptr = png_ptr;
+    fInfo_ptr = info_ptr;
+    autoClean.releasePngPtrs();
+    fNeedsToRereadHeader = false;
+    return true;
+}
+#endif // SK_GOOGLE3_PNG_HACK
 
 // Reads the header and initializes the output fields, if not NULL.
 //
@@ -598,7 +866,7 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
         return false;
     }
 
-    AutoCleanPng autoClean(png_ptr);
+    AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec);
 
     png_infop info_ptr = png_create_info_struct(png_ptr);
     if (info_ptr == nullptr) {
@@ -609,12 +877,10 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
 
     // FIXME: Could we use the return value of setjmp to specify the type of
     // error?
-    if (setjmp(png_jmpbuf(png_ptr))) {
+    if (setjmp(PNG_JMPBUF(png_ptr))) {
         return false;
     }
 
-    png_set_read_fn(png_ptr, static_cast<void*>(stream), sk_read_fn);
-
 #ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
     // Hookup our chunkReader so we can see any user-chunks the caller may be interested in.
     // This needs to be installed before we read the png header.  Android may store ninepatch
@@ -625,9 +891,31 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
     }
 #endif
 
-    // The call to png_read_info() gives us all of the information from the
-    // PNG file before the first IDAT (image data chunk).
-    png_read_info(png_ptr, info_ptr);
+    const bool decodedBounds = autoClean.decodeBounds();
+
+    if (!decodedBounds) {
+        return false;
+    }
+
+    // On success, decodeBounds releases ownership of png_ptr and info_ptr.
+    if (png_ptrp) {
+        *png_ptrp = png_ptr;
+    }
+    if (info_ptrp) {
+        *info_ptrp = info_ptr;
+    }
+
+    // decodeBounds takes care of setting outCodec
+    if (outCodec) {
+        SkASSERT(*outCodec);
+    }
+    return true;
+}
+
+// FIXME (scroggo): Once SK_GOOGLE3_PNG_HACK is no more, this method can be inline in
+// AutoCleanPng::infoCallback
+static void general_info_callback(png_structp png_ptr, png_infop info_ptr,
+                                  SkEncodedInfo::Color* outColor, SkEncodedInfo::Alpha* outAlpha) {
     png_uint_32 origWidth, origHeight;
     int bitDepth, encodedColorType;
     png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
@@ -701,30 +989,58 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
             color = SkEncodedInfo::kRGBA_Color;
             alpha = SkEncodedInfo::kUnpremul_Alpha;
     }
-
-    int numberPasses = png_set_interlace_handling(png_ptr);
-
-    autoClean.release();
-    if (png_ptrp) {
-        *png_ptrp = png_ptr;
+    if (outColor) {
+        *outColor = color;
     }
-    if (info_ptrp) {
-        *info_ptrp = info_ptr;
+    if (outAlpha) {
+        *outAlpha = alpha;
     }
+}
 
-    if (outCodec) {
-        sk_sp<SkColorSpace> colorSpace = read_color_space(png_ptr, info_ptr);
+#ifdef SK_GOOGLE3_PNG_HACK
+void SkPngCodec::rereadInfoCallback() {
+    general_info_callback(fPng_ptr, fInfo_ptr, nullptr, nullptr);
+    png_set_interlace_handling(fPng_ptr);
+    png_read_update_info(fPng_ptr, fInfo_ptr);
+}
+#endif
+
+void AutoCleanPng::infoCallback() {
+    SkEncodedInfo::Color color;
+    SkEncodedInfo::Alpha alpha;
+    general_info_callback(fPng_ptr, fInfo_ptr, &color, &alpha);
+
+    const int numberPasses = png_set_interlace_handling(fPng_ptr);
+
+    fReadHeader = true;
+    fDecodedBounds = true;
+#ifndef SK_GOOGLE3_PNG_HACK
+    // 1 tells libpng to save any extra data. We may be able to be more efficient by saving
+    // it ourselves.
+    png_process_data_pause(fPng_ptr, 1);
+#else
+    // Hack to make png_process_data stop.
+    fPng_ptr->buffer_size = 0;
+#endif
+    if (fOutCodec) {
+        SkASSERT(nullptr == *fOutCodec);
+        sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr);
         if (!colorSpace) {
             // Treat unmarked pngs as sRGB.
             colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
         }
 
         SkEncodedInfo encodedInfo = SkEncodedInfo::Make(color, alpha, 8);
+        // FIXME (scroggo): Once we get rid of SK_GOOGLE3_PNG_HACK, general_info_callback can
+        // be inlined, so these values will already be set.
+        png_uint_32 origWidth = png_get_image_width(fPng_ptr, fInfo_ptr);
+        png_uint_32 origHeight = png_get_image_height(fPng_ptr, fInfo_ptr);
+        png_byte bitDepth = png_get_bit_depth(fPng_ptr, fInfo_ptr);
         SkImageInfo imageInfo = encodedInfo.makeImageInfo(origWidth, origHeight, colorSpace);
 
         if (SkEncodedInfo::kOpaque_Alpha == alpha) {
             png_color_8p sigBits;
-            if (png_get_sBIT(png_ptr, info_ptr, &sigBits)) {
+            if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) {
                 if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) {
                     // Recommend a decode to 565 if the sBIT indicates 565.
                     imageInfo = imageInfo.makeColorType(kRGB_565_SkColorType);
@@ -733,29 +1049,32 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec
         }
 
         if (1 == numberPasses) {
-            *outCodec = new SkPngNormalCodec(encodedInfo, imageInfo, stream,
-                    chunkReader, png_ptr, info_ptr, bitDepth);
+            *fOutCodec = new SkPngNormalDecoder(encodedInfo, imageInfo, fStream,
+                    fChunkReader, fPng_ptr, fInfo_ptr, bitDepth);
         } else {
-            *outCodec = new SkPngInterlacedCodec(encodedInfo, imageInfo, stream,
-                    chunkReader, png_ptr, info_ptr, bitDepth, numberPasses);
+            *fOutCodec = new SkPngInterlacedDecoder(encodedInfo, imageInfo, fStream,
+                    fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, numberPasses);
         }
     }
 
-    return true;
+
+    // Release the pointers, which are now owned by the codec or the caller is expected to
+    // take ownership.
+    this->releasePngPtrs();
 }
 
 SkPngCodec::SkPngCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
                        SkStream* stream, SkPngChunkReader* chunkReader, void* png_ptr,
-                       void* info_ptr, int bitDepth, int numberPasses)
+                       void* info_ptr, int bitDepth)
     : INHERITED(encodedInfo, imageInfo, stream)
     , fPngChunkReader(SkSafeRef(chunkReader))
     , fPng_ptr(png_ptr)
     , fInfo_ptr(info_ptr)
-    , fSwizzlerSrcRow(nullptr)
     , fColorXformSrcRow(nullptr)
-    , fSrcRowBytes(imageInfo.width() * (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel())))
-    , fNumberPasses(numberPasses)
     , fBitDepth(bitDepth)
+#ifdef SK_GOOGLE3_PNG_HACK
+    , fNeedsToRereadHeader(true)
+#endif
 {}
 
 SkPngCodec::~SkPngCodec() {
@@ -778,7 +1097,7 @@ void SkPngCodec::destroyReadStruct() {
 
 bool SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options,
                                   SkPMColor ctable[], int* ctableCount) {
-    if (setjmp(png_jmpbuf((png_struct*)fPng_ptr))) {
+    if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) {
         SkCodecPrintf("Failed on png_read_update_info.\n");
         return false;
     }
@@ -818,6 +1137,11 @@ bool SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& opt
     return true;
 }
 
+void SkPngCodec::initializeXformAlphaAndWidth() {
+    fXformAlphaType = select_alpha_xform(this->dstInfo().alphaType(), this->getInfo().alphaType());
+    fXformWidth = this->swizzler() ? this->swizzler()->swizzleWidth() : this->dstInfo().width();
+}
+
 static inline bool apply_xform_on_decode(SkColorType dstColorType, SkEncodedInfo::Color srcColor) {
     // We will apply the color xform when reading the color table, unless F16 is requested.
     return SkEncodedInfo::kPalette_Color != srcColor || kRGBA_F16_SkColorType == dstColorType;
@@ -857,6 +1181,10 @@ SkSampler* SkPngCodec::getSampler(bool createIfNecessary) {
 }
 
 bool SkPngCodec::onRewind() {
+#ifdef SK_GOOGLE3_PNG_HACK
+    fNeedsToRereadHeader = true;
+    return true;
+#else
     // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header
     // succeeds, they will be repopulated, and if it fails, they will
     // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will
@@ -873,6 +1201,7 @@ bool SkPngCodec::onRewind() {
     fPng_ptr = png_ptr;
     fInfo_ptr = info_ptr;
     return true;
+#endif
 }
 
 SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
@@ -884,21 +1213,59 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
     {
         return kInvalidConversion;
     }
+#ifdef SK_GOOGLE3_PNG_HACK
+    // Note that this is done after initializeXforms. Otherwise that method
+    // would not have png_ptr to use.
+    if (!this->rereadHeaderIfNecessary()) {
+        return kCouldNotRewind;
+    }
+#endif
 
     if (options.fSubset) {
         return kUnimplemented;
     }
 
     this->allocateStorage(dstInfo);
-    int count = this->readRows(dstInfo, dst, rowBytes, dstInfo.height(), 0);
-    if (count > dstInfo.height()) {
-        *rowsDecoded = count;
-        return kIncompleteInput;
+    this->initializeXformAlphaAndWidth();
+    return this->decodeAllRows(dst, rowBytes, rowsDecoded);
+}
+
+SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
+        void* dst, size_t rowBytes, const SkCodec::Options& options,
+        SkPMColor* ctable, int* ctableCount) {
+    if (!conversion_possible(dstInfo, this->getInfo()) ||
+        !this->initializeXforms(dstInfo, options, ctable, ctableCount))
+    {
+        return kInvalidConversion;
     }
+#ifdef SK_GOOGLE3_PNG_HACK
+    // See note in onGetPixels.
+    if (!this->rereadHeaderIfNecessary()) {
+        return kCouldNotRewind;
+    }
+#endif
+
+    this->allocateStorage(dstInfo);
 
+    int firstRow, lastRow;
+    if (options.fSubset) {
+        firstRow = options.fSubset->top();
+        lastRow = options.fSubset->bottom() - 1;
+    } else {
+        firstRow = 0;
+        lastRow = dstInfo.height() - 1;
+    }
+    this->setRange(firstRow, lastRow, dst, rowBytes);
     return kSuccess;
 }
 
+SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) {
+    // FIXME: Only necessary on the first call.
+    this->initializeXformAlphaAndWidth();
+
+    return this->decode(rowsDecoded);
+}
+
 uint64_t SkPngCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
     const SkPMColor* colorPtr = get_color_ptr(fColorTable.get());
     if (colorPtr) {
@@ -913,8 +1280,8 @@ uint64_t SkPngCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
 SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) {
     SkAutoTDelete<SkStream> streamDeleter(stream);
 
-    SkCodec* outCodec;
-    if (read_header(stream, chunkReader, &outCodec, nullptr, nullptr)) {
+    SkCodec* outCodec = nullptr;
+    if (read_header(streamDeleter.get(), chunkReader, &outCodec, nullptr, nullptr)) {
         // Codec has taken ownership of the stream.
         SkASSERT(outCodec);
         streamDeleter.release();
index a4c20c7..e3059ba 100644 (file)
 #include "SkRefCnt.h"
 #include "SkSwizzler.h"
 
+// FIXME (scroggo): GOOGLE3 is currently using an outdated version of libpng,
+// so we need to work around the lack of the method png_process_data_pause.
+// This code will be unnecessary once we update GOOGLE3. It would make more
+// sense to condition this on the version of libpng being used, but we do not
+// know that here because png.h is only included by the cpp file.
+#define SK_GOOGLE3_PNG_HACK
+
 class SkStream;
 
 class SkPngCodec : public SkCodec {
@@ -39,25 +46,50 @@ protected:
         void* fPtr;
     };
 
+    SkPngCodec(const SkEncodedInfo&, const SkImageInfo&, SkStream*, SkPngChunkReader*,
+            void* png_ptr, void* info_ptr, int bitDepth);
+
     Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*)
             override;
     SkEncodedFormat onGetEncodedFormat() const override { return kPNG_SkEncodedFormat; }
     bool onRewind() override;
     uint64_t onGetFillValue(const SkImageInfo&) const override;
 
-    // Helper to set up swizzler, color xforms, and color table. Also calls png_read_update_info.
-    bool initializeXforms(const SkImageInfo& dstInfo, const Options&, SkPMColor* colorPtr,
-                          int* colorCount);
-    void initializeSwizzler(const SkImageInfo& dstInfo, const Options&);
     SkSampler* getSampler(bool createIfNecessary) override;
-    void allocateStorage(const SkImageInfo& dstInfo);
-    void applyXformRow(void* dst, const void* src, SkColorType, SkAlphaType, int width);
+    void applyXformRow(void* dst, const void* src);
 
-    virtual int readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count,
-                         int startRow) = 0;
+    voidp png_ptr() { return fPng_ptr; }
+    voidp info_ptr() { return fInfo_ptr; }
 
-    SkPngCodec(const SkEncodedInfo&, const SkImageInfo&, SkStream*, SkPngChunkReader*,
-               void* png_ptr, void* info_ptr, int, int);
+    SkSwizzler* swizzler() { return fSwizzler; }
+
+    // Initialize variables used by applyXformRow.
+    void initializeXformAlphaAndWidth();
+
+    /**
+     *  Pass available input to libpng to process it.
+     *
+     *  libpng will call any relevant callbacks installed. This will continue decoding
+     *  until it reaches the end of the file, or until a callback tells libpng to stop.
+     */
+    void processData();
+
+#ifdef SK_GOOGLE3_PNG_HACK
+    // In libpng 1.2.56, png_process_data_pause does not exist, so when we wanted to
+    // read the header, we may have read too far. In that case, we need to delete the
+    // png_ptr and info_ptr and recreate them. This method does that (and attaches the
+    // chunk reader.
+    bool rereadHeaderIfNecessary();
+
+    // This method sets up the new png_ptr/info_ptr (created in rereadHeaderIfNecessary)
+    // the way we set up the old one the first time in AutoCleanPng.decodeBounds's callback.
+    void rereadInfoCallback();
+#endif
+
+    Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes,
+            const SkCodec::Options&,
+            SkPMColor* ctable, int* ctableCount) override;
+    Result onIncrementalDecode(int*) override;
 
     SkAutoTUnref<SkPngChunkReader> fPngChunkReader;
     voidp                          fPng_ptr;
@@ -68,12 +100,8 @@ protected:
     SkAutoTDelete<SkSwizzler>          fSwizzler;
     std::unique_ptr<SkColorSpaceXform> fColorXform;
     SkAutoTMalloc<uint8_t>             fStorage;
-    uint8_t*                           fSwizzlerSrcRow;
     uint32_t*                          fColorXformSrcRow;
-    size_t                             fSrcRowBytes;
-
-    const int                          fNumberPasses;
-    int                                fBitDepth;
+    const int                          fBitDepth;
 
 private:
 
@@ -89,9 +117,24 @@ private:
     };
 
     bool createColorTable(const SkImageInfo& dstInfo, int* ctableCount);
+    // Helper to set up swizzler, color xforms, and color table. Also calls png_read_update_info.
+    bool initializeXforms(const SkImageInfo& dstInfo, const Options&, SkPMColor* colorPtr,
+                          int* colorCount);
+    void initializeSwizzler(const SkImageInfo& dstInfo, const Options&);
+    void allocateStorage(const SkImageInfo& dstInfo);
     void destroyReadStruct();
 
-    XformMode fXformMode;
+    virtual Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) = 0;
+    virtual void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) = 0;
+    virtual Result decode(int* rowsDecoded) = 0;
+
+    XformMode   fXformMode;
+    SkAlphaType fXformAlphaType;
+    int         fXformWidth;
+
+#ifdef SK_GOOGLE3_PNG_HACK
+    bool        fNeedsToRereadHeader;
+#endif
 
     typedef SkCodec INHERITED;
 };
index 47f99f7..403b42f 100644 (file)
@@ -101,37 +101,65 @@ SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void
     int scaledSubsetWidth = info.width();
     int scaledSubsetHeight = info.height();
 
+    const SkImageInfo scaledInfo = info.makeWH(scaledSize.width(), scaledSize.height());
+
+    {
+        // Although startScanlineDecode expects the bottom and top to match the
+        // SkImageInfo, startIncrementalDecode uses them to determine which rows to
+        // decode.
+        SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY,
+                                                      scaledSubsetWidth, scaledSubsetHeight);
+        codecOptions.fSubset = &incrementalSubset;
+        const SkCodec::Result startResult = this->codec()->startIncrementalDecode(
+                scaledInfo, pixels, rowBytes, &codecOptions,
+                options.fColorPtr, options.fColorCount);
+        if (SkCodec::kSuccess == startResult) {
+            int rowsDecoded;
+            const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
+            if (incResult == SkCodec::kSuccess) {
+                return SkCodec::kSuccess;
+            }
+            SkASSERT(SkCodec::kIncompleteInput == incResult);
+
+            // FIXME: Can zero initialized be read from SkCodec::fOptions?
+            this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes,
+                    options.fZeroInitialized, scaledSubsetHeight, rowsDecoded);
+            return SkCodec::kIncompleteInput;
+        } else if (startResult != SkCodec::kUnimplemented) {
+            return startResult;
+        }
+        // Otherwise fall down to use the old scanline decoder.
+        // codecOptions.fSubset will be reset below, so it will not continue to
+        // point to the object that is no longer on the stack.
+    }
+
     // Start the scanline decode.
     SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth,
             scaledSize.height());
     codecOptions.fSubset = &scanlineSubset;
-    SkCodec::Result result = this->codec()->startScanlineDecode(info.makeWH(scaledSize.width(),
-            scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount);
+
+    SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo,
+            &codecOptions, options.fColorPtr, options.fColorCount);
     if (SkCodec::kSuccess != result) {
         return result;
     }
 
     // At this point, we are only concerned with subsetting.  Either no scale was
     // requested, or the this->codec() is handling the scale.
-    switch (this->codec()->getScanlineOrder()) {
-        case SkCodec::kTopDown_SkScanlineOrder:
-        case SkCodec::kNone_SkScanlineOrder: {
-            if (!this->codec()->skipScanlines(scaledSubsetY)) {
-                this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
-                        scaledSubsetHeight, 0);
-                return SkCodec::kIncompleteInput;
-            }
+    // Note that subsetting is only supported for kTopDown, so this code will not be
+    // reached for other orders.
+    SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder);
+    if (!this->codec()->skipScanlines(scaledSubsetY)) {
+        this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                scaledSubsetHeight, 0);
+        return SkCodec::kIncompleteInput;
+    }
 
-            int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
-            if (decodedLines != scaledSubsetHeight) {
-                return SkCodec::kIncompleteInput;
-            }
-            return SkCodec::kSuccess;
-        }
-        default:
-            SkASSERT(false);
-            return SkCodec::kUnimplemented;
+    int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes);
+    if (decodedLines != scaledSubsetHeight) {
+        return SkCodec::kIncompleteInput;
     }
+    return SkCodec::kSuccess;
 }
 
 
@@ -174,10 +202,74 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
         sampledOptions.fSubset = &subset;
     }
 
+    // Since we guarantee that output dimensions are always at least one (even if the sampleSize
+    // is greater than a given dimension), the input sampleSize is not always the sampleSize that
+    // we use in practice.
+    const int sampleX = subsetWidth / info.width();
+    const int sampleY = subsetHeight / info.height();
+
+    const int samplingOffsetY = get_start_coord(sampleY);
+    const int startY = samplingOffsetY + subsetY;
+    int dstHeight = info.height();
+
+    const SkImageInfo nativeInfo = info.makeWH(nativeSize.width(), nativeSize.height());
+
+    {
+        // Although startScanlineDecode expects the bottom and top to match the
+        // SkImageInfo, startIncrementalDecode uses them to determine which rows to
+        // decode.
+        // Note: We *could* use "subsetY" and "subsetHeight" (calculated above) for
+        // incrementalSubset, but this code gives us a tighter bounds on the subset,
+        // meaning that we can start with the first row actually needed by the output,
+        // and stop when we've decoded the last row needed by the output.
+        SkIRect incrementalSubset;
+        incrementalSubset.fTop = startY;
+        incrementalSubset.fBottom = startY + (dstHeight - 1) * sampleY + 1;
+        if (sampledOptions.fSubset) {
+            incrementalSubset.fLeft = sampledOptions.fSubset->fLeft;
+            incrementalSubset.fRight = sampledOptions.fSubset->fRight;
+        } else {
+            incrementalSubset.fLeft = 0;
+            incrementalSubset.fRight = nativeSize.width();
+        }
+        SkCodec::Options incrementalOptions = sampledOptions;
+        incrementalOptions.fSubset = &incrementalSubset;
+        const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo,
+                pixels, rowBytes, &incrementalOptions, options.fColorPtr, options.fColorCount);
+        if (SkCodec::kSuccess == startResult) {
+            SkSampler* sampler = this->codec()->getSampler(true);
+            if (!sampler) {
+                return SkCodec::kUnimplemented;
+            }
+
+            if (sampler->setSampleX(sampleX) != info.width()) {
+                return SkCodec::kInvalidScale;
+            }
+            if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) {
+                return SkCodec::kInvalidScale;
+            }
+
+            sampler->setSampleY(sampleY);
+
+            int rowsDecoded;
+            const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded);
+            if (incResult == SkCodec::kSuccess) {
+                return SkCodec::kSuccess;
+            }
+            SkASSERT(incResult == SkCodec::kIncompleteInput);
+            const int lastRowInOutput = (rowsDecoded - startY) / sampleY;
+            // FIXME: Should this be info or nativeInfo? Does it make a difference?
+            this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
+                                               info.height(), lastRowInOutput);
+            return SkCodec::kIncompleteInput;
+        } else if (startResult != SkCodec::kUnimplemented) {
+            return startResult;
+        } // kUnimplemented means use the old method.
+    }
+
     // Start the scanline decode.
-    SkCodec::Result result = this->codec()->startScanlineDecode(
-            info.makeWH(nativeSize.width(), nativeSize.height()), &sampledOptions,
-            options.fColorPtr, options.fColorCount);
+    SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo,
+            &sampledOptions, options.fColorPtr, options.fColorCount);
     if (SkCodec::kSuccess != result) {
         return result;
     }
@@ -187,11 +279,6 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
         return SkCodec::kUnimplemented;
     }
 
-    // Since we guarantee that output dimensions are always at least one (even if the sampleSize
-    // is greater than a given dimension), the input sampleSize is not always the sampleSize that
-    // we use in practice.
-    const int sampleX = subsetWidth / info.width();
-    const int sampleY = subsetHeight / info.height();
     if (sampler->setSampleX(sampleX) != info.width()) {
         return SkCodec::kInvalidScale;
     }
@@ -199,9 +286,6 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
         return SkCodec::kInvalidScale;
     }
 
-    const int samplingOffsetY = get_start_coord(sampleY);
-    const int startY = samplingOffsetY + subsetY;
-    int dstHeight = info.height();
     switch(this->codec()->getScanlineOrder()) {
         case SkCodec::kTopDown_SkScanlineOrder: {
             if (!this->codec()->skipScanlines(startY)) {
@@ -266,30 +350,6 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix
             }
             return SkCodec::kIncompleteInput;
         }
-        case SkCodec::kNone_SkScanlineOrder: {
-            const int linesNeeded = subsetHeight - samplingOffsetY;
-            SkAutoTMalloc<uint8_t> storage(linesNeeded * rowBytes);
-            uint8_t* storagePtr = storage.get();
-
-            if (!this->codec()->skipScanlines(startY)) {
-                this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized,
-                        dstHeight, 0);
-                return SkCodec::kIncompleteInput;
-            }
-            int scanlines = this->codec()->getScanlines(storagePtr, linesNeeded, rowBytes);
-
-            for (int y = 0; y < dstHeight; y++) {
-                memcpy(pixels, storagePtr, info.minRowBytes());
-                storagePtr += sampleY * rowBytes;
-                pixels = SkTAddOffset<void>(pixels, rowBytes);
-            }
-
-            if (scanlines < dstHeight) {
-                // this->codec() has already handled filling uninitialized memory.
-                return SkCodec::kIncompleteInput;
-            }
-            return SkCodec::kSuccess;
-        }
         default:
             SkASSERT(false);
             return SkCodec::kUnimplemented;
index b233c32..0001558 100644 (file)
@@ -21,6 +21,30 @@ public:
     }
 
     /**
+     *  Update the sampler to sample every sampleY'th row.
+     */
+    void setSampleY(int sampleY) {
+        fSampleY = sampleY;
+    }
+
+    /**
+     *  Retrieve the value set for sampleY.
+     */
+    int sampleY() const {
+        return fSampleY;
+    }
+
+    /**
+     *  Based on fSampleY, return whether this row belongs in the output.
+     *
+     *  @param row Row of the image, starting with the first row used in the
+     *      output.
+     */
+    bool rowNeeded(int row) const {
+        return row % fSampleY == 0;
+    }
+
+    /**
      * Fill the remainder of the destination with a single color
      *
      * @param info
@@ -55,8 +79,13 @@ public:
     virtual void fill(const SkImageInfo& info, void* dst, size_t rowBytes,
             uint64_t colorOrIndex, SkCodec::ZeroInitialized zeroInit) {}
 
+    SkSampler()
+        : fSampleY(1)
+    {}
+
     virtual ~SkSampler() {}
 private:
+    int fSampleY;
 
     virtual int onSetSampleX(int) = 0;
 };
diff --git a/tests/CodecPartialTest.cpp b/tests/CodecPartialTest.cpp
new file mode 100644 (file)
index 0000000..4845c86
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkCodec.h"
+#include "SkData.h"
+#include "SkImageInfo.h"
+#include "SkRWBuffer.h"
+#include "SkString.h"
+
+#include "Resources.h"
+#include "Test.h"
+
+static sk_sp<SkData> make_from_resource(const char* name) {
+    SkString fullPath = GetResourcePath(name);
+    return SkData::MakeFromFileName(fullPath.c_str());
+}
+
+static SkImageInfo standardize_info(SkCodec* codec) {
+    SkImageInfo defaultInfo = codec->getInfo();
+    // Note: This drops the SkColorSpace, allowing the equality check between two
+    // different codecs created from the same file to have the same SkImageInfo.
+    return SkImageInfo::MakeN32Premul(defaultInfo.width(), defaultInfo.height());
+}
+
+static bool create_truth(sk_sp<SkData> data, SkBitmap* dst) {
+    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(std::move(data)));
+    if (!codec) {
+        return false;
+    }
+
+    const SkImageInfo info = standardize_info(codec);
+    dst->allocPixels(info);
+    return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
+}
+
+/*
+ *  Represents a stream without all of its data.
+ */
+class HaltingStream : public SkStream {
+public:
+    HaltingStream(sk_sp<SkData> data)
+        : fTotalSize(data->size())
+        , fLimit(fTotalSize / 2)
+        , fStream(std::move(data))
+    {}
+
+    void addNewData() {
+        // Arbitrary size, but deliberately different from
+        // the buffer size used by SkPngCodec.
+        fLimit = SkTMin(fTotalSize, fLimit + 1000);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        if (fStream.getPosition() + size > fLimit) {
+            size = fLimit - fStream.getPosition();
+        }
+
+        return fStream.read(buffer, size);
+    }
+
+    bool isAtEnd() const override {
+        return fStream.isAtEnd();
+    }
+
+    bool hasPosition() const override { return true; }
+    size_t getPosition() const override { return fStream.getPosition(); }
+    bool rewind() override { return fStream.rewind(); }
+    bool move(long offset) override { return fStream.move(offset); }
+
+private:
+    const size_t    fTotalSize;
+    size_t          fLimit;
+    SkMemoryStream  fStream;
+};
+
+static void test_partial(skiatest::Reporter* r, const char* name) {
+    sk_sp<SkData> file = make_from_resource(name);
+    if (!file) {
+        SkDebugf("missing resource %s\n", name);
+        return;
+    }
+
+    SkBitmap truth;
+    if (!create_truth(file, &truth)) {
+        ERRORF(r, "Failed to decode %s\n", name);
+        return;
+    }
+
+    const size_t fileSize = file->size();
+
+    // Now decode part of the file
+    HaltingStream* stream = new HaltingStream(file);
+
+    // Note that we cheat and hold on to a pointer to stream, though it is owned by
+    // partialCodec.
+    SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(stream));
+    if (!partialCodec) {
+        // Technically, this could be a small file where half the file is not
+        // enough.
+        ERRORF(r, "Failed to create codec for %s", name);
+        return;
+    }
+
+    const SkImageInfo info = standardize_info(partialCodec);
+    SkASSERT(info == truth.info());
+    SkBitmap incremental;
+    incremental.allocPixels(info);
+
+    const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
+            incremental.getPixels(), incremental.rowBytes());
+    if (startResult != SkCodec::kSuccess) {
+        ERRORF(r, "Failed to start incremental decode\n");
+        return;
+    }
+
+    while (true) {
+        const SkCodec::Result result = partialCodec->incrementalDecode();
+
+        if (stream->getPosition() == fileSize) {
+            REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+            break;
+        }
+
+        SkASSERT(stream->getPosition() < fileSize);
+
+        REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
+
+        // Append an arbitrary amount of data.
+        stream->addNewData();
+    }
+
+    // compare to original
+    for (int i = 0; i < info.height(); i++) {
+        REPORTER_ASSERT(r, !memcmp(truth.getAddr(0, 0), incremental.getAddr(0, 0),
+                                   info.minRowBytes()));
+    }
+}
+
+DEF_TEST(Codec_partial, r) {
+    test_partial(r, "plane.png");
+    test_partial(r, "plane_interlaced.png");
+    test_partial(r, "yellow_rose.png");
+    test_partial(r, "index8.png");
+    test_partial(r, "color_wheel.png");
+    test_partial(r, "mandrill_256.png");
+    test_partial(r, "mandrill_32.png");
+    test_partial(r, "arrow.png");
+    test_partial(r, "randPixels.png");
+    test_partial(r, "baby_tux.png");
+}
+
+// Test that calling getPixels when an incremental decode has been
+// started (but not finished) makes the next call to incrementalDecode
+// require a call to startIncrementalDecode.
+static void test_interleaved(skiatest::Reporter* r, const char* name) {
+    sk_sp<SkData> file = make_from_resource(name);
+    SkAutoTDelete<SkCodec> partialCodec(SkCodec::NewFromStream(
+            new HaltingStream(std::move(file))));
+    if (!partialCodec) {
+        ERRORF(r, "Failed to create codec for %s", name);
+        return;
+    }
+
+    const SkImageInfo info = standardize_info(partialCodec);
+    SkBitmap incremental;
+    incremental.allocPixels(info);
+
+    const SkCodec::Result startResult = partialCodec->startIncrementalDecode(info,
+            incremental.getPixels(), incremental.rowBytes());
+    if (startResult != SkCodec::kSuccess) {
+        ERRORF(r, "Failed to start incremental decode\n");
+        return;
+    }
+
+    SkCodec::Result result = partialCodec->incrementalDecode();
+    REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
+
+    SkBitmap full;
+    full.allocPixels(info);
+    result = partialCodec->getPixels(info, full.getPixels(), full.rowBytes());
+    REPORTER_ASSERT(r, result == SkCodec::kIncompleteInput);
+
+    // Now incremental decode will fail
+    result = partialCodec->incrementalDecode();
+    REPORTER_ASSERT(r, result == SkCodec::kInvalidParameters);
+}
+
+DEF_TEST(Codec_rewind, r) {
+    test_interleaved(r, "plane.png");
+    test_interleaved(r, "plane_interlaced.png");
+}
index 341433b..3248241 100644 (file)
 
 #include "png.h"
 
+#if PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR < 5
+    // FIXME (scroggo): Google3 needs to be updated to use a newer version of libpng. In
+    // the meantime, we had to break some pieces of SkPngCodec in order to support Google3.
+    // The parts that are broken are likely not used by Google3.
+    #define SK_PNG_DISABLE_TESTS
+#endif
+
 static void md5(const SkBitmap& bm, SkMD5::Digest* digest) {
     SkAutoLockPixels autoLockPixels(bm);
     SkASSERT(bm.getPixels());
@@ -79,6 +86,62 @@ SkIRect generate_random_subset(SkRandom* rand, int w, int h) {
     return rect;
 }
 
+static void test_incremental_decode(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
+        const SkMD5::Digest& goodDigest) {
+    SkBitmap bm;
+    bm.allocPixels(info);
+    SkAutoLockPixels autoLockPixels(bm);
+
+    REPORTER_ASSERT(r, SkCodec::kSuccess == codec->startIncrementalDecode(info, bm.getPixels(),
+                                                                          bm.rowBytes()));
+
+    REPORTER_ASSERT(r, SkCodec::kSuccess == codec->incrementalDecode());
+
+    compare_to_good_digest(r, goodDigest, bm);
+}
+
+// Test in stripes, similar to DM's kStripe_Mode
+static void test_in_stripes(skiatest::Reporter* r, SkCodec* codec, const SkImageInfo& info,
+                            const SkMD5::Digest& goodDigest) {
+    SkBitmap bm;
+    bm.allocPixels(info);
+    bm.eraseColor(SK_ColorYELLOW);
+
+    const int height = info.height();
+    // Note that if numStripes does not evenly divide height there will be an extra
+    // stripe.
+    const int numStripes = 4;
+
+    if (numStripes > height) {
+        // Image is too small.
+        return;
+    }
+
+    const int stripeHeight = height / numStripes;
+
+    // Iterate through the image twice. Once to decode odd stripes, and once for even.
+    for (int oddEven = 1; oddEven >= 0; oddEven--) {
+        for (int y = oddEven * stripeHeight; y < height; y += 2 * stripeHeight) {
+            SkIRect subset = SkIRect::MakeLTRB(0, y, info.width(),
+                                               SkTMin(y + stripeHeight, height));
+            SkCodec::Options options;
+            options.fSubset = &subset;
+            if (SkCodec::kSuccess != codec->startIncrementalDecode(info, bm.getAddr(0, y),
+                        bm.rowBytes(), &options)) {
+                ERRORF(r, "failed to start incremental decode!\ttop: %i\tbottom%i\n",
+                       subset.top(), subset.bottom());
+                return;
+            }
+            if (SkCodec::kSuccess != codec->incrementalDecode()) {
+                ERRORF(r, "failed incremental decode starting from line %i\n", y);
+                return;
+            }
+        }
+    }
+
+    compare_to_good_digest(r, goodDigest, bm);
+}
+
 template<typename Codec>
 static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info,
         const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest,
@@ -187,12 +250,14 @@ static bool supports_partial_scanlines(const char path[]) {
     return false;
 }
 
+// FIXME: Break up this giant function
 static void check(skiatest::Reporter* r,
                   const char path[],
                   SkISize size,
                   bool supportsScanlineDecoding,
                   bool supportsSubsetDecoding,
-                  bool supportsIncomplete = true) {
+                  bool supportsIncomplete,
+                  bool supportsNewScanlineDecoding = false) {
 
     SkAutoTDelete<SkStream> stream(GetResourceAsStream(path));
     if (!stream) {
@@ -221,12 +286,15 @@ static void check(skiatest::Reporter* r,
     test_codec(r, codec.get(), bm, info, size, expectedResult, &codecDigest, nullptr);
 
     // Scanline decoding follows.
-    // Need to call startScanlineDecode() first.
-    REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0)
-            == 0);
-    REPORTER_ASSERT(r, codec->skipScanlines(1)
-            == 0);
 
+    if (supportsNewScanlineDecoding && !isIncomplete) {
+        test_incremental_decode(r, codec, info, codecDigest);
+        test_in_stripes(r, codec, info, codecDigest);
+    }
+
+    // Need to call startScanlineDecode() first.
+    REPORTER_ASSERT(r, codec->getScanlines(bm.getAddr(0, 0), 1, 0) == 0);
+    REPORTER_ASSERT(r, !codec->skipScanlines(1));
     const SkCodec::Result startResult = codec->startScanlineDecode(info);
     if (supportsScanlineDecoding) {
         bm.eraseColor(SK_ColorYELLOW);
@@ -321,7 +389,7 @@ static void check(skiatest::Reporter* r,
     }
 
     // SkAndroidCodec tests
-    if (supportsScanlineDecoding || supportsSubsetDecoding) {
+    if (supportsScanlineDecoding || supportsSubsetDecoding || supportsNewScanlineDecoding) {
 
         SkAutoTDelete<SkStream> stream(GetResourceAsStream(path));
         if (!stream) {
@@ -359,6 +427,7 @@ static void check(skiatest::Reporter* r,
         REPORTER_ASSERT(r, gen->getPixels(info, bm.getPixels(), bm.rowBytes()));
         compare_to_good_digest(r, codecDigest, bm);
 
+#ifndef SK_PNG_DISABLE_TESTS
         // Test using SkFrontBufferedStream, as Android does
         SkStream* bufferedStream = SkFrontBufferedStream::Create(
                 new SkMemoryStream(std::move(fullData)), SkCodec::MinBufferedBytesNeeded());
@@ -368,26 +437,28 @@ static void check(skiatest::Reporter* r,
         if (codec) {
             test_info(r, codec.get(), info, SkCodec::kSuccess, &codecDigest);
         }
+#endif
     }
 
     // If we've just tested incomplete decodes, let's run the same test again on full decodes.
     if (isIncomplete) {
-        check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, false);
+        check(r, path, size, supportsScanlineDecoding, supportsSubsetDecoding, false,
+              supportsNewScanlineDecoding);
     }
 }
 
 DEF_TEST(Codec, r) {
     // WBMP
-    check(r, "mandrill.wbmp", SkISize::Make(512, 512), true, false);
+    check(r, "mandrill.wbmp", SkISize::Make(512, 512), true, false, true);
 
     // WEBP
-    check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true);
-    check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true);
-    check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true);
+    check(r, "baby_tux.webp", SkISize::Make(386, 395), false, true, true);
+    check(r, "color_wheel.webp", SkISize::Make(128, 128), false, true, true);
+    check(r, "yellow_rose.webp", SkISize::Make(400, 301), false, true, true);
 
     // BMP
-    check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false);
-    check(r, "rle.bmp", SkISize::Make(320, 240), true, false);
+    check(r, "randPixels.bmp", SkISize::Make(8, 8), true, false, true);
+    check(r, "rle.bmp", SkISize::Make(320, 240), true, false, true);
 
     // ICO
     // FIXME: We are not ready to test incomplete ICOs
@@ -395,7 +466,7 @@ DEF_TEST(Codec, r) {
     // Decodes an embedded BMP image
     check(r, "color_wheel.ico", SkISize::Make(128, 128), true, false, false);
     // Decodes an embedded PNG image
-    check(r, "google_chrome.ico", SkISize::Make(256, 256), true, false, false);
+    check(r, "google_chrome.ico", SkISize::Make(256, 256), false, false, false, true);
 
     // GIF
     // FIXME: We are not ready to test incomplete GIFs
@@ -405,30 +476,30 @@ DEF_TEST(Codec, r) {
     check(r, "randPixels.gif", SkISize::Make(8, 8), true, false, false);
 
     // JPG
-    check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false);
-    check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false);
+    check(r, "CMYK.jpg", SkISize::Make(642, 516), true, false, true);
+    check(r, "color_wheel.jpg", SkISize::Make(128, 128), true, false, true);
     // grayscale.jpg is too small to test incomplete
     check(r, "grayscale.jpg", SkISize::Make(128, 128), true, false, false);
-    check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false);
+    check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true, false, true);
     // randPixels.jpg is too small to test incomplete
     check(r, "randPixels.jpg", SkISize::Make(8, 8), true, false, false);
 
     // PNG
-    check(r, "arrow.png", SkISize::Make(187, 312), true, false, false);
-    check(r, "baby_tux.png", SkISize::Make(240, 246), true, false, false);
-    check(r, "color_wheel.png", SkISize::Make(128, 128), true, false, false);
-    check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), true, false, false);
-    check(r, "mandrill_128.png", SkISize::Make(128, 128), true, false, false);
-    check(r, "mandrill_16.png", SkISize::Make(16, 16), true, false, false);
-    check(r, "mandrill_256.png", SkISize::Make(256, 256), true, false, false);
-    check(r, "mandrill_32.png", SkISize::Make(32, 32), true, false, false);
-    check(r, "mandrill_512.png", SkISize::Make(512, 512), true, false, false);
-    check(r, "mandrill_64.png", SkISize::Make(64, 64), true, false, false);
-    check(r, "plane.png", SkISize::Make(250, 126), true, false, false);
-    // FIXME: We are not ready to test incomplete interlaced pngs
-    check(r, "plane_interlaced.png", SkISize::Make(250, 126), true, false, false);
-    check(r, "randPixels.png", SkISize::Make(8, 8), true, false, false);
-    check(r, "yellow_rose.png", SkISize::Make(400, 301), true, false, false);
+    check(r, "arrow.png", SkISize::Make(187, 312), false, false, true, true);
+    check(r, "baby_tux.png", SkISize::Make(240, 246), false, false, true, true);
+    check(r, "color_wheel.png", SkISize::Make(128, 128), false, false, true, true);
+    // half-transparent-white-pixel.png is too small to test incomplete
+    check(r, "half-transparent-white-pixel.png", SkISize::Make(1, 1), false, false, false, true);
+    check(r, "mandrill_128.png", SkISize::Make(128, 128), false, false, true, true);
+    check(r, "mandrill_16.png", SkISize::Make(16, 16), false, false, true, true);
+    check(r, "mandrill_256.png", SkISize::Make(256, 256), false, false, true, true);
+    check(r, "mandrill_32.png", SkISize::Make(32, 32), false, false, true, true);
+    check(r, "mandrill_512.png", SkISize::Make(512, 512), false, false, true, true);
+    check(r, "mandrill_64.png", SkISize::Make(64, 64), false, false, true, true);
+    check(r, "plane.png", SkISize::Make(250, 126), false, false, true, true);
+    check(r, "plane_interlaced.png", SkISize::Make(250, 126), false, false, true, true);
+    check(r, "randPixels.png", SkISize::Make(8, 8), false, false, true, true);
+    check(r, "yellow_rose.png", SkISize::Make(400, 301), false, false, true, true);
 
     // RAW
 // Disable RAW tests for Win32.
@@ -439,98 +510,6 @@ DEF_TEST(Codec, r) {
 #endif
 }
 
-// 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(GetResourceAsStream(path));
-    REPORTER_ASSERT(r, stream);
-    if (!stream) {
-        return;
-    }
-
-    SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
-    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
-        bool skipResult = codec->skipScanlines(stripeHeight);
-        REPORTER_ASSERT(r, skipResult);
-
-        int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
-                                     bm.rowBytes());
-        REPORTER_ASSERT(r, linesDecoded == stripeHeight);
-    }
-
-    // Even stripes
-    result = codec->startScanlineDecode(info);
-    REPORTER_ASSERT(r, result == SkCodec::kSuccess);
-
-    for (int i = 0; i < numStripes; i += 2) {
-        int linesDecoded = codec->getScanlines(bm.getAddr(0, i * stripeHeight), stripeHeight,
-                                     bm.rowBytes());
-        REPORTER_ASSERT(r, linesDecoded == stripeHeight);
-
-        // Skip the odd stripes
-        if (i + 1 < numStripes) {
-            bool skipResult = codec->skipScanlines(stripeHeight);
-            REPORTER_ASSERT(r, skipResult);
-        }
-    }
-
-    // Remainder at the end
-    if (remainingLines > 0) {
-        result = codec->startScanlineDecode(info);
-        REPORTER_ASSERT(r, result == SkCodec::kSuccess);
-
-        bool skipResult = codec->skipScanlines(height - remainingLines);
-        REPORTER_ASSERT(r, skipResult);
-
-        int linesDecoded = codec->getScanlines(bm.getAddr(0, height - remainingLines),
-                                     remainingLines, bm.rowBytes());
-        REPORTER_ASSERT(r, linesDecoded == remainingLines);
-    }
-
-    compare_to_good_digest(r, digest, bm);
-}
-
 static void test_invalid_stream(skiatest::Reporter* r, const void* stream, size_t len) {
     // Neither of these calls should return a codec. Bots should catch us if we leaked anything.
     SkCodec* codec = SkCodec::NewFromStream(new SkMemoryStream(stream, len, false));
@@ -670,26 +649,41 @@ static void test_invalid_parameters(skiatest::Reporter* r, const char path[]) {
         return;
     }
     SkAutoTDelete<SkCodec> decoder(SkCodec::NewFromStream(stream.release()));
+    if (!decoder) {
+        SkDebugf("Missing codec for %s\n", path);
+        return;
+    }
+
+    const SkImageInfo info = decoder->getInfo().makeColorType(kIndex_8_SkColorType);
 
     // This should return kSuccess because kIndex8 is supported.
     SkPMColor colorStorage[256];
     int colorCount;
-    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
-    if (SkCodec::kSuccess != result) {
+    SkCodec::Result result = decoder->startScanlineDecode(info, nullptr, colorStorage,
+                                                          &colorCount);
+    if (SkCodec::kSuccess == result) {
+        // This should return kInvalidParameters because, in kIndex_8 mode, we must pass in a valid
+        // colorPtr and a valid colorCountPtr.
+        result = decoder->startScanlineDecode(info, nullptr, nullptr, nullptr);
+        REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
+        result = decoder->startScanlineDecode(info);
+        REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
+    } else if (SkCodec::kUnimplemented == result) {
+        // New method should be supported:
+        SkBitmap bm;
+        sk_sp<SkColorTable> colorTable(new SkColorTable(colorStorage, 256));
+        bm.allocPixels(info, nullptr, colorTable.get());
+        result = decoder->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes(), nullptr,
+                                                 colorStorage, &colorCount);
+        REPORTER_ASSERT(r, SkCodec::kSuccess == result);
+        result = decoder->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes());
+        REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
+    } else {
+        // The test is uninteresting if kIndex8 is not supported
+        ERRORF(r, "Should not call test_invalid_parameters for non-Index8 file: %s\n", path);
         return;
     }
 
-    // This should return kInvalidParameters because, in kIndex_8 mode, we must pass in a valid
-    // colorPtr and a valid colorCountPtr.
-    result = decoder->startScanlineDecode(
-        decoder->getInfo().makeColorType(kIndex_8_SkColorType), nullptr, nullptr, nullptr);
-    REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
-    result = decoder->startScanlineDecode(
-        decoder->getInfo().makeColorType(kIndex_8_SkColorType));
-    REPORTER_ASSERT(r, SkCodec::kInvalidParameters == result);
 }
 
 DEF_TEST(Codec_Params, r) {
@@ -697,6 +691,11 @@ DEF_TEST(Codec_Params, r) {
     test_invalid_parameters(r, "mandrill.wbmp");
 }
 
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+
+#ifndef SK_PNG_DISABLE_TESTS   // reading chunks does not work properly with older versions.
+                               // It does not appear that anyone in Google3 is reading chunks.
+
 static void codex_test_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
     SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
     if (!sk_stream->write(data, len)) {
@@ -704,7 +703,6 @@ static void codex_test_write_fn(png_structp png_ptr, png_bytep data, png_size_t
     }
 }
 
-#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
 DEF_TEST(Codec_pngChunkReader, r) {
     // Create a dummy bitmap. Use unpremul RGBA for libpng.
     SkBitmap bm;
@@ -850,6 +848,7 @@ DEF_TEST(Codec_pngChunkReader, r) {
     REPORTER_ASSERT(r, SkCodec::kSuccess == result);
     REPORTER_ASSERT(r, chunkReader.allHaveBeenSeen());
 }
+#endif // SK_PNG_DISABLE_TESTS
 #endif // PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
 
 // Stream that can only peek up to a limit
@@ -1167,7 +1166,8 @@ DEF_TEST(Codec_PngRoundTrip, r) {
 }
 
 static void test_conversion_possible(skiatest::Reporter* r, const char* path,
-                                     bool testScanlineDecoder) {
+                                     bool supportsScanlineDecoder,
+                                     bool supportsIncrementalDecoder) {
     SkAutoTDelete<SkStream> stream(GetResourceAsStream(path));
     SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.release()));
     SkImageInfo infoF16 = codec->getInfo().makeColorType(kRGBA_F16_SkColorType);
@@ -1176,22 +1176,129 @@ static void test_conversion_possible(skiatest::Reporter* r, const char* path,
     bm.allocPixels(infoF16);
     SkCodec::Result result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes());
     REPORTER_ASSERT(r, SkCodec::kInvalidConversion == result);
-    if (testScanlineDecoder) {
-        result = codec->startScanlineDecode(infoF16);
+
+    result = codec->startScanlineDecode(infoF16);
+    if (supportsScanlineDecoder) {
         REPORTER_ASSERT(r, SkCodec::kInvalidConversion == result);
+    } else {
+        REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
+    }
+
+    result = codec->startIncrementalDecode(infoF16, bm.getPixels(), bm.rowBytes());
+    if (supportsIncrementalDecoder) {
+        REPORTER_ASSERT(r, SkCodec::kInvalidConversion == result);
+    } else {
+        REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
     }
 
     infoF16 = infoF16.makeColorSpace(infoF16.colorSpace()->makeLinearGamma());
     result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes());
     REPORTER_ASSERT(r, SkCodec::kSuccess == result);
-    if (testScanlineDecoder) {
-        result = codec->startScanlineDecode(infoF16);
+    result = codec->startScanlineDecode(infoF16);
+    if (supportsScanlineDecoder) {
+        REPORTER_ASSERT(r, SkCodec::kSuccess == result);
+    } else {
+        REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
+    }
+
+    result = codec->startIncrementalDecode(infoF16, bm.getPixels(), bm.rowBytes());
+    if (supportsIncrementalDecoder) {
         REPORTER_ASSERT(r, SkCodec::kSuccess == result);
+    } else {
+        REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
     }
 }
 
 DEF_TEST(Codec_F16ConversionPossible, r) {
-    test_conversion_possible(r, "color_wheel.webp", false);
-    test_conversion_possible(r, "mandrill_512_q075.jpg", true);
-    test_conversion_possible(r, "yellow_rose.png", true);
+    test_conversion_possible(r, "color_wheel.webp", false, false);
+    test_conversion_possible(r, "mandrill_512_q075.jpg", true, false);
+    test_conversion_possible(r, "yellow_rose.png", false, true);
+}
+
+// Only rewinds up to a limit.
+class LimitedRewindingStream : public SkStream {
+public:
+    static SkStream* Make(const char path[], size_t limit) {
+        SkStream* stream = GetResourceAsStream(path);
+        if (!stream) {
+            return nullptr;
+        }
+        return new LimitedRewindingStream(stream, limit);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        const size_t bytes = fStream->read(buffer, size);
+        fPosition += bytes;
+        return bytes;
+    }
+
+    bool isAtEnd() const override {
+        return fStream->isAtEnd();
+    }
+
+    bool rewind() override {
+        if (fPosition <= fLimit && fStream->rewind()) {
+            fPosition = 0;
+            return true;
+        }
+
+        return false;
+    }
+
+private:
+    SkAutoTDelete<SkStream> fStream;
+    const size_t            fLimit;
+    size_t                  fPosition;
+
+    LimitedRewindingStream(SkStream* stream, size_t limit)
+        : fStream(stream)
+        , fLimit(limit)
+        , fPosition(0)
+    {
+        SkASSERT(fStream);
+    }
+};
+
+DEF_TEST(Codec_fallBack, r) {
+    // SkAndroidCodec needs to be able to fall back to scanline decoding
+    // if incremental decoding does not work. Make sure this does not
+    // require a rewind.
+
+    // Formats that currently do not support incremental decoding
+    auto files = {
+            "box.gif",
+            "CMYK.jpg",
+            "color_wheel.ico",
+            "mandrill.wbmp",
+            "randPixels.bmp",
+            };
+    for (auto file : files) {
+        SkStream* stream = LimitedRewindingStream::Make(file, 14);
+        if (!stream) {
+            SkDebugf("Missing resources (%s). Set --resourcePath.\n", file);
+            return;
+        }
+
+        SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream));
+        if (!codec) {
+            ERRORF(r, "Failed to create codec for %s,", file);
+            continue;
+        }
+
+        SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType);
+        SkBitmap bm;
+        bm.allocPixels(info);
+
+        if (SkCodec::kUnimplemented != codec->startIncrementalDecode(info, bm.getPixels(),
+                bm.rowBytes())) {
+            ERRORF(r, "Is scanline decoding now implemented for %s?", file);
+            continue;
+        }
+
+        // Scanline decoding should not require a rewind.
+        SkCodec::Result result = codec->startScanlineDecode(info);
+        if (SkCodec::kSuccess != result) {
+            ERRORF(r, "Scanline decoding failed for %s with %i", file, result);
+        }
+    }
 }
index e53d142..993c3fa 100644 (file)
@@ -171,6 +171,10 @@ static void test_encode(skiatest::Reporter* reporter, SkImage* image) {
     REPORTER_ASSERT(reporter, origEncoded->size() > 0);
 
     sk_sp<SkImage> decoded(SkImage::MakeFromEncoded(origEncoded));
+    if (!decoded) {
+        ERRORF(reporter, "failed to decode image!");
+        return;
+    }
     REPORTER_ASSERT(reporter, decoded);
     assert_equal(reporter, image, nullptr, decoded.get());
 
@@ -599,6 +603,10 @@ static bool has_pixels(const SkPMColor pixels[], int count, SkPMColor expected)
 }
 
 static void test_read_pixels(skiatest::Reporter* reporter, SkImage* image) {
+    if (!image) {
+        ERRORF(reporter, "Failed to create image!");
+        return;
+    }
     const SkPMColor expected = SkPreMultiplyColor(SK_ColorWHITE);
     const SkPMColor notExpected = ~expected;
 
@@ -684,6 +692,10 @@ static void check_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* ima
 }
 
 static void test_legacy_bitmap(skiatest::Reporter* reporter, const SkImage* image, SkImage::LegacyBitmapMode mode) {
+    if (!image) {
+        ERRORF(reporter, "Failed to create image.");
+        return;
+    }
     SkBitmap bitmap;
     REPORTER_ASSERT(reporter, image->asLegacyBitmap(&bitmap, mode));
     check_legacy_bitmap(reporter, image, bitmap, mode);
@@ -735,6 +747,10 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ImageLegacyBitmap_Gpu, reporter, ctxInfo) {
 #endif
 
 static void test_peek(skiatest::Reporter* reporter, SkImage* image, bool expectPeekSuccess) {
+    if (!image) {
+        ERRORF(reporter, "Failed to create image!");
+        return;
+    }
     SkPixmap pm;
     bool success = image->peekPixels(&pm);
     REPORTER_ASSERT(reporter, expectPeekSuccess == success);
@@ -926,6 +942,11 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DeferredTextureImage, reporter, ctxInfo) {
 
     for (auto testCase : testCases) {
         sk_sp<SkImage> image(testCase.fImageFactory());
+        if (!image) {
+            ERRORF(reporter, "Failed to create image!");
+            continue;
+        }
+
         size_t size = image->getDeferredTextureImageData(*proxy, testCase.fParams.data(),
                                                          static_cast<int>(testCase.fParams.size()),
                                                          nullptr, SkSourceGammaTreatment::kIgnore);