Chromium Code Reviews| Index: third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
| diff --git a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
| index 8e6e19d0163bf49d3634bb637d2d5039c3972b7a..e810f58aee587fc40c2cb4e68181fb952b658196 100644 |
| --- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
| +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
| @@ -40,7 +40,6 @@ |
| #include "platform/image-decoders/png/PNGImageReader.h" |
| #include "png.h" |
| -#include "wtf/PtrUtil.h" |
| #include <memory> |
| namespace blink { |
| @@ -50,14 +49,157 @@ PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption, |
| sk_sp<SkColorSpace> targetColorSpace, |
| size_t maxDecodedBytes, |
| size_t offset) |
| - : ImageDecoder(alphaOption, |
| - colorOptions, |
| - std::move(targetColorSpace), |
| + : ImageDecoder(alphaOption, colorOptions, std::move(targetColorSpace), |
| maxDecodedBytes), |
| - m_offset(offset) {} |
| + m_frameCount(0), |
| + m_currentFrame(0), |
| + m_repetitionCount(cAnimationLoopOnce), |
| + m_hasAlphaChannel(false), |
| + m_currentBufferSawAlpha(false), |
| + m_isParsing(false) |
| +{ |
| + m_reader = wrapUnique(new PNGImageReader(this, offset)); |
|
scroggo_chromium
2016/12/08 13:59:09
Now that m_reader has the same lifetime as PNGImag
joostouwerling
2016/12/08 16:28:39
That's difficult since they cross reference each o
scroggo_chromium
2016/12/08 16:33:25
What about if you move PNGParseQuery to PNGImageRe
joostouwerling
2016/12/08 21:33:20
Done.
|
| +} |
| PNGImageDecoder::~PNGImageDecoder() {} |
| +bool PNGImageDecoder::setFailed() { |
| + // Update the frame count to make sure it reflects the most up to date number |
| + // of frames there were parsed. We don't want to set the decoder to the failed |
|
scroggo_chromium
2016/12/08 13:59:09
nit: I think this sounds better if you switch "the
joostouwerling
2016/12/08 16:28:39
Done.
|
| + // state if the reader was able to successfully parse some frames. |
| + if (m_isParsing) |
| + m_frameCount = m_reader->frameCount(); |
| + |
| + // There are three cases in which the decoder is invalidated: |
| + // 1) No frames are received so far. |
| + // 2) The image is not animated. |
| + // 3) Decoding for the first frame fails. |
| + if (m_frameCount == 0 || (m_reader->parseCompleted() && m_frameCount == 1) || |
| + (!m_isParsing && m_currentFrame == 0)) |
| + return ImageDecoder::setFailed(); |
| + |
| + // If decoding fails for later frames, we still want to show earlier frames, |
| + // but the frame count needs to be adjusted. We do not want to shrink |
| + // |m_frameBufferCache|, since clients may store pointers to later frames. |
| + if (!m_isParsing) { |
| + m_frameBufferCache[m_currentFrame].clearPixelData(); |
| + m_frameBufferCache[m_currentFrame].setStatus(ImageFrame::FrameEmpty); |
| + m_frameCount = m_currentFrame; |
| + } |
| + |
| + // Set parseCompleted to true, so parse() returns early and thus won't update |
| + // |m_frameCount| to the pre-failure value when it is called again. |
| + m_reader->setParseCompleted(true); |
| + return false; |
| +} |
| + |
| +size_t PNGImageDecoder::decodeFrameCount() { |
| + parse(PNGParseQuery::PNGMetaDataQuery); |
| + return m_frameCount; |
| +} |
| + |
| +void PNGImageDecoder::decode(size_t index) { |
| + if (failed()) |
| + return; |
| + |
| + updateAggressivePurging(index); |
| + |
| + Vector<size_t> framesToDecode = findFramesToDecode(index); |
| + for (auto i = framesToDecode.rbegin(); i != framesToDecode.rend(); i++) { |
| + m_currentFrame = *i; |
| + m_reader->decode(*m_data, *i); |
| + if (failed() || !postDecodeProcessing(*i)) |
| + return; |
| + } |
| +} |
| + |
| +void PNGImageDecoder::clearFrameBuffer(size_t frameIndex) { |
| + m_reader->clearDecodeState(frameIndex); |
| + m_frameBufferCache[frameIndex].clearPixelData(); |
|
scroggo_chromium
2016/12/08 13:59:09
For consistency with the other decoders, you might
joostouwerling
2016/12/08 16:28:39
Calling ImageDecoder::clearFrameBuffer may be the
|
| +} |
| + |
| +void PNGImageDecoder::onInitFrameBuffer(size_t index) { |
| + unsigned colorChannels = m_hasAlphaChannel ? 4 : 3; |
| + if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(m_reader->pngPtr(), |
| + m_reader->infoPtr())) { |
| + m_reader->createInterlaceBuffer(colorChannels * size().width() * |
| + size().height()); |
| + if (!m_reader->interlaceBuffer()) |
| + setFailed(); |
| + } |
| +} |
| + |
| +bool PNGImageDecoder::canReusePreviousFrameBuffer(size_t frameIndex) const { |
| + DCHECK(frameIndex < m_frameBufferCache.size()); |
| + return m_frameBufferCache[frameIndex].getDisposalMethod() != |
| + ImageFrame::DisposeOverwritePrevious; |
| +} |
| + |
| +void PNGImageDecoder::parse(PNGParseQuery query) { |
| + if (failed() || m_reader->parseCompleted()) |
| + return; |
| + |
| + m_isParsing = true; |
| + if (!m_reader->parse(*m_data, query) && isAllDataReceived()) |
| + setFailed(); |
| + else if (query == PNGParseQuery::PNGMetaDataQuery) |
| + m_frameCount = m_reader->frameCount(); |
| + |
| + m_isParsing = false; |
| +} |
| + |
| +void PNGImageDecoder::setRepetitionCount(size_t repetitionCount) { |
| + m_repetitionCount = |
| + (repetitionCount == 0) ? cAnimationLoopInfinite : repetitionCount; |
| +} |
| + |
| +// This matches the existing behavior to loop once if decoding fails, but this |
| +// should be changed to stick with m_repetitionCount to match other browsers. |
| +// See crbug.com/267883 |
| +int PNGImageDecoder::repetitionCount() const { |
| + if (m_reader->parseCompleted() && m_reader->frameCount() == 1) |
| + return cAnimationNone; |
| + return failed() ? cAnimationLoopOnce : m_repetitionCount; |
| +} |
| + |
| +// These are mapped according to: |
| +// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk |
| +static inline ImageFrame::DisposalMethod getDisposalMethod( |
| + uint8_t disposalMethod) { |
| + switch (disposalMethod) { |
| + case 0: |
| + return ImageFrame::DisposalMethod::DisposeKeep; |
| + case 1: |
| + return ImageFrame::DisposalMethod::DisposeOverwriteBgcolor; |
| + case 2: |
| + return ImageFrame::DisposalMethod::DisposeOverwritePrevious; |
| + default: |
| + return ImageFrame::DisposalMethod::DisposeNotSpecified; |
| + } |
| +} |
| + |
| +// These are mapped according to: |
| +// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk |
| +static inline ImageFrame::AlphaBlendSource getAlphaBlend(uint8_t alphaBlend) { |
| + if (alphaBlend == 1) |
| + return ImageFrame::AlphaBlendSource::BlendAtopPreviousFrame; |
| + return ImageFrame::AlphaBlendSource::BlendAtopBgcolor; |
| +} |
| + |
| +void PNGImageDecoder::initializeNewFrame(size_t index) { |
| + const PNGImageReader::FrameInfo& frameInfo = m_reader->frameInfo(index); |
| + ImageFrame* buffer = &m_frameBufferCache[index]; |
| + |
| + IntRect frameRectWithinSize = |
| + intersection(frameInfo.frameRect, {IntPoint(), size()}); |
| + buffer->setOriginalFrameRect(frameRectWithinSize); |
| + buffer->setDuration(frameInfo.duration); |
| + buffer->setDisposalMethod(getDisposalMethod(frameInfo.disposalMethod)); |
| + buffer->setAlphaBlendSource(getAlphaBlend(frameInfo.alphaBlend)); |
| + buffer->setRequiredPreviousFrameIndex( |
| + findRequiredPreviousFrame(index, false)); |
| +} |
| + |
| inline float pngFixedToFloat(png_fixed_point x) { |
| return ((float)x) * 0.00001f; |
| } |
| @@ -115,22 +257,8 @@ inline sk_sp<SkColorSpace> readColorSpace(png_structp png, png_infop info) { |
| void PNGImageDecoder::headerAvailable() { |
| png_structp png = m_reader->pngPtr(); |
| png_infop info = m_reader->infoPtr(); |
| - png_uint_32 width = png_get_image_width(png, info); |
| - png_uint_32 height = png_get_image_height(png, info); |
| - |
| - // Protect against large PNGs. See http://bugzil.la/251381 for more details. |
| - const unsigned long maxPNGSize = 1000000UL; |
| - if (width > maxPNGSize || height > maxPNGSize) { |
| - longjmp(JMPBUF(png), 1); |
| - return; |
| - } |
| - |
| - // Set the image size now that the image header is available. |
| - if (!setSize(width, height)) { |
| - longjmp(JMPBUF(png), 1); |
| - return; |
| - } |
| + png_uint_32 width, height; |
| int bitDepth, colorType, interlaceType, compressionType, filterType, channels; |
| png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, |
| &interlaceType, &compressionType, &filterType); |
| @@ -156,16 +284,36 @@ void PNGImageDecoder::headerAvailable() { |
| colorType == PNG_COLOR_TYPE_GRAY_ALPHA) |
| png_set_gray_to_rgb(png); |
| - if ((colorType & PNG_COLOR_MASK_COLOR) && !ignoresColorSpace()) { |
| - // We only support color profiles for color PALETTE and RGB[A] PNG. |
| - // Supporting color profiles for gray-scale images is slightly tricky, at |
| - // least using the CoreGraphics ICC library, because we expand gray-scale |
| - // images to RGB but we do not similarly transform the color profile. We'd |
| - // either need to transform the color profile or we'd need to decode into a |
| - // gray-scale image buffer and hand that to CoreGraphics. |
| - sk_sp<SkColorSpace> colorSpace = readColorSpace(png, info); |
| - if (colorSpace) { |
| - setEmbeddedColorSpace(colorSpace); |
| + // Only set the size and the color space of the image once. Since non-first |
| + // frames also use this method, we don't want them to override the size of |
| + // the image to the size of their frame rect. Frames also don't specify their |
| + // own color space, so we need to set it only once. |
| + if (!isDecodedSizeAvailable()) { |
| + // Protect against large PNGs. See http://bugzil.la/251381 for more details. |
| + const unsigned long maxPNGSize = 1000000UL; |
| + if (width > maxPNGSize || height > maxPNGSize) { |
| + longjmp(JMPBUF(png), 1); |
| + return; |
| + } |
| + |
| + // Set the image size now that the image header is available. |
| + if (!setSize(width, height)) { |
| + longjmp(JMPBUF(png), 1); |
| + return; |
| + } |
| + |
| + if ((colorType & PNG_COLOR_MASK_COLOR) && !ignoresColorSpace()) { |
| + // We only support color profiles for color PALETTE and RGB[A] PNG. |
| + // Supporting color profiles for gray-scale images is slightly tricky, at |
| + // least using the CoreGraphics ICC library, because we expand gray-scale |
| + // images to RGB but we do not similarly transform the color profile. We'd |
| + // either need to transform the color profile or we'd need to decode into |
| + // a |
| + // gray-scale image buffer and hand that to CoreGraphics. |
| + sk_sp<SkColorSpace> colorSpace = readColorSpace(png, info); |
| + if (colorSpace) { |
| + setEmbeddedColorSpace(colorSpace); |
| + } |
| } |
| } |
| @@ -203,20 +351,8 @@ void PNGImageDecoder::headerAvailable() { |
| channels = png_get_channels(png, info); |
| ASSERT(channels == 3 || channels == 4); |
| - m_reader->setHasAlpha(channels == 4); |
| - |
| - if (m_reader->decodingSizeOnly()) { |
| -// If we only needed the size, halt the reader. |
| -#if PNG_LIBPNG_VER_MAJOR > 1 || \ |
| - (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5) |
| - // Passing '0' tells png_process_data_pause() not to cache unprocessed data. |
| - m_reader->setReadOffset(m_reader->currentBufferSize() - |
| - png_process_data_pause(png, 0)); |
| -#else |
| - m_reader->setReadOffset(m_reader->currentBufferSize() - png->buffer_size); |
| - png->buffer_size = 0; |
| -#endif |
| - } |
| + m_hasAlphaChannel = (channels == 4); |
| + m_currentBufferSawAlpha = false; |
| } |
| void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, |
| @@ -225,75 +361,60 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, |
| if (m_frameBufferCache.isEmpty()) |
| return; |
| - // Initialize the framebuffer if needed. |
| - ImageFrame& buffer = m_frameBufferCache[0]; |
| - if (buffer.getStatus() == ImageFrame::FrameEmpty) { |
| - png_structp png = m_reader->pngPtr(); |
| - if (!buffer.setSizeAndColorSpace(size().width(), size().height(), |
| - colorSpaceForSkImages())) { |
| - longjmp(JMPBUF(png), 1); |
| - return; |
| - } |
| - |
| - unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3; |
| - if (PNG_INTERLACE_ADAM7 == |
| - png_get_interlace_type(png, m_reader->infoPtr())) { |
| - m_reader->createInterlaceBuffer(colorChannels * size().width() * |
| - size().height()); |
| - if (!m_reader->interlaceBuffer()) { |
| - longjmp(JMPBUF(png), 1); |
| - return; |
| - } |
| - } |
| - |
| - buffer.setStatus(ImageFrame::FramePartial); |
| - buffer.setHasAlpha(false); |
| - |
| - // For PNGs, the frame always fills the entire image. |
| - buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); |
| + if (!initFrameBuffer(m_currentFrame)) { |
| + setFailed(); |
| + return; |
| } |
| + // This frameRect is already clipped, so that it fits within the size of the |
| + // image. This is done in initializeNewFrame() after a frameCount() call. |
| + ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; |
| + const IntRect& frameRect = buffer.originalFrameRect(); |
| + |
| /* libpng comments (here to explain what follows). |
| - * |
| - * this function is called for every row in the image. If the |
| - * image is interlacing, and you turned on the interlace handler, |
| - * this function will be called for every row in every pass. |
| - * Some of these rows will not be changed from the previous pass. |
| - * When the row is not changed, the new_row variable will be NULL. |
| - * The rows and passes are called in order, so you don't really |
| - * need the row_num and pass, but I'm supplying them because it |
| - * may make your life easier. |
| - */ |
| - |
| - // Nothing to do if the row is unchanged, or the row is outside |
| - // the image bounds: libpng may send extra rows, ignore them to |
| - // make our lives easier. |
| + * |
| + * this function is called for every row in the image. If the |
| + * image is interlacing, and you turned on the interlace handler, |
| + * this function will be called for every row in every pass. |
| + * Some of these rows will not be changed from the previous pass. |
| + * When the row is not changed, the new_row variable will be NULL. |
| + * The rows and passes are called in order, so you don't really |
| + * need the row_num and pass, but I'm supplying them because it |
| + * may make your life easier. |
| + */ |
| + |
| + // Nothing to do if the row is unchanged, or the row is outside the image |
| + // bounds. In the case that a frame presents more data than the indicated |
| + // frame size, ignore the extra rows and thus use the frame size as the |
| + // source of truth. libpng may also send extra rows. Ignore those as well. |
| + // This prevents us from trying to write outside of the image bounds. |
| if (!rowBuffer) |
| return; |
| - int y = rowIndex; |
| - if (y < 0 || y >= size().height()) |
| + int y = rowIndex + frameRect.y(); |
| + DCHECK_GE(y, 0); |
| + if (y >= size().height()) |
| return; |
| /* libpng comments (continued). |
| - * |
| - * For the non-NULL rows of interlaced images, you must call |
| - * png_progressive_combine_row() passing in the row and the |
| - * old row. You can call this function for NULL rows (it will |
| - * just return) and for non-interlaced images (it just does the |
| - * memcpy for you) if it will make the code easier. Thus, you |
| - * can just do this for all cases: |
| - * |
| - * png_progressive_combine_row(png_ptr, old_row, new_row); |
| - * |
| - * where old_row is what was displayed for previous rows. Note |
| - * that the first pass (pass == 0 really) will completely cover |
| - * the old row, so the rows do not have to be initialized. After |
| - * the first pass (and only for interlaced images), you will have |
| - * to pass the current row, and the function will combine the |
| - * old row and the new row. |
| - */ |
| - |
| - bool hasAlpha = m_reader->hasAlpha(); |
| + * |
| + * For the non-NULL rows of interlaced images, you must call |
| + * png_progressive_combine_row() passing in the row and the |
| + * old row. You can call this function for NULL rows (it will |
| + * just return) and for non-interlaced images (it just does the |
| + * memcpy for you) if it will make the code easier. Thus, you |
| + * can just do this for all cases: |
| + * |
| + * png_progressive_combine_row(png_ptr, old_row, new_row); |
| + * |
| + * where old_row is what was displayed for previous rows. Note |
| + * that the first pass (pass == 0 really) will completely cover |
| + * the old row, so the rows do not have to be initialized. After |
| + * the first pass (and only for interlaced images), you will have |
| + * to pass the current row, and the function will combine the |
| + * old row and the new row. |
| + */ |
| + |
| + bool hasAlpha = m_hasAlphaChannel; |
| png_bytep row = rowBuffer; |
| if (png_bytep interlaceBuffer = m_reader->interlaceBuffer()) { |
| @@ -304,9 +425,9 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, |
| // Write the decoded row pixels to the frame buffer. The repetitive |
| // form of the row write loops is for speed. |
| - ImageFrame::PixelData* const dstRow = buffer.getAddr(0, y); |
| + ImageFrame::PixelData* const dstRow = buffer.getAddr(frameRect.x(), y); |
| unsigned alphaMask = 255; |
| - int width = size().width(); |
| + int width = frameRect.width(); |
| png_bytep srcPtr = row; |
| if (hasAlpha) { |
| @@ -328,22 +449,50 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, |
| srcPtr = (png_bytep)dstRow; |
| } |
| - if (buffer.premultiplyAlpha()) { |
| - for (auto *dstPixel = dstRow; dstPixel < dstRow + width; |
| - dstPixel++, srcPtr += 4) { |
| - buffer.setRGBAPremultiply(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], |
| - srcPtr[3]); |
| - alphaMask &= srcPtr[3]; |
| + if (m_frameBufferCache[m_currentFrame].getAlphaBlendSource() == |
| + ImageFrame::BlendAtopBgcolor) { |
| + if (buffer.premultiplyAlpha()) { |
| + for (auto* dstPixel = dstRow; dstPixel < dstRow + width; |
| + dstPixel++, srcPtr += 4) { |
| + buffer.setRGBAPremultiply(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], |
| + srcPtr[3]); |
| + alphaMask &= srcPtr[3]; |
| + } |
| + } else { |
| + for (auto* dstPixel = dstRow; dstPixel < dstRow + width; |
| + dstPixel++, srcPtr += 4) { |
| + buffer.setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], |
| + srcPtr[3]); |
| + alphaMask &= srcPtr[3]; |
| + } |
| } |
| } else { |
| - for (auto *dstPixel = dstRow; dstPixel < dstRow + width; |
| - dstPixel++, srcPtr += 4) { |
| - buffer.setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], srcPtr[3]); |
| - alphaMask &= srcPtr[3]; |
| + // Now, the blend method is ImageFrame::BlendAtopPreviousFrame. Since the |
| + // frame data of the previous frame is copied at initFrameBuffer, we can |
| + // blend the pixel of this frame, stored in |srcPtr|, over the previous |
| + // pixel stored in |dstPixel|. |
| + if (buffer.premultiplyAlpha()) { |
| + for (auto* dstPixel = dstRow; dstPixel < dstRow + width; |
| + dstPixel++, srcPtr += 4) { |
| + buffer.blendRGBAPremultiplied(dstPixel, srcPtr[0], srcPtr[1], |
| + srcPtr[2], srcPtr[3]); |
| + alphaMask &= srcPtr[3]; |
| + } |
| + } else { |
| + for (auto* dstPixel = dstRow; dstPixel < dstRow + width; |
| + dstPixel++, srcPtr += 4) { |
| + buffer.blendRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], |
| + srcPtr[3]); |
| + alphaMask &= srcPtr[3]; |
| + } |
| } |
| } |
| + |
| + if (alphaMask != 255 && !m_currentBufferSawAlpha) |
| + m_currentBufferSawAlpha = true; |
| + |
| } else { |
| - for (auto *dstPixel = dstRow; dstPixel < dstRow + width; |
| + for (auto* dstPixel = dstRow; dstPixel < dstRow + width; |
| dstPixel++, srcPtr += 3) { |
| buffer.setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], 255); |
| } |
| @@ -357,38 +506,67 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, |
| } |
| } |
| - if (alphaMask != 255 && !buffer.hasAlpha()) |
| - buffer.setHasAlpha(true); |
| - |
| buffer.setPixelsChanged(true); |
| } |
| -void PNGImageDecoder::complete() { |
| - if (m_frameBufferCache.isEmpty()) |
| - return; |
| - |
| - m_frameBufferCache[0].setStatus(ImageFrame::FrameComplete); |
| +bool PNGImageDecoder::frameIsCompleteAtIndex(size_t index) const { |
| + if (failed()) |
|
scroggo_chromium
2016/12/08 13:59:09
I just noticed that the other implementations do n
joostouwerling
2016/12/08 16:28:39
I'm not entirely sure why it was there in the firs
|
| + return false; |
| + |
| + // For non-animated images, return whether the status of the frame is |
| + // ImageFrame::FrameComplete through ImageDecoder::frameIsCompleteAtIndex. |
| + // We only know that the frame is fully received when it is successfully |
| + // decoded, since there is no explicit check whether the IEND chunk has been |
| + // received. |
| + if (index == 0 && m_reader->parseCompleted() && m_frameCount == 1) |
| + return ImageDecoder::frameIsCompleteAtIndex(index); |
| + |
| + // For first frames of animated images, PNGImageReader exposes whether it is |
| + // fully received through firstFrameFullyReceived(). Non-first frames are |
| + // reported by |m_reader| once it has parsed all data for that frame, so we |
| + // can simply return if the index is below the reported frame count. We have |
| + // to use |m_frameCount| here, and not |m_frameBufferCache|.size(), since |
| + // |m_frameCount| gets adjusted when decoding or parsing fails, but |
| + // |m_frameBufferCache| does not, as explained above at |
| + // PNGImageDecoder::setFailed(). |
| + if (index == 0) |
| + return m_reader->firstFrameFullyReceived(); |
| + return (index < m_frameCount); |
| } |
| -inline bool isComplete(const PNGImageDecoder* decoder) { |
| - return decoder->frameIsCompleteAtIndex(0); |
| +float PNGImageDecoder::frameDurationAtIndex(size_t index) const { |
| + return (index < m_frameCount ? m_frameBufferCache[index].duration() : 0); |
| } |
| -void PNGImageDecoder::decode(bool onlySize) { |
| - if (failed()) |
| +void PNGImageDecoder::complete() { |
| + if (m_frameBufferCache.isEmpty()) |
| return; |
| - if (!m_reader) |
| - m_reader = makeUnique<PNGImageReader>(this, m_offset); |
| - |
| - // If we couldn't decode the image but have received all the data, decoding |
| - // has failed. |
| - if (!m_reader->decode(*m_data, onlySize) && isAllDataReceived()) |
| - setFailed(); |
| - |
| - // If decoding is done or failed, we don't need the PNGImageReader anymore. |
| - if (isComplete(this) || failed()) |
| - m_reader.reset(); |
| + // We don't know for sure at this point if we've received enough pixels to |
| + // completely fill the frame. This is not taken into account. The error could |
| + // be in the indicated frame size, or in the frame data chunks. |
| + // |
| + // If the frame size is wrong, we could correct for that, but later frames |
| + // may depend on this frame's size when they're initialized in |
| + // initFrameBuffer(), in the case the disposal method of this frame is |
| + // DisposeOverwriteBgColor. There is no informed decision that can be made |
| + // about what the author intended, so we stay with the indicated frame size. |
| + // |
| + // In the case the data chunk is too small, there's not much we can do. This |
| + // method only gets called when the frame end has been received, so the |
| + // encoder indicated that all frame data is received. It could be an encoding |
| + // error, or it may be an intentional file size reduction. |
| + // |
| + // Therefore, the frame size and pixels that aren't decoded are left as-is. |
| + |
| + if (m_reader->interlaceBuffer()) |
| + m_reader->clearInterlaceBuffer(); |
| + |
| + ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; |
| + buffer.setStatus(ImageFrame::FrameComplete); |
| + |
| + if (!m_currentBufferSawAlpha) |
| + correctAlphaWhenFrameBufferSawNoAlpha(m_currentFrame); |
| } |
| } // namespace blink |