Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1327)

Unified Diff: third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp

Issue 2386453003: WIP: Implement APNG (Closed)
Patch Set: Progressive decoding for animated images Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..e3e231828461ee528f2ae132876ddf06122f7177 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,8 @@ 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();
+ if (y >= size().height())
scroggo_chromium 2016/10/28 14:20:32 Since you removed the check for y < 0, please add
joostouwerling 2016/10/28 18:41:25 Done.
return;
/* libpng comments (continued).
@@ -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

Powered by Google App Engine
This is Rietveld 408576698