| 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 dff41e392ebff0c2226f28f98a7fb5d92c734c52..1872324cef0e281df61730c5995fa2125a79a2bb 100644
|
| --- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
|
| @@ -38,8 +38,8 @@
|
|
|
| #include "platform/image-decoders/png/PNGImageDecoder.h"
|
|
|
| +#include "platform/image-decoders/png/PNGImageReader.h"
|
| #include "png.h"
|
| -#include "wtf/PtrUtil.h"
|
| #include <memory>
|
|
|
| #if !defined(PNG_LIBPNG_VER_MAJOR) || !defined(PNG_LIBPNG_VER_MINOR)
|
| @@ -55,123 +55,109 @@
|
| #define JMPBUF(png_ptr) png_ptr->jmpbuf
|
| #endif
|
|
|
| -namespace {
|
|
|
| -inline blink::PNGImageDecoder* imageDecoder(png_structp png)
|
| +namespace blink {
|
| +
|
| +PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes, size_t offset)
|
| + : ImageDecoder(alphaOption, colorOptions, maxDecodedBytes)
|
| + , m_offset(offset)
|
| + , m_metaDataDecoded(false)
|
| + , m_frameCount(0)
|
| + , m_currentFrame(0)
|
| + , m_repetitionCount(cAnimationLoopOnce)
|
| {
|
| - return static_cast<blink::PNGImageDecoder*>(png_get_progressive_ptr(png));
|
| }
|
|
|
| -void PNGAPI pngHeaderAvailable(png_structp png, png_infop)
|
| +PNGImageDecoder::~PNGImageDecoder()
|
| {
|
| - imageDecoder(png)->headerAvailable();
|
| }
|
|
|
| -void PNGAPI pngRowAvailable(png_structp png, png_bytep row, png_uint_32 rowIndex, int state)
|
| +size_t PNGImageDecoder::decodeFrameCount()
|
| {
|
| - imageDecoder(png)->rowAvailable(row, rowIndex, state);
|
| + if (!m_metaDataDecoded)
|
| + parse(PNGParseQuery::PNGMetaDataQuery);
|
| + return m_frameCount;
|
| }
|
|
|
| -void PNGAPI pngComplete(png_structp png, png_infop)
|
| +inline bool frameComplete(ImageFrame& frame)
|
| {
|
| - imageDecoder(png)->complete();
|
| + return frame.getStatus() == ImageFrame::FrameComplete;
|
| }
|
|
|
| -void PNGAPI pngFailed(png_structp png, png_const_charp)
|
| +void PNGImageDecoder::decode(size_t index)
|
| {
|
| - longjmp(JMPBUF(png), 1);
|
| + m_currentFrame = index;
|
| + m_reader->decode(*m_data, index);
|
| }
|
|
|
| -} // namespace
|
| +void PNGImageDecoder::parse(PNGParseQuery query)
|
| +{
|
| + if (failed())
|
| + return;
|
|
|
| -namespace blink {
|
| + if (!m_reader)
|
| + m_reader = wrapUnique(new PNGImageReader(this, m_offset));
|
|
|
| -class PNGImageReader final {
|
| - USING_FAST_MALLOC(PNGImageReader);
|
| - WTF_MAKE_NONCOPYABLE(PNGImageReader);
|
| -public:
|
| - PNGImageReader(PNGImageDecoder* decoder, size_t readOffset)
|
| - : m_decoder(decoder)
|
| - , m_readOffset(readOffset)
|
| - , m_currentBufferSize(0)
|
| - , m_decodingSizeOnly(false)
|
| - , m_hasAlpha(false)
|
| -#if USE(QCMSLIB)
|
| - , m_rowBuffer()
|
| -#endif
|
| - {
|
| - m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0);
|
| - m_info = png_create_info_struct(m_png);
|
| - png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, pngRowAvailable, pngComplete);
|
| - }
|
| + if (!m_reader->parse(*m_data, query) && isAllDataReceived())
|
| + setFailed();
|
|
|
| - ~PNGImageReader()
|
| - {
|
| - png_destroy_read_struct(m_png ? &m_png : 0, m_info ? &m_info : 0, 0);
|
| - ASSERT(!m_png && !m_info);
|
| + if (query == PNGParseQuery::PNGMetaDataQuery)
|
| + m_frameCount = m_reader->frameCount();
|
| +}
|
|
|
| - m_readOffset = 0;
|
| - }
|
| +void PNGImageDecoder::setRepetitionCount(size_t repetitionCount)
|
| +{
|
| + m_repetitionCount = (repetitionCount == 0) ? cAnimationLoopInfinite
|
| + : repetitionCount;
|
| +}
|
|
|
| - bool decode(const SegmentReader& data, bool sizeOnly)
|
| - {
|
| - m_decodingSizeOnly = sizeOnly;
|
| -
|
| - // We need to do the setjmp here. Otherwise bad things will happen.
|
| - if (setjmp(JMPBUF(m_png)))
|
| - return m_decoder->setFailed();
|
| -
|
| - const char* segment;
|
| - while (size_t segmentLength = data.getSomeData(segment, m_readOffset)) {
|
| - m_readOffset += segmentLength;
|
| - m_currentBufferSize = m_readOffset;
|
| - png_process_data(m_png, m_info, reinterpret_cast<png_bytep>(const_cast<char*>(segment)), segmentLength);
|
| - if (sizeOnly ? m_decoder->isDecodedSizeAvailable() : m_decoder->frameIsCompleteAtIndex(0))
|
| - return true;
|
| - }
|
| +// 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_metaDataDecoded && isAllDataReceived() && m_reader->frameCount() == 1)
|
| + return cAnimationNone;
|
| + return failed() ? cAnimationLoopOnce : m_repetitionCount;
|
| +}
|
|
|
| - return false;
|
| +// 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;
|
| }
|
| +}
|
|
|
| - png_structp pngPtr() const { return m_png; }
|
| - png_infop infoPtr() const { return m_info; }
|
| -
|
| - size_t getReadOffset() const { return m_readOffset; }
|
| - void setReadOffset(size_t offset) { m_readOffset = offset; }
|
| - size_t currentBufferSize() const { return m_currentBufferSize; }
|
| - bool decodingSizeOnly() const { return m_decodingSizeOnly; }
|
| - void setHasAlpha(bool hasAlpha) { m_hasAlpha = hasAlpha; }
|
| - bool hasAlpha() const { return m_hasAlpha; }
|
| -
|
| - png_bytep interlaceBuffer() const { return m_interlaceBuffer.get(); }
|
| - void createInterlaceBuffer(int size) { m_interlaceBuffer = wrapArrayUnique(new png_byte[size]); }
|
| -#if USE(QCMSLIB)
|
| - png_bytep rowBuffer() const { return m_rowBuffer.get(); }
|
| - void createRowBuffer(int size) { m_rowBuffer = wrapArrayUnique(new png_byte[size]); }
|
| -#endif
|
| -
|
| -private:
|
| - png_structp m_png;
|
| - png_infop m_info;
|
| - PNGImageDecoder* m_decoder;
|
| - size_t m_readOffset;
|
| - size_t m_currentBufferSize;
|
| - bool m_decodingSizeOnly;
|
| - bool m_hasAlpha;
|
| - std::unique_ptr<png_byte[]> m_interlaceBuffer;
|
| -#if USE(QCMSLIB)
|
| - std::unique_ptr<png_byte[]> m_rowBuffer;
|
| -#endif
|
| -};
|
|
|
| -PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes, size_t offset)
|
| - : ImageDecoder(alphaOption, colorOptions, maxDecodedBytes)
|
| - , m_offset(offset)
|
| +// 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;
|
| }
|
|
|
| -PNGImageDecoder::~PNGImageDecoder()
|
| +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));
|
| }
|
|
|
| void PNGImageDecoder::headerAvailable()
|
| @@ -181,17 +167,21 @@ void PNGImageDecoder::headerAvailable()
|
| 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;
|
| - }
|
| + // Only set the size of the image once. Since single frames also use this
|
| + // method, we don't want them to override the size to their frame rect.
|
| + 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;
|
| + // Set the image size now that the image header is available.
|
| + if (!setSize(width, height)) {
|
| + longjmp(JMPBUF(png), 1);
|
| + return;
|
| + }
|
| }
|
|
|
| int bitDepth, colorType, interlaceType, compressionType, filterType, channels;
|
| @@ -270,26 +260,16 @@ void PNGImageDecoder::headerAvailable()
|
| 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)
|
| - // '0' argument to png_process_data_pause means: Do not 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
|
| - }
|
| }
|
|
|
| void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int)
|
| {
|
| +
|
| if (m_frameBufferCache.isEmpty())
|
| 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.setSizeAndColorProfile(size().width(), size().height(), colorProfile())) {
|
| @@ -317,11 +297,12 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
|
| #endif
|
| buffer.setStatus(ImageFrame::FramePartial);
|
| buffer.setHasAlpha(false);
|
| -
|
| - // For PNGs, the frame always fills the entire image.
|
| - buffer.setOriginalFrameRect(IntRect(IntPoint(), size()));
|
| }
|
|
|
| + // This frameRect is already clipped, so that it fits within the size of the
|
| + // image. This is done in initializeNewFrame() after a frameCount() call.
|
| + const IntRect& frameRect = buffer.originalFrameRect();
|
| +
|
| /* libpng comments (here to explain what follows).
|
| *
|
| * this function is called for every row in the image. If the
|
| @@ -339,7 +320,7 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
|
| // make our lives easier.
|
| if (!rowBuffer)
|
| return;
|
| - int y = rowIndex;
|
| + int y = rowIndex + frameRect.y();
|
| if (y < 0 || y >= size().height())
|
| return;
|
|
|
| @@ -380,25 +361,24 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
|
|
|
| // Write the decoded row pixels to the frame buffer. The repetitive
|
| // form of the row write loops is for speed.
|
| - ImageFrame::PixelData* address = buffer.getAddr(0, y);
|
| + ImageFrame::PixelData* address = buffer.getAddr(frameRect.x(), y);
|
| unsigned alphaMask = 255;
|
| - int width = size().width();
|
|
|
| png_bytep pixel = row;
|
| if (hasAlpha) {
|
| if (buffer.premultiplyAlpha()) {
|
| - for (int x = 0; x < width; ++x, pixel += 4) {
|
| + for (int x = frameRect.x(); x < frameRect.maxX(); ++x, pixel += 4) {
|
| buffer.setRGBAPremultiply(address++, pixel[0], pixel[1], pixel[2], pixel[3]);
|
| alphaMask &= pixel[3];
|
| }
|
| } else {
|
| - for (int x = 0; x < width; ++x, pixel += 4) {
|
| + for (int x = frameRect.x(); x < frameRect.maxX(); ++x, pixel += 4) {
|
| buffer.setRGBARaw(address++, pixel[0], pixel[1], pixel[2], pixel[3]);
|
| alphaMask &= pixel[3];
|
| }
|
| }
|
| } else {
|
| - for (int x = 0; x < width; ++x, pixel += 3) {
|
| + for (int x = frameRect.x(); x < frameRect.maxX(); ++x, pixel += 3) {
|
| buffer.setRGBARaw(address++, pixel[0], pixel[1], pixel[2], 255);
|
| }
|
| }
|
| @@ -409,35 +389,29 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex,
|
| buffer.setPixelsChanged(true);
|
| }
|
|
|
| -void PNGImageDecoder::complete()
|
| +bool PNGImageDecoder::frameIsCompleteAtIndex(size_t index) const
|
| {
|
| - if (m_frameBufferCache.isEmpty())
|
| - return;
|
| -
|
| - m_frameBufferCache[0].setStatus(ImageFrame::FrameComplete);
|
| + // @TODO(joostouwerling): show complete frames even if a later frame fails.
|
| + if (failed())
|
| + return false;
|
| + if (index >= m_frameBufferCache.size())
|
| + return false;
|
| + if (index == 0)
|
| + return ImageDecoder::frameIsCompleteAtIndex(index);
|
| + return true;
|
| }
|
|
|
| -inline bool isComplete(const PNGImageDecoder* decoder)
|
| +float PNGImageDecoder::frameDurationAtIndex(size_t index) const
|
| {
|
| - return decoder->frameIsCompleteAtIndex(0);
|
| + return (index < m_frameBufferCache.size() ?
|
| + m_frameBufferCache[index].duration() : 0);
|
| }
|
|
|
| -void PNGImageDecoder::decode(bool onlySize)
|
| +void PNGImageDecoder::complete()
|
| {
|
| - if (failed())
|
| + if (m_frameBufferCache.isEmpty())
|
| return;
|
| -
|
| - if (!m_reader)
|
| - m_reader = wrapUnique(new 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();
|
| + m_frameBufferCache[m_currentFrame].setStatus(ImageFrame::FrameComplete);
|
| }
|
|
|
| } // namespace blink
|
|
|