Index: Source/platform/image-decoders/png/PNGImageDecoder.cpp |
diff --git a/Source/platform/image-decoders/png/PNGImageDecoder.cpp b/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
index 99fde2f278671211987446b4b73657c8e4f37586..5e830db615f733128d37c385f542ef30b4fc6339 100644 |
--- a/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
+++ b/Source/platform/image-decoders/png/PNGImageDecoder.cpp |
@@ -39,6 +39,7 @@ |
#include "config.h" |
#include "platform/image-decoders/png/PNGImageDecoder.h" |
+#include "platform/RuntimeEnabledFeatures.h" |
#include "wtf/PassOwnPtr.h" |
#include "png.h" |
@@ -92,8 +93,15 @@ public: |
PNGImageReader(PNGImageDecoder* decoder) |
: m_decoder(decoder) |
, m_readOffset(0) |
- , m_currentBufferSize(0) |
- , m_decodingSizeOnly(false) |
+ , m_parseOffset(0) |
+ , m_infoSize(0) |
+ , m_isAnimated(false) |
+ , m_isParsed(false) |
+ , m_width(0) |
+ , m_height(0) |
+ , m_repetitionCount(cAnimationNone) |
+ , m_posterFrame(0) |
+ , m_visibleFrames(1) |
, m_hasAlpha(false) |
#if USE(QCMSLIB) |
, m_transform(0) |
@@ -103,6 +111,7 @@ public: |
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); |
+ memset(&m_currentFrame, 0, sizeof(m_currentFrame)); |
} |
~PNGImageReader() |
@@ -110,6 +119,20 @@ public: |
close(); |
} |
+ struct FrameInfo { |
+ unsigned start; |
+ unsigned finish; |
+ unsigned width; |
+ unsigned height; |
+ unsigned xOffset; |
+ unsigned yOffset; |
+ unsigned duration; |
+ unsigned dispose; |
+ unsigned blend; |
+ }; |
+ |
+ static const unsigned headerSize = 33; |
+ |
void close() |
{ |
if (m_png && m_info) |
@@ -119,34 +142,188 @@ public: |
clearColorTransform(); |
#endif |
m_readOffset = 0; |
+ m_parseOffset = 0; |
} |
- bool decode(const SharedBuffer& data, bool sizeOnly) |
+ bool parse(const SharedBuffer& data) |
{ |
- m_decodingSizeOnly = sizeOnly; |
+ if (data.size() < headerSize) |
+ return false; |
+ |
+ const unsigned long maxPNGSize = 1000000UL; |
+ png_bytep inputData = reinterpret_cast<png_bytep>(const_cast<char*>(data.data())); |
+ unsigned inputSize = data.size(); |
+ |
+ if (!m_parseOffset) { |
+ if (memcmp(data.data() + 12, "IHDR", 4)) |
+ return false; |
+ m_currentFrame.width = m_width = png_get_uint_32(inputData + 16); |
+ m_currentFrame.height = m_height = png_get_uint_32(inputData + 20); |
+ if (m_width > maxPNGSize || m_height > maxPNGSize) |
+ return false; |
+ png_byte bitDepth = inputData[24]; |
+ if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && bitDepth != 16) |
+ return false; |
+ png_byte colorType = inputData[25]; |
+ if (colorType == 1 || colorType == 5 || colorType > 6) |
+ return false; |
+ if (crc32(crc32(0, Z_NULL, 0), inputData + 12, 17) != png_get_uint_32(inputData + 29)) |
+ return false; |
+ |
+ m_infoSize = m_parseOffset = headerSize; |
+ } |
+ |
+ while (!m_isParsed) { |
+ if (inputSize < m_parseOffset + 12) |
+ return false; |
+ |
+ png_bytep chunk = inputData + m_parseOffset; |
+ png_bytep chunkId = chunk + 4; |
+ unsigned chunkSize = png_get_uint_32(chunk); |
+ unsigned chunkEnd = m_parseOffset + chunkSize + 12; |
+ |
+ if (!memcmp(chunkId, "IEND", 4) && !chunkSize) { |
+ if (!m_currentFrame.start) |
+ return false; |
+ m_isParsed = true; |
+ m_currentFrame.finish = chunkEnd; |
+ m_frames.append(m_currentFrame); |
+ return true; |
+ } |
+ if (!memcmp(chunkId, "IDAT", 4) && !m_currentFrame.start) { |
+ m_currentFrame.start = m_parseOffset; |
+ if (!m_decoder->setSize(m_width, m_height)) |
+ return false; |
+ } |
+ |
+ if (inputSize < chunkEnd) |
+ return false; |
+ |
+ if (RuntimeEnabledFeatures::animatedPNGEnabled()) { |
+ if (!memcmp(chunkId, "fdAT", 4) && m_isAnimated && !m_currentFrame.start) { |
+ m_currentFrame.start = m_parseOffset; |
+ } else if (!memcmp(chunkId, "acTL", 4) && chunkSize == 8 && !m_isAnimated && !m_currentFrame.start) { |
+ m_isAnimated = true; |
+ m_posterFrame = 1; |
+ m_visibleFrames = 0; |
+ m_repetitionCount = static_cast<int>(png_get_uint_32(chunk + 12)) - 1; |
+ } else if (!memcmp(chunkId, "fcTL", 4) && chunkSize == 26 && (m_isAnimated || !m_currentFrame.start)) { |
+ if (m_currentFrame.start) { |
+ m_currentFrame.finish = chunkEnd; |
+ m_frames.append(m_currentFrame); |
+ m_currentFrame.start = 0; |
+ } else if (m_frames.isEmpty()) { |
+ m_posterFrame = 0; |
+ } |
+ |
+ m_currentFrame.width = png_get_uint_32(chunk + 12); |
+ m_currentFrame.height = png_get_uint_32(chunk + 16); |
+ m_currentFrame.xOffset = png_get_uint_32(chunk + 20); |
+ m_currentFrame.yOffset = png_get_uint_32(chunk + 24); |
+ unsigned numerator = png_get_uint_16(chunk + 28); |
+ unsigned denominator = png_get_uint_16(chunk + 30); |
+ m_currentFrame.duration = (!denominator) ? numerator * 10 : (int)(numerator * 1000.0 / denominator); |
+ m_currentFrame.dispose = chunk[32]; |
+ m_currentFrame.blend = chunk[33]; |
+ |
+ if (m_currentFrame.width > maxPNGSize || m_currentFrame.height > maxPNGSize |
+ || m_currentFrame.xOffset > maxPNGSize || m_currentFrame.yOffset > maxPNGSize |
+ || m_currentFrame.xOffset + m_currentFrame.width > m_width |
+ || m_currentFrame.yOffset + m_currentFrame.height > m_height |
+ || m_currentFrame.dispose > 2 || m_currentFrame.blend > 1) |
+ return false; |
+ |
+ if (!m_visibleFrames) { |
+ m_currentFrame.blend = 0; |
+ if (m_currentFrame.dispose == 2) |
+ m_currentFrame.dispose = 1; |
+ } |
+ m_visibleFrames++; |
+ } else if (!m_currentFrame.start && m_frames.isEmpty()) { |
+ m_infoSize = chunkEnd; |
+ } |
+ } |
+ |
+ m_parseOffset = chunkEnd; |
+ } |
+ return true; |
+ } |
+ |
+ bool decode(const SharedBuffer& data, size_t index) |
+ { |
+ static png_byte dataIEND[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}; |
+ |
+ index += m_posterFrame; |
+ if (index && index >= m_frames.size()) |
+ return true; |
+ |
+ if (index || !m_readOffset) { |
+ png_destroy_read_struct(&m_png, &m_info, 0); |
+ 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); |
+ } |
- // 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 (unsigned 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 false; |
+ |
+ if (!index) { |
+ const char* segment; |
+ unsigned endOffset = (!m_frames.isEmpty()) ? m_frames[0].finish : 0; |
+ while (unsigned segmentLength = data.getSomeData(segment, m_readOffset)) { |
+ if (endOffset && m_readOffset + segmentLength > endOffset) |
+ segmentLength = endOffset - m_readOffset; |
+ |
+ m_readOffset += segmentLength; |
+ png_process_data(m_png, m_info, reinterpret_cast<png_bytep>(const_cast<char*>(segment)), segmentLength); |
+ |
+ if (m_readOffset == endOffset) { |
+ png_process_data(m_png, m_info, dataIEND, 12); |
+ break; |
+ } |
+ } |
+ } else { |
+ m_readOffset = 0; |
+ png_bytep inputData = reinterpret_cast<png_bytep>(const_cast<char*>(data.data())); |
+ png_bytep chunk = inputData + m_frames[index].start; |
+ png_bytep finish = inputData + m_frames[index].finish; |
+ if (inputData + data.size() < finish) |
return true; |
+ |
+ png_set_crc_action(m_png, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); |
+ if (m_frames[index].width != m_width || m_frames[index].height != m_height) { |
+ unsigned char header[headerSize]; |
+ memcpy(&header[0], inputData, headerSize); |
+ png_save_uint_32(&header[16], m_frames[index].width); |
+ png_save_uint_32(&header[20], m_frames[index].height); |
+ png_process_data(m_png, m_info, header, headerSize); |
+ png_process_data(m_png, m_info, inputData + headerSize, m_infoSize - headerSize); |
+ } else { |
+ png_process_data(m_png, m_info, inputData, m_infoSize); |
+ } |
+ |
+ png_byte dataIDAT[8] = {0, 0, 0, 0, 73, 68, 65, 84}; |
+ while (chunk < finish) { |
+ unsigned size = png_get_uint_32(chunk); |
+ if (!memcmp(chunk + 4, "fdAT", 4)) { |
+ png_save_uint_32(&dataIDAT[0], size - 4); |
+ png_process_data(m_png, m_info, dataIDAT, 8); |
+ png_process_data(m_png, m_info, chunk + 12, size); |
+ } |
+ chunk += size + 12; |
+ } |
+ png_process_data(m_png, m_info, dataIEND, 12); |
} |
- return false; |
+ return true; |
} |
png_structp pngPtr() const { return m_png; } |
png_infop infoPtr() const { return m_info; } |
- void setReadOffset(unsigned offset) { m_readOffset = offset; } |
- unsigned currentBufferSize() const { return m_currentBufferSize; } |
- bool decodingSizeOnly() const { return m_decodingSizeOnly; } |
+ int repetitionCount() const { return m_repetitionCount; } |
+ size_t imagesCount() const { return m_frames.isEmpty() ? 1 : m_frames.size() - m_posterFrame; } |
+ const FrameInfo * frameAtIndex(size_t index) const { return m_frames.isEmpty() ? &m_currentFrame : &m_frames[index + m_posterFrame]; } |
void setHasAlpha(bool hasAlpha) { m_hasAlpha = hasAlpha; } |
bool hasAlpha() const { return m_hasAlpha; } |
@@ -194,8 +371,17 @@ private: |
png_infop m_info; |
PNGImageDecoder* m_decoder; |
unsigned m_readOffset; |
- unsigned m_currentBufferSize; |
- bool m_decodingSizeOnly; |
+ unsigned m_parseOffset; |
+ unsigned m_infoSize; |
+ bool m_isAnimated; |
+ bool m_isParsed; |
+ unsigned m_width; |
+ unsigned m_height; |
+ int m_repetitionCount; |
+ size_t m_posterFrame; |
+ size_t m_visibleFrames; |
+ FrameInfo m_currentFrame; |
+ Vector<FrameInfo> m_frames; |
bool m_hasAlpha; |
OwnPtr<png_byte[]> m_interlaceBuffer; |
#if USE(QCMSLIB) |
@@ -207,6 +393,7 @@ private: |
PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes) |
: ImageDecoder(alphaOption, colorOptions, maxDecodedBytes) |
, m_hasColorProfile(false) |
+ , m_currentFrame(0) |
{ |
} |
@@ -214,6 +401,48 @@ PNGImageDecoder::~PNGImageDecoder() |
{ |
} |
+bool PNGImageDecoder::frameIsCompleteAtIndex(size_t index) const |
+{ |
+ if (!m_reader || failed()) |
+ return false; |
+ if (m_frameBufferCache.size() <= 1) |
+ return ImageDecoder::frameIsCompleteAtIndex(index); |
+ bool frameIsLoadedAtIndex = index < m_frameBufferCache.size(); |
+ return frameIsLoadedAtIndex; |
+} |
+ |
+float PNGImageDecoder::frameDurationAtIndex(size_t index) const |
+{ |
+ return (index < m_frameBufferCache.size()) ? m_frameBufferCache[index].duration() : 0; |
+} |
+ |
+int PNGImageDecoder::repetitionCount() const |
+{ |
+ return (!m_reader || failed()) ? cAnimationNone : m_reader->repetitionCount(); |
+} |
+ |
+size_t PNGImageDecoder::clearCacheExceptFrame(size_t clearExceptFrame) |
+{ |
+ if (clearExceptFrame >= m_frameBufferCache.size() || m_frameBufferCache.size() <= 1) |
+ return 0; |
+ |
+ size_t prevFrame = clearExceptFrame; |
+ if (m_frameBufferCache[clearExceptFrame].disposalMethod() == ImageFrame::DisposeOverwritePrevious) |
+ prevFrame = m_frameBufferCache[clearExceptFrame].requiredPreviousFrameIndex(); |
+ |
+ while (clearExceptFrame != kNotFound && m_frameBufferCache[clearExceptFrame].status() != ImageFrame::FrameComplete) |
+ clearExceptFrame = m_frameBufferCache[clearExceptFrame].requiredPreviousFrameIndex(); |
+ |
+ size_t frameBytesCleared = 0; |
+ for (size_t i = 0; i < m_frameBufferCache.size(); ++i) { |
+ if (i != prevFrame && i != clearExceptFrame) { |
+ frameBytesCleared += frameBytesAtIndex(i); |
+ clearFrameBuffer(i); |
+ } |
+ } |
+ return frameBytesCleared; |
+} |
+ |
#if USE(QCMSLIB) |
static void getColorProfile(png_structp png, png_infop info, ColorProfile& colorProfile, bool& sRGB) |
{ |
@@ -255,22 +484,7 @@ 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, filterType, channels; |
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlaceType, &compressionType, &filterType); |
@@ -337,17 +551,6 @@ 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) |
@@ -356,7 +559,7 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
return; |
// Initialize the framebuffer if needed. |
- ImageFrame& buffer = m_frameBufferCache[0]; |
+ ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; |
if (buffer.status() == ImageFrame::FrameEmpty) { |
png_structp png = m_reader->pngPtr(); |
if (!buffer.setSize(size().width(), size().height())) { |
@@ -365,8 +568,10 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
} |
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 (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr()) |
+ || m_currentFrame) { |
+ if (!m_reader->interlaceBuffer()) |
+ m_reader->createInterlaceBuffer(colorChannels * size().width() * size().height()); |
if (!m_reader->interlaceBuffer()) { |
longjmp(JMPBUF(png), 1); |
return; |
@@ -375,7 +580,8 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
#if USE(QCMSLIB) |
if (m_reader->colorTransform()) { |
- m_reader->createRowBuffer(colorChannels * size().width()); |
+ if (!m_reader->rowBuffer()) |
+ m_reader->createRowBuffer(colorChannels * size().width()); |
if (!m_reader->rowBuffer()) { |
longjmp(JMPBUF(png), 1); |
return; |
@@ -385,8 +591,10 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
buffer.setStatus(ImageFrame::FramePartial); |
buffer.setHasAlpha(false); |
- // For PNGs, the frame always fills the entire image. |
- buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); |
+ if (!initFrameBuffer()) { |
+ longjmp(JMPBUF(png), 1); |
+ return; |
+ } |
} |
/* libpng comments (here to explain what follows). |
@@ -438,6 +646,10 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, |
png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer); |
} |
+ // Only do incremental image display for the first frame. |
+ if (m_currentFrame) |
+ return; |
+ |
#if USE(QCMSLIB) |
if (qcms_transform* transform = m_reader->colorTransform()) { |
qcms_transform_data(transform, row, m_reader->rowBuffer(), size().width()); |
@@ -481,30 +693,171 @@ void PNGImageDecoder::complete() |
if (m_frameBufferCache.isEmpty()) |
return; |
- m_frameBufferCache[0].setStatus(ImageFrame::FrameComplete); |
+ ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; |
+ buffer.setStatus(ImageFrame::FrameComplete); |
+ |
+ if (!m_currentFrame) |
+ return; |
+ |
+ const IntRect& rect = buffer.originalFrameRect(); |
+ bool hasAlpha = m_reader->hasAlpha(); |
+ unsigned colorChannels = hasAlpha ? 4 : 3; |
+ unsigned alphaMask = 255; |
+ ImageFrame::AlphaBlendSource blendMethod = buffer.alphaBlendSource(); |
+ |
+ if (blendMethod == ImageFrame::BlendAtopPreviousFrame && !hasAlpha) |
+ blendMethod = ImageFrame::BlendAtopBgcolor; |
+ |
+ png_bytep row = m_reader->interlaceBuffer(); |
+ for (int y = rect.y(); y < rect.maxY(); ++y, row += colorChannels * size().width()) { |
+ png_bytep pixel = row; |
+#if USE(QCMSLIB) |
+ if (qcms_transform* transform = m_reader->colorTransform()) { |
+ qcms_transform_data(transform, row, m_reader->rowBuffer(), size().width()); |
+ pixel = m_reader->rowBuffer(); |
+ } |
+#endif |
+ ImageFrame::PixelData* address = buffer.getAddr(rect.x(), y); |
+ if (hasAlpha) { |
+ if (blendMethod == ImageFrame::BlendAtopBgcolor) { |
+ if (buffer.premultiplyAlpha()) { |
+ for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 4) { |
+ buffer.setRGBAPremultiply(address++, pixel[0], pixel[1], pixel[2], pixel[3]); |
+ alphaMask &= pixel[3]; |
+ } |
+ } else { |
+ for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 4) { |
+ buffer.setRGBARaw(address++, pixel[0], pixel[1], pixel[2], pixel[3]); |
+ alphaMask &= pixel[3]; |
+ } |
+ } |
+ } else { |
+ if (buffer.premultiplyAlpha()) { |
+ for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 4) { |
+ buffer.overRGBAPremultiply(address++, pixel[0], pixel[1], pixel[2], pixel[3]); |
+ alphaMask &= pixel[3]; |
+ } |
+ } else { |
+ for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 4) { |
+ buffer.overRGBARaw(address++, pixel[0], pixel[1], pixel[2], pixel[3]); |
+ alphaMask &= pixel[3]; |
+ } |
+ } |
+ } |
+ } else { |
+ for (int x = rect.x(); x < rect.maxX(); ++x, pixel += 3) { |
+ buffer.setRGBARaw(address++, pixel[0], pixel[1], pixel[2], 255); |
+ } |
+ } |
+ } |
+ |
+ if (alphaMask == 255) { |
+ if (buffer.originalFrameRect().contains(IntRect(IntPoint(), size()))) { |
+ buffer.setHasAlpha(false); |
+ } else { |
+ size_t frameIndex = m_currentFrame; |
+ const ImageFrame* prevBuffer = &m_frameBufferCache[--frameIndex]; |
+ while (frameIndex && (prevBuffer->disposalMethod() == ImageFrame::DisposeOverwritePrevious)) |
+ prevBuffer = &m_frameBufferCache[--frameIndex]; |
+ if ((prevBuffer->disposalMethod() == ImageFrame::DisposeOverwriteBgcolor) |
+ && !prevBuffer->hasAlpha() && buffer.originalFrameRect().contains(prevBuffer->originalFrameRect())) |
+ buffer.setHasAlpha(false); |
+ } |
+ } else if (blendMethod == ImageFrame::BlendAtopBgcolor && !buffer.hasAlpha()) { |
+ buffer.setHasAlpha(true); |
+ } |
+} |
+ |
+size_t PNGImageDecoder::decodeFrameCount() |
+{ |
+ return parse() ? m_reader->imagesCount() : m_frameBufferCache.size(); |
+} |
+ |
+void PNGImageDecoder::decode(size_t index) |
+{ |
+ if (!m_reader || failed()) |
+ return; |
+ |
+ Vector<size_t> framesToDecode; |
+ size_t frameToDecode = index; |
+ do { |
+ framesToDecode.append(frameToDecode); |
+ frameToDecode = m_frameBufferCache[frameToDecode].requiredPreviousFrameIndex(); |
+ } while (frameToDecode != kNotFound && m_frameBufferCache[frameToDecode].status() != ImageFrame::FrameComplete); |
+ |
+ for (auto i = framesToDecode.rbegin(); i != framesToDecode.rend(); ++i) { |
+ m_currentFrame = *i; |
+ if (!m_reader->decode(*m_data, m_currentFrame)) { |
+ setFailed(); |
+ break; |
+ } |
+ |
+ // We need more data to continue decoding. |
+ if (m_frameBufferCache[m_currentFrame].status() != ImageFrame::FrameComplete) |
+ break; |
+ } |
+} |
+ |
+void PNGImageDecoder::initializeNewFrame(size_t index) |
+{ |
+ ImageFrame* buffer = &m_frameBufferCache[index]; |
+ bool frameRectIsOpaque = (buffer->status() == ImageFrame::FrameComplete) ? !m_reader->hasAlpha() : false; |
+ buffer->setRequiredPreviousFrameIndex(findRequiredPreviousFrame(index, frameRectIsOpaque)); |
+ const PNGImageReader::FrameInfo* frame = m_reader->frameAtIndex(index); |
+ IntRect frameRect(frame->xOffset, frame->yOffset, frame->width, frame->height); |
+ buffer->setOriginalFrameRect(intersection(frameRect, IntRect(IntPoint(), size()))); |
+ buffer->setDuration(frame->duration); |
+ buffer->setDisposalMethod((frame->dispose == 2) ? ImageFrame::DisposeOverwritePrevious : (frame->dispose == 1) ? ImageFrame::DisposeOverwriteBgcolor : ImageFrame::DisposeKeep); |
+ buffer->setAlphaBlendSource((frame->blend == 1) ? ImageFrame::BlendAtopPreviousFrame : ImageFrame::BlendAtopBgcolor); |
} |
-inline bool isComplete(const PNGImageDecoder* decoder) |
+bool PNGImageDecoder::initFrameBuffer() |
{ |
- return decoder->frameIsCompleteAtIndex(0); |
+ ImageFrame& buffer = m_frameBufferCache[m_currentFrame]; |
+ |
+ const size_t requiredPreviousFrameIndex = buffer.requiredPreviousFrameIndex(); |
+ if (requiredPreviousFrameIndex == kNotFound) { |
+ // This frame doesn't rely on any previous data. |
+ buffer.zeroFillPixelData(); |
+ if (!m_currentFrame) |
+ buffer.setHasAlpha(false); |
+ } else { |
+ const ImageFrame& prevBuffer = m_frameBufferCache[requiredPreviousFrameIndex]; |
+ ASSERT(prevBuffer.status() == ImageFrame::FrameComplete); |
+ |
+ // Preserve the last frame as the starting state for this frame. |
+ if (!buffer.copyBitmapData(prevBuffer)) |
+ return setFailed(); |
+ |
+ if (prevBuffer.disposalMethod() == ImageFrame::DisposeOverwriteBgcolor) { |
+ // We want to clear the previous frame to transparent, without |
+ // affecting pixels in the image outside of the frame. |
+ const IntRect& prevRect = prevBuffer.originalFrameRect(); |
+ ASSERT(!prevRect.contains(IntRect(IntPoint(), size()))); |
+ buffer.zeroFillFrameRect(prevRect); |
+ } |
+ } |
+ return true; |
} |
-void PNGImageDecoder::decode(bool onlySize) |
+bool PNGImageDecoder::parse() |
{ |
if (failed()) |
- return; |
+ return false; |
if (!m_reader) |
m_reader = adoptPtr(new PNGImageReader(this)); |
// If we couldn't decode the image but have received all the data, decoding |
// has failed. |
- if (!m_reader->decode(*m_data, onlySize) && isAllDataReceived()) |
+ if (!m_reader->parse(*m_data) && isAllDataReceived()) |
setFailed(); |
- // If decoding is done or failed, we don't need the PNGImageReader anymore. |
- if (isComplete(this) || failed()) |
+ if (failed()) { |
m_reader.clear(); |
+ return false; |
+ } |
+ return true; |
} |
} // namespace blink |