| 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 ea896bd0370b078d13b7eb7b5563712d6da8fd1c..1028e9eb230750021f02a389717c1e8ae14d64e5 100644
|
| --- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| @@ -38,9 +38,7 @@
|
|
|
| #include "platform/image-decoders/png/PNGImageDecoder.h"
|
|
|
| -#include "platform/image-decoders/png/PNGImageReader.h"
|
| #include "png.h"
|
| -#include "wtf/PtrUtil.h"
|
| #include <memory>
|
|
|
| namespace blink {
|
| @@ -50,10 +48,149 @@ PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
|
| size_t maxDecodedBytes,
|
| size_t offset)
|
| : ImageDecoder(alphaOption, colorBehavior, maxDecodedBytes),
|
| - m_offset(offset) {}
|
| + m_reader(this, offset),
|
| + m_frameCount(0),
|
| + m_currentFrame(0),
|
| + m_repetitionCount(cAnimationLoopOnce),
|
| + m_hasAlphaChannel(false),
|
| + m_currentBufferSawAlpha(false),
|
| + m_isParsing(false) {}
|
|
|
| PNGImageDecoder::~PNGImageDecoder() {}
|
|
|
| +bool PNGImageDecoder::setFailed() {
|
| + // Update the frame count to make sure it reflects the most up to date number
|
| + // of frames that were parsed. We don't want to set the decoder to the failed
|
| + // 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();
|
| + return false;
|
| +}
|
| +
|
| +size_t PNGImageDecoder::decodeFrameCount() {
|
| + parse(PNGImageReader::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);
|
| + ImageDecoder::clearFrameBuffer(frameIndex);
|
| +}
|
| +
|
| +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(PNGImageReader::PNGParseQuery query) {
|
| + if (failed() || m_reader.parseCompleted())
|
| + return;
|
| +
|
| + m_isParsing = true;
|
| + if (!m_reader.parse(*m_data, query) && isAllDataReceived())
|
| + setFailed();
|
| + else if (query == PNGImageReader::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;
|
| +}
|
| +
|
| +static inline ImageFrame::DisposalMethod getDisposalMethod(
|
| + uint8_t disposalMethod) {
|
| + switch (disposalMethod) {
|
| + case kAPNGDisposeKeep:
|
| + return ImageFrame::DisposalMethod::DisposeKeep;
|
| + case kAPNGDisposeBgcolor:
|
| + return ImageFrame::DisposalMethod::DisposeOverwriteBgcolor;
|
| + case kAPNGDisposePrevious:
|
| + return ImageFrame::DisposalMethod::DisposeOverwritePrevious;
|
| + default:
|
| + return ImageFrame::DisposalMethod::DisposeNotSpecified;
|
| + }
|
| +}
|
| +
|
| +static inline ImageFrame::AlphaBlendSource getAlphaBlend(uint8_t alphaBlend) {
|
| + if (alphaBlend == kAPNGAlphaBlendPreviousFrame)
|
| + 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;
|
| }
|
| @@ -109,24 +246,10 @@ 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_structp png = m_reader.pngPtr();
|
| + png_infop info = m_reader.infoPtr();
|
|
|
| + png_uint_32 width, height;
|
| int bitDepth, colorType, interlaceType, compressionType, filterType, channels;
|
| png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType,
|
| &interlaceType, &compressionType, &filterType);
|
| @@ -152,16 +275,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);
|
| + }
|
| }
|
| }
|
|
|
| @@ -199,20 +342,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,
|
| @@ -221,88 +352,82 @@ 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);
|
| + // When a client calls ImageDecoder::setMemoryAllocator *before* decoding the
|
| + // frame count, the first frame won't get initialized correctly. The call will
|
| + // resize |m_frameBufferCache| to 1, and therefore ImageDecoder::frameCount
|
| + // will *not* call initializeNewFrame for the first frame, whether it is a
|
| + // static image or the first frame of an animated image. Amongst others, the
|
| + // frame rect will not be set. If this is the case, initialize the frame here.
|
| + if (m_frameBufferCache[0].originalFrameRect().size() == IntSize(0, 0))
|
| + initializeNewFrame(0);
|
|
|
| - // 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()) {
|
| + if (png_bytep interlaceBuffer = m_reader.interlaceBuffer()) {
|
| unsigned colorChannels = hasAlpha ? 4 : 3;
|
| row = interlaceBuffer + (rowIndex * colorChannels * size().width());
|
| - png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer);
|
| + png_progressive_combine_row(m_reader.pngPtr(), row, 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) {
|
| @@ -324,20 +449,48 @@ 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;
|
| dstPixel++, srcPtr += 3) {
|
| @@ -353,38 +506,64 @@ 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 {
|
| + // 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 = WTF::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
|
|
|