Chromium Code Reviews| Index: src/codec/SkPngCodec.cpp |
| diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp |
| index a5ff9fcc96655b9cd36987b89ba5409e4ae83e7b..682b10e248f11ec618c99f70ac58b35120b70930 100644 |
| --- a/src/codec/SkPngCodec.cpp |
| +++ b/src/codec/SkPngCodec.cpp |
| @@ -261,6 +261,206 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { |
| return nullptr; |
| } |
| +static int bytes_per_pixel(int bitsPerPixel) { |
|
scroggo
2016/04/25 15:10:43
Before I scan this 200 line block line by line to
msarett
2016/04/25 15:18:21
Sorry I should I have mentioned - yes this is all
|
| + // 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 { |
| +public: |
| + SkPngScanlineDecoder(int width, int height, const SkEncodedInfo& info, SkStream* stream, |
| + SkPngChunkReader* chunkReader, 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) |
| + {} |
| + |
| + Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options, |
| + SkPMColor ctable[], int* ctableCount) override { |
| + if (!conversion_possible(dstInfo, this->getInfo())) { |
| + return kInvalidConversion; |
| + } |
| + |
| + const Result result = this->initializeSwizzler(dstInfo, options, ctable, |
| + ctableCount); |
| + if (result != kSuccess) { |
| + return result; |
| + } |
| + |
| + fStorage.reset(this->getInfo().width() * |
| + (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()))); |
| + fSrcRow = fStorage.get(); |
| + |
| + return kSuccess; |
| + } |
| + |
| + 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; |
| + } |
| + |
| + 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); |
| + } |
| + |
| + return row; |
| + } |
| + |
| + 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; |
| + } |
| + |
| + for (int row = 0; row < count; row++) { |
| + png_read_row(this->png_ptr(), fSrcRow, nullptr); |
| + } |
| + return true; |
| + } |
| + |
| +private: |
| + SkAutoTMalloc<uint8_t> fStorage; |
| + uint8_t* fSrcRow; |
| + |
| + typedef SkPngCodec INHERITED; |
| +}; |
| + |
| + |
| +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; |
| + } |
| + |
| + const Result result = this->initializeSwizzler(dstInfo, options, ctable, |
| + ctableCount); |
| + if (result != kSuccess) { |
| + return result; |
| + } |
| + |
| + 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; |
| + |
| + return SkCodec::kSuccess; |
| + } |
| + |
| + 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; |
| + } else { |
| + // rewindIfNeeded resets fCurrScanline, since it assumes that start |
| + // needs to be called again before scanline decoding. PNG scanline |
| + // decoding is the exception, since it needs to rewind between |
| + // calls to getScanlines. Keep track of fCurrScanline, to undo the |
| + // reset. |
| + const int currScanline = this->nextScanline(); |
| + // This method would never be called if currScanline is -1 |
| + SkASSERT(currScanline != -1); |
| + |
| + if (!this->rewindIfNeeded()) { |
| + return kCouldNotRewind; |
| + } |
| + this->updateCurrScanline(currScanline); |
| + } |
| + |
| + 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; |
| + } |
| + 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); |
| + } |
| + } |
| + //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; |
| + } |
| + |
| + return count; |
| + } |
| + |
| + bool onSkipScanlines(int count) override { |
| + // The non-virtual version will update fCurrScanline. |
| + return true; |
| + } |
| + |
| + SkScanlineOrder onGetScanlineOrder() const override { |
| + return kNone_SkScanlineOrder; |
| + } |
| + |
| +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; |
| + |
| + typedef SkPngCodec INHERITED; |
| +}; |
| + |
| // Reads the header and initializes the output fields, if not NULL. |
| // |
| // @param stream Input data. Will be read to get enough information to properly |
| @@ -268,23 +468,17 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { |
| // @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL. |
| // If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is |
| // expected to continue to own it for the lifetime of the png_ptr. |
| +// @param outCodec Optional output variable. If non-NULL, will be set to a new |
| +// SkPngCodec on success. |
| // @param png_ptrp Optional output variable. If non-NULL, will be set to a new |
| // png_structp on success. |
| // @param info_ptrp Optional output variable. If non-NULL, will be set to a new |
| // png_infop on success; |
| -// @param info Optional output variable. If non-NULL, will be set to |
| -// reflect the properties of the encoded image on success. |
| -// @param bitDepthPtr Optional output variable. If non-NULL, will be set to the |
| -// bit depth of the encoded image on success. |
| -// @param numberPassesPtr Optional output variable. If non-NULL, will be set to |
| -// the number_passes of the encoded image on success. |
| // @return true on success, in which case the caller is responsible for calling |
| // png_destroy_read_struct(png_ptrp, info_ptrp). |
| // If it returns false, the passed in fields (except stream) are unchanged. |
| -static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
| - png_structp* png_ptrp, png_infop* info_ptrp, |
| - int* width, int* height, SkEncodedInfo* info, int* bitDepthPtr, |
| - int* numberPassesPtr) { |
| +static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, SkCodec** outCodec, |
| + png_structp* png_ptrp, png_infop* info_ptrp) { |
| // The image is known to be a PNG. Decode enough to know the SkImageInfo. |
| png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, |
| sk_error_fn, sk_warning_fn); |
| @@ -327,10 +521,6 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
| png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth, |
| &encodedColorType, nullptr, nullptr, nullptr); |
| - if (bitDepthPtr) { |
| - *bitDepthPtr = bitDepth; |
| - } |
| - |
| // Tell libpng to strip 16 bit/color files down to 8 bits/color. |
| // TODO: Should we handle this in SkSwizzler? Could this also benefit |
| // RAW decodes? |
| @@ -401,19 +591,7 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
| } |
| int numberPasses = png_set_interlace_handling(png_ptr); |
| - if (numberPassesPtr) { |
| - *numberPassesPtr = numberPasses; |
| - } |
| - if (info) { |
| - *info = SkEncodedInfo::Make(color, alpha, 8); |
| - } |
| - if (width) { |
| - *width = origWidth; |
| - } |
| - if (height) { |
| - *height = origHeight; |
| - } |
| autoClean.release(); |
| if (png_ptrp) { |
| *png_ptrp = png_ptr; |
| @@ -422,6 +600,19 @@ static bool read_header(SkStream* stream, SkPngChunkReader* chunkReader, |
| *info_ptrp = info_ptr; |
| } |
| + if (outCodec) { |
| + sk_sp<SkColorSpace> colorSpace = read_color_space(png_ptr, info_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); |
| + } |
| + |
| + *outCodec = new SkPngInterlacedScanlineDecoder(origWidth, origHeight, info, stream, |
| + chunkReader, png_ptr, info_ptr, bitDepth, numberPasses, colorSpace); |
| + } |
| + |
| return true; |
| } |
| @@ -496,8 +687,7 @@ bool SkPngCodec::onRewind() { |
| png_structp png_ptr; |
| png_infop info_ptr; |
| - if (!read_header(this->stream(), fPngChunkReader.get(), &png_ptr, &info_ptr, |
| - nullptr, nullptr, nullptr, nullptr, nullptr)) { |
| + if (!read_header(this->stream(), fPngChunkReader.get(), nullptr, &png_ptr, &info_ptr)) { |
| return false; |
| } |
| @@ -506,12 +696,6 @@ bool SkPngCodec::onRewind() { |
| return true; |
| } |
| -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; |
| -} |
| - |
| SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, |
| size_t dstRowBytes, const Options& options, |
| SkPMColor ctable[], int* ctableCount, |
| @@ -606,222 +790,16 @@ uint32_t SkPngCodec::onGetFillValue(SkColorType colorType) const { |
| return INHERITED::onGetFillValue(colorType); |
| } |
| -// Subclass of SkPngCodec which supports scanline decoding |
| -class SkPngScanlineDecoder : 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, |
| - sk_sp<SkColorSpace> colorSpace) |
| - : INHERITED(width, height, info, stream, chunkReader, png_ptr, info_ptr, bitDepth, 1, |
| - colorSpace) |
| - , fSrcRow(nullptr) |
| - {} |
| - |
| - Result onStartScanlineDecode(const SkImageInfo& dstInfo, const Options& options, |
| - SkPMColor ctable[], int* ctableCount) override { |
| - if (!conversion_possible(dstInfo, this->getInfo())) { |
| - return kInvalidConversion; |
| - } |
| - |
| - const Result result = this->initializeSwizzler(dstInfo, options, ctable, |
| - ctableCount); |
| - if (result != kSuccess) { |
| - return result; |
| - } |
| - |
| - fStorage.reset(this->getInfo().width() * |
| - (bytes_per_pixel(this->getEncodedInfo().bitsPerPixel()))); |
| - fSrcRow = fStorage.get(); |
| - |
| - return kSuccess; |
| - } |
| - |
| - 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; |
| - } |
| - |
| - 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); |
| - } |
| - |
| - return row; |
| - } |
| - |
| - 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; |
| - } |
| - |
| - for (int row = 0; row < count; row++) { |
| - png_read_row(this->png_ptr(), fSrcRow, nullptr); |
| - } |
| - return true; |
| - } |
| - |
| -private: |
| - SkAutoTMalloc<uint8_t> fStorage; |
| - uint8_t* fSrcRow; |
| - |
| - typedef SkPngCodec INHERITED; |
| -}; |
| - |
| - |
| -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; |
| - } |
| - |
| - const Result result = this->initializeSwizzler(dstInfo, options, ctable, |
| - ctableCount); |
| - if (result != kSuccess) { |
| - return result; |
| - } |
| - |
| - 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; |
| - |
| - return SkCodec::kSuccess; |
| - } |
| - |
| - 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; |
| - } else { |
| - // rewindIfNeeded resets fCurrScanline, since it assumes that start |
| - // needs to be called again before scanline decoding. PNG scanline |
| - // decoding is the exception, since it needs to rewind between |
| - // calls to getScanlines. Keep track of fCurrScanline, to undo the |
| - // reset. |
| - const int currScanline = this->nextScanline(); |
| - // This method would never be called if currScanline is -1 |
| - SkASSERT(currScanline != -1); |
| - |
| - if (!this->rewindIfNeeded()) { |
| - return kCouldNotRewind; |
| - } |
| - this->updateCurrScanline(currScanline); |
| - } |
| - |
| - 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; |
| - } |
| - 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); |
| - } |
| - } |
| - //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; |
| - } |
| - |
| - return count; |
| - } |
| - |
| - bool onSkipScanlines(int count) override { |
| - // The non-virtual version will update fCurrScanline. |
| - return true; |
| - } |
| - |
| - SkScanlineOrder onGetScanlineOrder() const override { |
| - return kNone_SkScanlineOrder; |
| - } |
| - |
| -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; |
| - |
| - typedef SkPngCodec INHERITED; |
| -}; |
| - |
| SkCodec* SkPngCodec::NewFromStream(SkStream* stream, SkPngChunkReader* chunkReader) { |
| SkAutoTDelete<SkStream> streamDeleter(stream); |
| - png_structp png_ptr; |
| - png_infop info_ptr; |
| - int width, height; |
| - SkEncodedInfo imageInfo; |
| - int bitDepth; |
| - int numberPasses; |
| - |
| - if (!read_header(stream, chunkReader, &png_ptr, &info_ptr, &width, &height, &imageInfo, |
| - &bitDepth, &numberPasses)) { |
| - return nullptr; |
| - } |
| - |
| - auto colorSpace = read_color_space(png_ptr, info_ptr); |
| - if (1 == numberPasses) { |
| - return new SkPngScanlineDecoder(width, height, imageInfo, streamDeleter.release(), |
| - chunkReader, png_ptr, info_ptr, bitDepth, colorSpace); |
| + SkCodec* outCodec; |
| + if (read_header(stream, chunkReader, &outCodec, nullptr, nullptr)) { |
| + // Codec has taken ownership of the stream. |
| + SkASSERT(outCodec); |
| + streamDeleter.release(); |
| + return outCodec; |
| } |
| - return new SkPngInterlacedScanlineDecoder(width, height, imageInfo, streamDeleter.release(), |
| - chunkReader, png_ptr, info_ptr, bitDepth, |
| - numberPasses, colorSpace); |
| + return nullptr; |
| } |