| 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 6deba9ce22adfd250319cac12dada224f706df3d..cb37d138bd29caa3860fdbdb00875b763aa4a2d3 100644
|
| --- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| @@ -38,11 +38,6 @@
|
|
|
| #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 {
|
|
|
| PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
|
| @@ -50,10 +45,101 @@ PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
|
| size_t maxDecodedBytes,
|
| size_t offset)
|
| : ImageDecoder(alphaOption, colorBehavior, maxDecodedBytes),
|
| - m_offset(offset) {}
|
| + m_offset(offset),
|
| + m_currentFrame(0),
|
| + // It would be logical to default to cAnimationNone, but BitmapImage uses
|
| + // that as a signal to never check again, meaning the actual count will
|
| + // never be respected.
|
| + m_repetitionCount(cAnimationLoopOnce),
|
| + m_hasAlphaChannel(false),
|
| + m_currentBufferSawAlpha(false) {}
|
|
|
| PNGImageDecoder::~PNGImageDecoder() {}
|
|
|
| +bool PNGImageDecoder::setFailed() {
|
| + m_reader.reset();
|
| + return ImageDecoder::setFailed();
|
| +}
|
| +
|
| +size_t PNGImageDecoder::decodeFrameCount() {
|
| + parse(ParseQuery::MetaData);
|
| + return failed() ? m_frameBufferCache.size() : m_reader->frameCount();
|
| +}
|
| +
|
| +void PNGImageDecoder::decode(size_t index) {
|
| + parse(ParseQuery::MetaData);
|
| +
|
| + if (failed())
|
| + return;
|
| +
|
| + updateAggressivePurging(index);
|
| +
|
| + Vector<size_t> framesToDecode = findFramesToDecode(index);
|
| + for (auto i = framesToDecode.rbegin(); i != framesToDecode.rend(); i++) {
|
| + m_currentFrame = *i;
|
| + if (!m_reader->decode(*m_data, *i)) {
|
| + setFailed();
|
| + return;
|
| + }
|
| +
|
| + // If this returns false, we need more data to continue decoding.
|
| + if (!postDecodeProcessing(*i))
|
| + break;
|
| + }
|
| +
|
| + // It is also a fatal error if all data is received and we have decoded all
|
| + // frames available but the file is truncated.
|
| + if (index >= m_frameBufferCache.size() - 1 && isAllDataReceived() &&
|
| + m_reader && !m_reader->parseCompleted())
|
| + setFailed();
|
| +}
|
| +
|
| +void PNGImageDecoder::parse(ParseQuery query) {
|
| + if (failed() || (m_reader && m_reader->parseCompleted()))
|
| + return;
|
| +
|
| + if (!m_reader)
|
| + m_reader = WTF::makeUnique<PNGImageReader>(this, m_offset);
|
| +
|
| + if (!m_reader->parse(*m_data, query))
|
| + setFailed();
|
| +}
|
| +
|
| +void PNGImageDecoder::clearFrameBuffer(size_t index) {
|
| + if (m_reader)
|
| + m_reader->clearDecodeState(index);
|
| + ImageDecoder::clearFrameBuffer(index);
|
| +}
|
| +
|
| +bool PNGImageDecoder::canReusePreviousFrameBuffer(size_t index) const {
|
| + DCHECK(index < m_frameBufferCache.size());
|
| + return m_frameBufferCache[index].getDisposalMethod() !=
|
| + ImageFrame::DisposeOverwritePrevious;
|
| +}
|
| +
|
| +void PNGImageDecoder::setRepetitionCount(int repetitionCount) {
|
| + m_repetitionCount = repetitionCount;
|
| +}
|
| +
|
| +int PNGImageDecoder::repetitionCount() const {
|
| + return failed() ? cAnimationLoopOnce : m_repetitionCount;
|
| +}
|
| +
|
| +void PNGImageDecoder::initializeNewFrame(size_t index) {
|
| + const PNGImageReader::FrameInfo& frameInfo = m_reader->frameInfo(index);
|
| + ImageFrame& buffer = m_frameBufferCache[index];
|
| +
|
| + DCHECK(IntRect(IntPoint(), size()).contains(frameInfo.frameRect));
|
| + buffer.setOriginalFrameRect(frameInfo.frameRect);
|
| +
|
| + buffer.setDuration(frameInfo.duration);
|
| + buffer.setDisposalMethod(frameInfo.disposalMethod);
|
| + buffer.setAlphaBlendSource(frameInfo.alphaBlend);
|
| +
|
| + size_t previousFrameIndex = findRequiredPreviousFrame(index, false);
|
| + buffer.setRequiredPreviousFrameIndex(previousFrameIndex);
|
| +}
|
| +
|
| inline sk_sp<SkColorSpace> readColorSpace(png_structp png, png_infop info) {
|
| if (png_get_valid(png, info, PNG_INFO_sRGB))
|
| return SkColorSpace::MakeSRGB();
|
| @@ -111,22 +197,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;
|
| png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType,
|
| &interlaceType, &compressionType, nullptr);
|
| @@ -148,11 +220,29 @@ 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.
|
| - // TODO(msarret): Add GRAY profile support, block CYMK?
|
| - if (sk_sp<SkColorSpace> colorSpace = readColorSpace(png, info))
|
| - setEmbeddedColorSpace(std::move(colorSpace));
|
| + // Only set the size and the color space of the image once since non-first
|
| + // frames also use this method: there is no per-frame color space, and the
|
| + // image size is determined from the header width and height.
|
| + 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.
|
| + // TODO(msarret): Add GRAY profile support, block CYMK?
|
| + if (sk_sp<SkColorSpace> colorSpace = readColorSpace(png, info))
|
| + setEmbeddedColorSpace(colorSpace);
|
| + }
|
| }
|
|
|
| if (!hasEmbeddedColorSpace()) {
|
| @@ -171,6 +261,8 @@ void PNGImageDecoder::headerAvailable() {
|
| }
|
| }
|
|
|
| + DCHECK(isDecodedSizeAvailable());
|
| +
|
| // Tell libpng to send us rows for interlaced pngs.
|
| if (interlaceType == PNG_INTERLACE_ADAM7)
|
| png_set_interlace_handling(png);
|
| @@ -180,56 +272,41 @@ void PNGImageDecoder::headerAvailable() {
|
|
|
| int channels = png_get_channels(png, info);
|
| DCHECK(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);
|
| }
|
|
|
| void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
|
| unsigned rowIndex,
|
| int) {
|
| - if (m_frameBufferCache.isEmpty())
|
| + if (m_currentFrame >= m_frameBufferCache.size())
|
| return;
|
|
|
| - // Initialize the framebuffer if needed.
|
| - ImageFrame& buffer = m_frameBufferCache[0];
|
| + ImageFrame& buffer = m_frameBufferCache[m_currentFrame];
|
| if (buffer.getStatus() == ImageFrame::FrameEmpty) {
|
| png_structp png = m_reader->pngPtr();
|
| - if (!buffer.setSizeAndColorSpace(size().width(), size().height(),
|
| - colorSpaceForSkImages())) {
|
| + if (!initFrameBuffer(m_currentFrame)) {
|
| longjmp(JMPBUF(png), 1);
|
| return;
|
| }
|
|
|
| - unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3;
|
| + DCHECK_EQ(ImageFrame::FramePartial, buffer.getStatus());
|
| +
|
| if (PNG_INTERLACE_ADAM7 ==
|
| png_get_interlace_type(png, m_reader->infoPtr())) {
|
| - m_reader->createInterlaceBuffer(colorChannels * size().width() *
|
| - size().height());
|
| + unsigned colorChannels = m_hasAlphaChannel ? 4 : 3;
|
| + m_reader->createInterlaceBuffer(colorChannels * size().area());
|
| 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()));
|
| + m_currentBufferSawAlpha = false;
|
| }
|
|
|
| + const IntRect& frameRect = buffer.originalFrameRect();
|
| + DCHECK(IntRect(IntPoint(), size()).contains(frameRect));
|
| +
|
| /* libpng comments (here to explain what follows).
|
| *
|
| * this function is called for every row in the image. If the
|
| @@ -242,14 +319,22 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
|
| * 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.
|
| + // 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 use the frame size as the source
|
| + // of truth. libpng can send extra rows: ignore them too, this to prevent
|
| + // memory writes outside of the image bounds (security).
|
| if (!rowBuffer)
|
| return;
|
| - int y = rowIndex;
|
| - if (y < 0 || y >= size().height())
|
| +
|
| + DCHECK_GT(frameRect.height(), 0);
|
| + if (rowIndex >= static_cast<unsigned>(frameRect.height()))
|
| + return;
|
| +
|
| + int y = rowIndex + frameRect.y();
|
| + if (y < 0)
|
| return;
|
| + DCHECK_LT(y, size().height());
|
|
|
| /* libpng comments (continued).
|
| *
|
| @@ -270,7 +355,7 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
|
| * old row and the new row.
|
| */
|
|
|
| - bool hasAlpha = m_reader->hasAlpha();
|
| + bool hasAlpha = m_hasAlphaChannel;
|
| png_bytep row = rowBuffer;
|
|
|
| if (png_bytep interlaceBuffer = m_reader->interlaceBuffer()) {
|
| @@ -281,8 +366,8 @@ 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);
|
| - int width = size().width();
|
| + ImageFrame::PixelData* const dstRow = buffer.getAddr(frameRect.x(), y);
|
| + int width = frameRect.width();
|
|
|
| png_bytep srcPtr = row;
|
| if (hasAlpha) {
|
| @@ -305,23 +390,47 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
|
| }
|
|
|
| unsigned alphaMask = 255;
|
| - if (buffer.premultiplyAlpha()) {
|
| - for (auto *dstPixel = dstRow; dstPixel < dstRow + width;
|
| - srcPtr += 4, ++dstPixel) {
|
| - 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;
|
| - srcPtr += 4, ++dstPixel) {
|
| - 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 && !buffer.hasAlpha())
|
| - buffer.setHasAlpha(true);
|
| + if (alphaMask != 255)
|
| + m_currentBufferSawAlpha = true;
|
|
|
| } else {
|
| for (auto *dstPixel = dstRow; dstPixel < dstRow + width;
|
| @@ -341,32 +450,44 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
|
| buffer.setPixelsChanged(true);
|
| }
|
|
|
| -void PNGImageDecoder::complete() {
|
| - if (m_frameBufferCache.isEmpty())
|
| +void PNGImageDecoder::frameComplete() {
|
| + if (m_currentFrame >= m_frameBufferCache.size())
|
| return;
|
|
|
| - m_frameBufferCache[0].setStatus(ImageFrame::FrameComplete);
|
| -}
|
| + if (m_reader->interlaceBuffer())
|
| + m_reader->clearInterlaceBuffer();
|
| +
|
| + ImageFrame& buffer = m_frameBufferCache[m_currentFrame];
|
| + if (buffer.getStatus() == ImageFrame::FrameEmpty) {
|
| + longjmp(JMPBUF(m_reader->pngPtr()), 1);
|
| + return;
|
| + }
|
| +
|
| + if (!m_currentBufferSawAlpha)
|
| + correctAlphaWhenFrameBufferSawNoAlpha(m_currentFrame);
|
|
|
| -inline bool isComplete(const PNGImageDecoder* decoder) {
|
| - return decoder->frameIsCompleteAtIndex(0);
|
| + buffer.setStatus(ImageFrame::FrameComplete);
|
| }
|
|
|
| -void PNGImageDecoder::decode(bool onlySize) {
|
| - if (failed())
|
| - return;
|
| +bool PNGImageDecoder::frameIsCompleteAtIndex(size_t index) const {
|
| + if (!isDecodedSizeAvailable())
|
| + return false;
|
|
|
| - if (!m_reader)
|
| - m_reader = WTF::makeUnique<PNGImageReader>(this, m_offset);
|
| + DCHECK(!failed() && m_reader);
|
|
|
| - // 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();
|
| + // For non-animated images, return whether the status of the frame is
|
| + // ImageFrame::FrameComplete with ImageDecoder::frameIsCompleteAtIndex.
|
| + // This matches the behavior of WEBPImageDecoder.
|
| + if (m_reader->parseCompleted() && m_reader->frameCount() == 1)
|
| + return ImageDecoder::frameIsCompleteAtIndex(index);
|
| +
|
| + return m_reader->frameIsReceivedAtIndex(index);
|
| +}
|
|
|
| - // If decoding is done or failed, we don't need the PNGImageReader anymore.
|
| - if (isComplete(this) || failed())
|
| - m_reader.reset();
|
| +float PNGImageDecoder::frameDurationAtIndex(size_t index) const {
|
| + if (index < m_frameBufferCache.size())
|
| + return m_frameBufferCache[index].duration();
|
| + return 0;
|
| }
|
|
|
| } // namespace blink
|
|
|