Index: src/codec/SkPngCodec.cpp |
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp |
index a5ff9fcc96655b9cd36987b89ba5409e4ae83e7b..1ad7f8006ee85a0a8016e129dd15104993fe8108 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) { |
+ // 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); |
+ } else { |
+ *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; |
} |