Chromium Code Reviews| Index: src/codec/SkPngCodec.cpp |
| diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp |
| index 1ad7f8006ee85a0a8016e129dd15104993fe8108..d2c13f0d3964e28a8404196dcae3f4198b1d43a8 100644 |
| --- a/src/codec/SkPngCodec.cpp |
| +++ b/src/codec/SkPngCodec.cpp |
| @@ -32,18 +32,6 @@ 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 +46,21 @@ 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 them, as well as the |
|
msarett
2016/05/20 15:04:40
Codec refs but doesn't own the chunkReader?
scroggo_chromium
2016/05/20 16:51:59
I'm not sure what you're asking. Do you want a cle
msarett
2016/05/20 18:21:38
Yes thanks.
|
| + * 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) |
| + , fStream(stream) |
| + , fChunkReader(reader) |
| + , fOutCodec(codecPtr) |
| + {} |
| ~AutoCleanPng() { |
| // fInfo_ptr will never be non-nullptr unless fPng_ptr is. |
| @@ -75,20 +75,102 @@ public: |
| fInfo_ptr = info_ptr; |
| } |
| - void release() { |
| - fPng_ptr = nullptr; |
| - fInfo_ptr = nullptr; |
| + /** |
| + * 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(); |
| + |
| + /** |
| + * 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) { |
| + static_cast<AutoCleanPng*>(png_get_progressive_ptr(png_ptr))->infoCallback(); |
| } |
| private: |
| - png_structp fPng_ptr; |
| - png_infop fInfo_ptr; |
| + png_structp fPng_ptr; |
| + png_infop fInfo_ptr; |
| + bool fDecodedBounds; |
| + SkStream* fStream; |
| + SkPngChunkReader* fChunkReader; |
| + SkCodec** fOutCodec; |
| + |
| + void infoCallback(); |
| + |
| + void releasePngPtrs() { |
| + fPng_ptr = nullptr; |
| + fInfo_ptr = nullptr; |
| + } |
| }; |
| #define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng) |
| +bool AutoCleanPng::decodeBounds() { |
| + if (setjmp(png_jmpbuf(fPng_ptr))) { |
| + return false; |
| + } |
| + |
| + // 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. |
| + return false; |
| + } |
| + |
| + png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, bytesRead); |
| + if (fDecodedBounds) { |
| + return true; |
| + } |
| + } |
| +} |
| + |
| +void SkPngCodec::processData() { |
| + if (setjmp(png_jmpbuf(fPng_ptr))) { |
| + // FIXME: I think we need to throw out the png_ptr at this point. |
| + // A straightforward way to do that might be to set a flag telling |
| + // ourselves that we need to rewind. This makes sense if we |
| + // deliberately called png_error (i.e. when we decoded as many |
| + // scanlines as we wanted to), but maybe not otherwise (so maybe |
| + // we should do something there? what to do here?) |
| + return; |
| + } |
| + |
| + // 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; |
| @@ -261,204 +343,251 @@ 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)) |
|
msarett
2016/05/20 15:04:40
Why std::move?
scroggo_chromium
2016/05/20 16:51:59
My understanding of sk_sp is that if I do not std:
msarett
2016/05/20 18:21:38
That makes sense to me - thanks for the explanatio
|
| + , fLinesDecoded(0) |
| + , fDst(nullptr) |
| + , fRowBytes(0) |
| + , fFirstRow(0) |
| + , fLastRow(0) |
| + , fCallback(nullptr) |
| {} |
| - 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? |
| - fStorage.reset(this->getInfo().width() * |
| - (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()))); |
| - fSrcRow = fStorage.get(); |
| + // Variables for decodeAllRows/allRowsCallback |
| + void* fDst; |
| + size_t fRowBytes; |
| - return kSuccess; |
| + // Variables for partial decode |
| + int fFirstRow; // FIXME: Move to baseclass? |
| + int fLastRow; |
| + std::function<void*(int)> fCallback; |
| + |
| + typedef SkPngCodec INHERITED; |
| + |
| + static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) { |
| + return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr)); |
|
msarett
2016/05/20 15:04:41
I think I've figured it out, but maybe comment on
scroggo_chromium
2016/05/20 16:51:59
I'm not sure it's necessary to document the method
msarett
2016/05/20 18:21:38
Acknowledged.
|
| } |
| - 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; |
| + |
| + // FIXME: Make it possible to continue decoding all rows? |
|
msarett
2016/05/20 15:04:40
What does this mean?
scroggo_chromium
2016/05/20 16:51:59
This comment is probably out of place here. Remove
msarett
2016/05/20 18:21:38
Acknowledged.
|
| + 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) { |
|
msarett
2016/05/20 15:04:41
Should we set rowDecoded even if it's a success?
scroggo_chromium
2016/05/20 16:51:59
I don't think so. On success the client need not r
msarett
2016/05/20 18:21:38
Acknowledged.
|
| + *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) override { |
| + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, RowCallback, nullptr); |
| + fFirstRow = firstRow; |
| + fLastRow = lastRow; |
| + fLinesDecoded = 0; |
| + } |
| + |
| + SkCodec::Result decode(std::function<void*(int)> callback, int* rowsDecoded) override { |
| + fCallback = callback; |
| + |
| + 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; |
| + fLinesDecoded++; |
| + void* dst = fCallback(rowNum); |
| + if (dst) { |
| + this->swizzler()->swizzle(dst, row); |
| } |
| - const Result result = this->initializeSwizzler(dstInfo, options, ctable, |
| - ctableCount); |
| - if (result != kSuccess) { |
| - return result; |
| + if (rowNum == fLastRow) { |
| + png_error(this->png_ptr(), "Fake error to stop decoding scanlines."); |
|
msarett
2016/05/20 15:04:41
Can we pass more info to this call so, when we cat
scroggo_chromium
2016/05/20 16:51:59
Not to png_error, but we could call longjmp direct
scroggo_chromium
2016/05/23 21:00:10
Patch set 6 calls longjmp directly, and uses diffe
|
| } |
| + } |
| +}; |
| - 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; |
| + int fLinesDecoded; |
| + bool fInterlacedComplete; |
| + png_uint_32 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; |
| + png_error(this->png_ptr(), "Fake error to stop interlaced scanline decoding"); |
| } |
| - this->updateCurrScanline(currScanline); |
| } |
| + } |
| + |
| + 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); |
| + |
| + // FIXME: Make it possible to continue decoding all rows? |
| + fFirstRow = 0; |
| + fLastRow = height - 1; |
| + fLinesDecoded = 0; |
| + |
| + this->processData(); |
| - 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; |
| + png_bytep srcRow = fInterlaceBuffer.get(); |
| + // FIXME: When resuming, this may rewrite rows that did not change. |
| + for (int rowNum = 0; rowNum <= fLastRow; 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) { |
|
msarett
2016/05/20 15:04:40
Same question as above... Should we still set row
scroggo_chromium
2016/05/20 16:51:59
Same answer as above :) I don't think we need to,
|
| + *rowsDecoded = fLinesDecoded; |
|
msarett
2016/05/20 15:04:40
This is what we want for filling... But is it wei
scroggo_chromium
2016/05/20 16:51:59
Perhaps weird, but I don't know why else the clien
msarett
2016/05/20 18:21:38
Thanks for the comment. I don't feel strongly abo
|
| } |
| - 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) override { |
| + this->setUpInterlaceBuffer(lastRow - firstRow + 1); |
| + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr); |
| + fFirstRow = firstRow; |
| + fLastRow = lastRow; |
| + fLinesDecoded = 0; |
| } |
| - SkScanlineOrder onGetScanlineOrder() const override { |
| - return kNone_SkScanlineOrder; |
| - } |
| + SkCodec::Result decode(std::function<void*(int)> callback, 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(); |
| + for (int rowNum = fFirstRow; rowNum <= lastRow; rowNum++, srcRow += fPng_rowbytes) { |
| + void* dst = callback(rowNum); |
| + if (dst) { |
| + this->swizzler()->swizzle(dst, srcRow); |
| + } |
| + } |
| - 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. |
| @@ -486,7 +615,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) { |
| @@ -501,8 +630,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 |
| @@ -513,12 +640,34 @@ 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); |
| + png_set_progressive_read_fn(png_ptr, &autoClean, AutoCleanPng::InfoCallback, nullptr, nullptr); |
| + const bool decodedBounds = autoClean.decodeBounds(); |
| + // For safety, clear the pointers to autoClean. |
| + png_set_progressive_read_fn(png_ptr, nullptr, nullptr, nullptr, nullptr); |
| + |
| + 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. |
| @@ -526,7 +675,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. |
| @@ -540,18 +689,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 { |
| @@ -563,11 +712,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 { |
| @@ -590,40 +739,39 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec |
| alpha = SkEncodedInfo::kUnpremul_Alpha; |
| } |
| - int numberPasses = png_set_interlace_handling(png_ptr); |
| - |
| - autoClean.release(); |
| - if (png_ptrp) { |
| - *png_ptrp = png_ptr; |
| - } |
| - if (info_ptrp) { |
| - *info_ptrp = info_ptr; |
| - } |
| + const int numberPasses = png_set_interlace_handling(fPng_ptr); |
| - if (outCodec) { |
| - sk_sp<SkColorSpace> colorSpace = read_color_space(png_ptr, info_ptr); |
| + if (fOutCodec) { |
| + SkASSERT(nullptr == *fOutCodec); |
| + sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr); |
| 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; |
| + fDecodedBounds = true; |
| + // 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); |
| + |
| + // 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) |
| {} |
| @@ -645,22 +793,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; |
| } |
| } |
| @@ -673,7 +818,7 @@ SkCodec::Result SkPngCodec::initializeSwizzler(const SkImageInfo& requestedInfo, |
| options)); |
| SkASSERT(fSwizzler); |
| - return kSuccess; |
| + return true; |
| } |
| @@ -709,79 +854,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, |
| + 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); |
| return kSuccess; |
| } |
| +SkCodec::Result SkPngCodec::onIncrementalDecode(std::function<void*(int)> callback, |
| + int* rowsDecoded) { |
| + return this->decode(callback, rowsDecoded); |
| +} |
| + |
| uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const { |
| const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); |
| if (colorPtr) { |
| @@ -793,7 +899,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); |