diff options
author | scroggo <scroggo@chromium.org> | 2016-06-01 07:31:28 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-06-01 07:31:28 -0700 |
commit | 30e78c9737ff4861dc4e3fa1e4cd010680ed6965 (patch) | |
tree | b5f81285844f331b3ae29f088cef22e45ca83e8a /src | |
parent | e3fa811657ecf4ab694d026752a81080c6b10611 (diff) |
Make SkPngCodec decode progressively.
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()
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
- Make initializeSwizzler return a bool instead of an SkCodec::Result
(It only returned kSuccess or kInvalidInput anyway)
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
Committed: https://skia.googlesource.com/skia/+/a4b09a117d4d1ba5dda372e6a2323e653766539e
Review-Url: https://codereview.chromium.org/1997703003
Diffstat (limited to 'src')
-rw-r--r-- | src/codec/SkCodec.cpp | 125 | ||||
-rw-r--r-- | src/codec/SkGifCodec.cpp | 2 | ||||
-rw-r--r-- | src/codec/SkIcoCodec.cpp | 79 | ||||
-rw-r--r-- | src/codec/SkIcoCodec.h | 13 | ||||
-rw-r--r-- | src/codec/SkPngCodec.cpp | 710 | ||||
-rw-r--r-- | src/codec/SkPngCodec.h | 33 | ||||
-rw-r--r-- | src/codec/SkSampledCodec.cpp | 164 | ||||
-rw-r--r-- | src/codec/SkSampler.h | 29 |
8 files changed, 769 insertions, 386 deletions
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp index 3da7f5fb07..3c485c8beb 100644 --- a/src/codec/SkCodec.cpp +++ b/src/codec/SkCodec.cpp @@ -154,6 +154,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()) { @@ -166,17 +180,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; @@ -228,23 +232,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; } @@ -254,36 +313,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) { @@ -331,7 +392,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; @@ -365,8 +425,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); diff --git a/src/codec/SkGifCodec.cpp b/src/codec/SkGifCodec.cpp index 75e9d63fa7..dcc25b8dfd 100644 --- a/src/codec/SkGifCodec.cpp +++ b/src/codec/SkGifCodec.cpp @@ -520,7 +520,7 @@ uint32_t SkGifCodec::onGetFillValue(SkColorType colorType) const { SkCodec::Result SkGifCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options& opts, SkPMColor inputColorPtr[], int* inputColorCount) { - return this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, this->options()); + return this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts); } void SkGifCodec::handleScanlineFrame(int count, int* rowsBeforeFrame, int* rowsInFrame) { diff --git a/src/codec/SkIcoCodec.cpp b/src/codec/SkIcoCodec.cpp index 0e81b72407..abcf4dc7d2 100644 --- a/src/codec/SkIcoCodec.cpp +++ b/src/codec/SkIcoCodec.cpp @@ -186,6 +186,7 @@ SkIcoCodec::SkIcoCodec(int width, int height, const SkEncodedInfo& info, : INHERITED(width, height, info, nullptr) , fEmbeddedCodecs(codecs) , fCurrScanlineCodec(nullptr) + , fCurrIncrementalCodec(nullptr) {} /* @@ -289,6 +290,7 @@ SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, result = embeddedCodec->startScanlineDecode(dstInfo, &options, colorTable, colorCount); if (kSuccess == result) { fCurrScanlineCodec = embeddedCodec; + fCurrIncrementalCodec = nullptr; return result; } @@ -309,13 +311,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; } diff --git a/src/codec/SkIcoCodec.h b/src/codec/SkIcoCodec.h index 8c56aee4c6..a227d8c671 100644 --- a/src/codec/SkIcoCodec.h +++ b/src/codec/SkIcoCodec.h @@ -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; }; diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp index 43d1580c17..f64843181d 100644 --- a/src/codec/SkPngCodec.cpp +++ b/src/codec/SkPngCodec.cpp @@ -23,27 +23,22 @@ // 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); @@ -58,9 +53,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. @@ -75,20 +83,117 @@ 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 info_ptr) { + // 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(); + + 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. -// TODO: If we add support for non-native swizzles, we'll need to handle that here. bool SkPngCodec::createColorTable(SkColorType dstColorType, bool premultiply, int* ctableCount) { int numColors; @@ -267,204 +372,254 @@ 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; -} - -// Subclass of SkPngCodec which supports scanline decoding -class SkPngScanlineDecoder : public SkPngCodec { +class SkPngNormalDecoder : public SkPngCodec { public: - SkPngScanlineDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream, - SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, int bitDepth, + SkPngNormalDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream, + SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth, sk_sp<SkColorSpace> colorSpace) - : INHERITED(width, height, info, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1, - colorSpace) - , fSrcRow(nullptr) + : INHERITED(width, height, info, stream, reader, png_ptr, info_ptr, bitDepth, + std::move(colorSpace)) + , 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())) { - return kInvalidConversion; - } + static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->allRowsCallback(row, rowNum); + } - const Result result = this->initializeSwizzler(dstInfo, options, ctable, - ctableCount); - if (result != kSuccess) { - return result; - } + static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->rowCallback(row, rowNum); + } +private: + int fLinesDecoded; // FIXME: Move to baseclass? + void* fDst; + size_t fRowBytes; + + // Variables for partial decode + int fFirstRow; // FIXME: Move to baseclass? + int fLastRow; - fStorage.reset(this->getInfo().width() * - (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()))); - fSrcRow = fStorage.get(); + typedef SkPngCodec INHERITED; - return kSuccess; + static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) { + return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr)); } - int onGetScanlines(void* dst, int count, size_t rowBytes) override { - // Assume that an error in libpng indicates an incomplete input. - int row = 0; - if (setjmp(png_jmpbuf(this->png_ptr()))) { - SkCodecPrintf("setjmp long jump!\n"); - return row; + Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->getInfo().height(); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, AllRowsCallback, nullptr); + fDst = dst; + fRowBytes = rowBytes; + + fLinesDecoded = 0; + + this->processData(); + + if (fLinesDecoded == height) { + return SkCodec::kSuccess; } - void* dstRow = dst; - for (; row < count; row++) { - png_read_row(this->png_ptr(), fSrcRow, nullptr); - this->swizzler()->swizzle(dstRow, fSrcRow); - dstRow = SkTAddOffset<void>(dstRow, rowBytes); + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; } - return row; + return SkCodec::kIncompleteInput; } - bool onSkipScanlines(int count) override { - // Assume that an error in libpng indicates an incomplete input. - if (setjmp(png_jmpbuf(this->png_ptr()))) { - SkCodecPrintf("setjmp long jump!\n"); - return false; + void allRowsCallback(png_bytep row, int rowNum) { + SkASSERT(rowNum - fFirstRow == fLinesDecoded); + fLinesDecoded++; + this->swizzler()->swizzle(fDst, row); + fDst = SkTAddOffset<void>(fDst, fRowBytes); + } + + void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, 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(this->png_ptr(), fSrcRow, nullptr); + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; } - return true; - } -private: - SkAutoTMalloc<uint8_t> fStorage; - uint8_t* fSrcRow; + return SkCodec::kIncompleteInput; + } - typedef SkPngCodec INHERITED; -}; + void rowCallback(png_bytep row, int rowNum) { + if (rowNum < fFirstRow) { + // Ignore this row. + return; + } + SkASSERT(rowNum <= fLastRow); -class SkPngInterlacedScanlineDecoder : public SkPngCodec { -public: - SkPngInterlacedScanlineDecoder(int width, int height, const SkEncodedInfo& info, - SkStream* stream, SkPngChunkReader* chunkReader, png_structp png_ptr, - png_infop info_ptr, int bitDepth, int numberPasses, sk_sp<SkColorSpace> colorSpace) - : INHERITED(width, height, info, stream, chunkReader, png_ptr, info_ptr, bitDepth, - numberPasses, colorSpace) - , fHeight(-1) - , fCanSkipRewind(false) - { - SkASSERT(numberPasses != 1); - } - - Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options, - SkPMColor ctable[], int* ctableCount) override { - if (!conversion_possible(dstInfo, this->getInfo())) { - return kInvalidConversion; + if (this->swizzler()->rowNeeded(fLinesDecoded)) { + this->swizzler()->swizzle(fDst, row); + fDst = SkTAddOffset<void>(fDst, fRowBytes); } - const Result result = this->initializeSwizzler(dstInfo, options, ctable, - ctableCount); - if (result != kSuccess) { - return result; + fLinesDecoded++; + + if (rowNum == fLastRow) { + // Fake error to stop decoding scanlines. + longjmp(png_jmpbuf(this->png_ptr()), kStopDecoding); } + } +}; - fHeight = dstInfo.height(); - // FIXME: This need not be called on a second call to onStartScanlineDecode. - fSrcRowBytes = this->getInfo().width() * - (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel())); - fGarbageRow.reset(fSrcRowBytes); - fGarbageRowPtr = static_cast<uint8_t*>(fGarbageRow.get()); - fCanSkipRewind = true; +class SkPngInterlacedDecoder : public SkPngCodec { +public: + SkPngInterlacedDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream, + SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth, + sk_sp<SkColorSpace> colorSpace, int numberPasses) + : INHERITED(width, height, info, stream, reader, png_ptr, info_ptr, bitDepth, + std::move(colorSpace)) + , fNumberPasses(numberPasses) + , fFirstRow(0) + , fLastRow(0) + , fLinesDecoded(0) + , fInterlacedComplete(false) + , fPng_rowbytes(0) + {} - return SkCodec::kSuccess; + 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); } - int onGetScanlines(void* dst, int count, size_t dstRowBytes) override { - // rewind stream if have previously called onGetScanlines, - // since we need entire progressive image to get scanlines - if (fCanSkipRewind) { - // We already rewound in onStartScanlineDecode, so there is no reason to rewind. - // Next time onGetScanlines is called, we will need to rewind. - fCanSkipRewind = false; +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; + + // 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; + } + + 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 { - // 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; + 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); } - this->updateCurrScanline(currScanline); } + } - if (setjmp(png_jmpbuf(this->png_ptr()))) { - SkCodecPrintf("setjmp long jump!\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; + SkCodec::Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->getInfo().height(); + this->setUpInterlaceBuffer(height); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, 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->swizzler()->swizzle(dst, srcRow); + dst = SkTAddOffset<void>(dst, rowBytes); + srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes); } - SkAutoTMalloc<uint8_t> storage(count * fSrcRowBytes); - uint8_t* storagePtr = storage.get(); - uint8_t* srcRow; - const int startRow = this->nextScanline(); - for (int i = 0; i < this->numberPasses(); i++) { - // read rows we planned to skip into garbage row - for (int y = 0; y < startRow; y++){ - png_read_row(this->png_ptr(), fGarbageRowPtr, nullptr); - } - // read rows we care about into buffer - srcRow = storagePtr; - for (int y = 0; y < count; y++) { - png_read_row(this->png_ptr(), srcRow, nullptr); - srcRow += fSrcRowBytes; - } - // read rows we don't want into garbage buffer - for (int y = 0; y < fHeight - startRow - count; y++) { - png_read_row(this->png_ptr(), fGarbageRowPtr, nullptr); - } + if (fInterlacedComplete) { + return SkCodec::kSuccess; } - //swizzle the rows we care about - srcRow = storagePtr; - void* dstRow = dst; - for (int y = 0; y < count; y++) { - this->swizzler()->swizzle(dstRow, srcRow); - dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); - srcRow += fSrcRowBytes; + + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; } - return count; + return SkCodec::kIncompleteInput; } - bool onSkipScanlines(int count) override { - // The non-virtual version will update fCurrScanline. - return true; + 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_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr); + fFirstRow = firstRow; + fLastRow = lastRow; + fDst = dst; + fRowBytes = rowBytes; + fLinesDecoded = 0; } - SkScanlineOrder onGetScanlineOrder() const override { - return kNone_SkScanlineOrder; - } + SkCodec::Result decode(int* rowsDecoded) override { + this->processData(); -private: - int fHeight; - size_t fSrcRowBytes; - SkAutoMalloc fGarbageRow; - uint8_t* fGarbageRowPtr; - // 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. - // SkPngInterlacedScanlineDecoder has an extra wrinkle - calling - // onStartScanlineDecode followed by onGetScanlines does *not* require a - // rewind. Since rewindIfNeeded does not have this flexibility, we need to - // add another layer. - bool fCanSkipRewind; + // Now call the callback 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()->sampleY(); + void* dst = fDst; + for (int rowNum = fFirstRow; rowNum <= lastRow; rowNum += sampleY) { + this->swizzler()->swizzle(dst, srcRow); + dst = SkTAddOffset<void>(dst, fRowBytes); + srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes * sampleY); + } - typedef SkPngCodec INHERITED; + if (fInterlacedComplete) { + return SkCodec::kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; + } + return SkCodec::kIncompleteInput; + } + + void setUpInterlaceBuffer(int height) { + fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr()); + fInterlaceBuffer.reset(fPng_rowbytes * height); + fInterlacedComplete = false; + } }; // Reads the header and initializes the output fields, if not NULL. @@ -492,7 +647,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) { @@ -507,8 +662,6 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec 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 @@ -519,12 +672,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; +} + +void AutoCleanPng::infoCallback() { png_uint_32 origWidth, origHeight; int bitDepth, encodedColorType; - png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, + png_get_IHDR(fPng_ptr, fInfo_ptr, &origWidth, &origHeight, &bitDepth, &encodedColorType, nullptr, nullptr, nullptr); // Tell libpng to strip 16 bit/color files down to 8 bits/color. @@ -532,7 +704,7 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec // RAW decodes? if (bitDepth == 16) { SkASSERT(PNG_COLOR_TYPE_PALETTE != encodedColorType); - png_set_strip_16(png_ptr); + png_set_strip_16(fPng_ptr); } // Now determine the default colorType and alphaType and set the required transforms. @@ -546,18 +718,18 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec // byte into separate bytes (useful for paletted and grayscale images). if (bitDepth < 8) { // TODO: Should we use SkSwizzler here? - png_set_packing(png_ptr); + png_set_packing(fPng_ptr); } color = SkEncodedInfo::kPalette_Color; // Set the alpha depending on if a transparency chunk exists. - alpha = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? + alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ? SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha; break; case PNG_COLOR_TYPE_RGB: - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { // Convert to RGBA if transparency chunk exists. - png_set_tRNS_to_alpha(png_ptr); + png_set_tRNS_to_alpha(fPng_ptr); color = SkEncodedInfo::kRGBA_Color; alpha = SkEncodedInfo::kBinary_Alpha; } else { @@ -569,11 +741,11 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. if (bitDepth < 8) { // TODO: Should we use SkSwizzler here? - png_set_expand_gray_1_2_4_to_8(png_ptr); + png_set_expand_gray_1_2_4_to_8(fPng_ptr); } - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - png_set_tRNS_to_alpha(png_ptr); + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(fPng_ptr); color = SkEncodedInfo::kGrayAlpha_Color; alpha = SkEncodedInfo::kBinary_Alpha; } else { @@ -596,45 +768,55 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec alpha = SkEncodedInfo::kUnpremul_Alpha; } - int numberPasses = png_set_interlace_handling(png_ptr); + const int numberPasses = png_set_interlace_handling(fPng_ptr); - autoClean.release(); - if (png_ptrp) { - *png_ptrp = png_ptr; + fReadHeader = true; +#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5) + // 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); + fDecodedBounds = true; +#else + // We may have read more than the header. Empty buffer and move to the end of the + // header so future calls can read the rows. + fDecodedBounds = fStream->move(-fPng_ptr->buffer_size); + fPng_ptr->buffer_size = 0; + if (!fDecodedBounds) { + // Stream could not be moved to the correct place. + return; } - if (info_ptrp) { - *info_ptrp = info_ptr; - } - - if (outCodec) { - sk_sp<SkColorSpace> colorSpace = read_color_space(png_ptr, info_ptr); +#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 info = SkEncodedInfo::Make(color, alpha, 8); - if (1 == numberPasses) { - *outCodec = new SkPngScanlineDecoder(origWidth, origHeight, info, stream, - chunkReader, png_ptr, info_ptr, bitDepth, colorSpace); + *fOutCodec = new SkPngNormalDecoder(origWidth, origHeight, info, fStream, + fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, std::move(colorSpace)); } else { - *outCodec = new SkPngInterlacedScanlineDecoder(origWidth, origHeight, info, stream, - chunkReader, png_ptr, info_ptr, bitDepth, numberPasses, colorSpace); + *fOutCodec = new SkPngInterlacedDecoder(origWidth, origHeight, info, fStream, + fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, std::move(colorSpace), + 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(int width, int height, const SkEncodedInfo& info, SkStream* stream, SkPngChunkReader* chunkReader, png_structp png_ptr, png_infop info_ptr, - int bitDepth, int numberPasses, sk_sp<SkColorSpace> colorSpace) + int bitDepth, sk_sp<SkColorSpace> colorSpace) : INHERITED(width, height, info, stream, colorSpace) , fPngChunkReader(SkSafeRef(chunkReader)) , fPng_ptr(png_ptr) , fInfo_ptr(info_ptr) - , fNumberPasses(numberPasses) , fBitDepth(bitDepth) {} @@ -656,22 +838,19 @@ void SkPngCodec::destroyReadStruct() { // Getting the pixels /////////////////////////////////////////////////////////////////////////////// -SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, - const Options& options, - SkPMColor ctable[], - int* ctableCount) { - // FIXME: Could we use the return value of setjmp to specify the type of - // error? +bool SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, + const Options& options, + SkPMColor ctable[], + int* ctableCount) { if (setjmp(png_jmpbuf(fPng_ptr))) { - SkCodecPrintf("setjmp long jump!\n"); - return kInvalidInput; + return false; } png_read_update_info(fPng_ptr, fInfo_ptr); if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { if (!this->createColorTable(requestedInfo.colorType(), kPremul_SkAlphaType == requestedInfo.alphaType(), ctableCount)) { - return kInvalidInput; + return false; } } @@ -684,7 +863,7 @@ SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, options)); SkASSERT(fSwizzler); - return kSuccess; + return true; } @@ -720,79 +899,40 @@ SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* } // Note that ctable and ctableCount may be modified if there is a color table - const Result result = this->initializeSwizzler(requestedInfo, options, ctable, ctableCount); - if (result != kSuccess) { - return result; + if (!this->initializeSwizzler(requestedInfo, options, ctable, ctableCount)) { + return kInvalidInput; // or parameters? } - const int width = requestedInfo.width(); - const int height = requestedInfo.height(); - const int bpp = bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()); - const size_t srcRowBytes = width * bpp; - - // FIXME: Could we use the return value of setjmp to specify the type of - // error? - int row = 0; - // This must be declared above the call to setjmp to avoid memory leaks on incomplete images. - SkAutoTMalloc<uint8_t> storage; - if (setjmp(png_jmpbuf(fPng_ptr))) { - // Assume that any error that occurs while reading rows is caused by an incomplete input. - if (fNumberPasses > 1) { - // FIXME (msarett): Handle incomplete interlaced pngs. - return (row == height) ? kSuccess : kInvalidInput; - } - // FIXME: We do a poor job on incomplete pngs compared to other decoders (ex: Chromium, - // Ubuntu Image Viewer). This is because we use the default buffer size in libpng (8192 - // bytes), and if we can't fill the buffer, we immediately fail. - // For example, if we try to read 8192 bytes, and the image (incorrectly) only contains - // half that, which may have been enough to contain a non-zero number of lines, we fail - // when we could have decoded a few more lines and then failed. - // The read function that we provide for libpng has no way of indicating that we have - // made a partial read. - // Making our buffer size smaller improves our incomplete decodes, but what impact does - // it have on regular decode performance? Should we investigate using a different API - // instead of png_read_row? Chromium uses png_process_data. - *rowsDecoded = row; - return (row == height) ? kSuccess : kIncompleteInput; - } - - // FIXME: We could split these out based on subclass. - void* dstRow = dst; - if (fNumberPasses > 1) { - storage.reset(height * srcRowBytes); - uint8_t* const base = storage.get(); - - for (int i = 0; i < fNumberPasses; i++) { - uint8_t* srcRow = base; - for (int y = 0; y < height; y++) { - png_read_row(fPng_ptr, srcRow, nullptr); - srcRow += srcRowBytes; - } - } + return this->decodeAllRows(dst, dstRowBytes, rowsDecoded); +} - // Now swizzle it. - uint8_t* srcRow = base; - for (; row < height; row++) { - fSwizzler->swizzle(dstRow, srcRow); - dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); - srcRow += srcRowBytes; - } - } else { - storage.reset(srcRowBytes); - uint8_t* srcRow = storage.get(); - for (; row < height; row++) { - png_read_row(fPng_ptr, srcRow, nullptr); - fSwizzler->swizzle(dstRow, srcRow); - dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); - } +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())) { + return kInvalidConversion; } - // read rest of file, and get additional comment and time chunks in info_ptr - png_read_end(fPng_ptr, fInfo_ptr); + if (!this->initializeSwizzler(dstInfo, options, ctable, ctableCount)) { + return kInvalidInput; + } + 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) { + return this->decode(rowsDecoded); +} + uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const { const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); if (colorPtr) { @@ -804,7 +944,7 @@ uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const { SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) { SkAutoTDelete<SkStream> streamDeleter(stream); - SkCodec* outCodec; + SkCodec* outCodec = nullptr; if (read_header(stream, chunkReader, &outCodec, nullptr, nullptr)) { // Codec has taken ownership of the stream. SkASSERT(outCodec); diff --git a/src/codec/SkPngCodec.h b/src/codec/SkPngCodec.h index 0a8878395a..51d8978199 100644 --- a/src/codec/SkPngCodec.h +++ b/src/codec/SkPngCodec.h @@ -27,6 +27,9 @@ public: virtual ~SkPngCodec(); protected: + SkPngCodec(int width, int height, const SkEncodedInfo&, SkStream*, SkPngChunkReader*, + png_structp, png_infop, int bitDepth, sk_sp<SkColorSpace>); + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, SkPMColor*, int*, int*) override; SkEncodedFormat onGetEncodedFormat() const override { return kPNG_SkEncodedFormat; } @@ -34,34 +37,46 @@ protected: uint32_t onGetFillValue(SkColorType) const override; // Helper to set up swizzler and color table. Also calls png_read_update_info. - Result initializeSwizzler(const SkImageInfo& requestedInfo, const Options&, - SkPMColor*, int* ctableCount); + bool initializeSwizzler(const SkImageInfo& requestedInfo, const Options&, + SkPMColor*, int* ctableCount); SkSampler* getSampler(bool createIfNecessary) override { SkASSERT(fSwizzler); return fSwizzler; } - SkPngCodec(int width, int height, const SkEncodedInfo&, SkStream*, SkPngChunkReader*, - png_structp, png_infop, int, int, sk_sp<SkColorSpace>); - png_structp png_ptr() { return fPng_ptr; } + png_infop info_ptr() { return fInfo_ptr; } SkSwizzler* swizzler() { return fSwizzler; } - int numberPasses() const { return fNumberPasses; } + + /** + * 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(); + + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const SkCodec::Options&, + SkPMColor* ctable, int* ctableCount) override; + Result onIncrementalDecode(int*) override; private: SkAutoTUnref<SkPngChunkReader> fPngChunkReader; png_structp fPng_ptr; png_infop fInfo_ptr; - // These are stored here so they can be used both by normal decoding and scanline decoding. SkAutoTUnref<SkColorTable> fColorTable; // May be unpremul. SkAutoTDelete<SkSwizzler> fSwizzler; - const int fNumberPasses; - int fBitDepth; + const int fBitDepth; bool createColorTable(SkColorType dstColorType, bool premultiply, int* ctableCount); void destroyReadStruct(); + 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; + typedef SkCodec INHERITED; }; diff --git a/src/codec/SkSampledCodec.cpp b/src/codec/SkSampledCodec.cpp index 49c939c1f8..8c39f277ae 100644 --- a/src/codec/SkSampledCodec.cpp +++ b/src/codec/SkSampledCodec.cpp @@ -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 when to start + // and end calling the callback. + 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,70 @@ SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pix sampledOptions.fSubset = ⊂ } + // 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 when to start + // and end calling the callback. + 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 +275,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 +282,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 +346,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; diff --git a/src/codec/SkSampler.h b/src/codec/SkSampler.h index 73e11c986e..af6f48598c 100644 --- a/src/codec/SkSampler.h +++ b/src/codec/SkSampler.h @@ -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 @@ -54,8 +78,13 @@ public: virtual void fill(const SkImageInfo& info, void* dst, size_t rowBytes, uint32_t colorOrIndex, SkCodec::ZeroInitialized zeroInit) {} + SkSampler() + : fSampleY(1) + {} + virtual ~SkSampler() {} private: + int fSampleY; virtual int onSetSampleX(int) = 0; }; |