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..c8c7b0e594e71f229ede9d4413a6d7b87ba078f9 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,8 +320,9 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
// make our lives easier. |
if (!rowBuffer) |
return; |
- int y = rowIndex; |
- if (y < 0 || y >= size().height()) |
+ int y = rowIndex + frameRect.y(); |
+ ASSERT(y >= 0); |
+ if (y >= size().height()) |
return; |
/* libpng comments (continued). |
@@ -380,25 +362,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 +390,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); |
scroggo_chromium
2016/10/31 13:35:12
From https://codereview.chromium.org/2386453003/di
joostouwerling
2016/10/31 18:40:19
Acknowledged, that is the approach I wanted to tak
|
} |
} // namespace blink |