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

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

Issue 2386453003: WIP: Implement APNG (Closed)
Patch Set: Process feedback on patch 12 Created 4 years, 1 month 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 2ef4182cbe85b28b4158c7acf551d76c72a85a91..85f08c4bd17734043dc4c65102e0a862da749a1e 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)
@@ -53,115 +53,260 @@
#define JMPBUF(png_ptr) png_ptr->jmpbuf
#endif
-namespace {
+namespace blink {
+
+PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
+ ColorSpaceOption colorOptions,
+ size_t maxDecodedBytes,
+ size_t offset)
+ : ImageDecoder(alphaOption, colorOptions, maxDecodedBytes),
+ m_offset(offset),
+ m_frameCount(0),
+ m_currentFrame(0),
+ m_repetitionCount(cAnimationLoopOnce) {}
-inline blink::PNGImageDecoder* imageDecoder(png_structp png) {
- return static_cast<blink::PNGImageDecoder*>(png_get_progressive_ptr(png));
+PNGImageDecoder::~PNGImageDecoder() {}
+
+size_t PNGImageDecoder::decodeFrameCount() {
+ parse(PNGParseQuery::PNGMetaDataQuery);
+ return m_frameCount;
}
-void PNGAPI pngHeaderAvailable(png_structp png, png_infop) {
- imageDecoder(png)->headerAvailable();
+void PNGImageDecoder::decode(size_t index) {
+ parse(PNGParseQuery::PNGMetaDataQuery);
+
+ // @TODO(joostouwerling): show complete frames even if a later frame fails.
+ if (failed())
+ return;
+
+ updateAggressivePurging(index);
+
+ Vector<size_t> framesToDecode;
+ size_t frameToDecode = index;
+
+ // This method is only called by ImageDecoder::frameBufferAtIndex if the frame
+ // status of frame |index| is not ImageFrame::FrameComplete. Therefore, it is
+ // OK that the do-while loop always appends |index| to |m_framesToDecode|,
+ // without checking for its status.
+ //
+ // The requiredPreviousFrameIndex for each frame is set in
+ // PNGImageDecoder::initializeNewFrame().
+ do {
+ framesToDecode.append(frameToDecode);
+ frameToDecode =
+ m_frameBufferCache[frameToDecode].requiredPreviousFrameIndex();
+ } while (frameToDecode != kNotFound &&
+ m_frameBufferCache[frameToDecode].getStatus() !=
+ ImageFrame::FrameComplete);
+
+ for (auto i = framesToDecode.rbegin(); i != framesToDecode.rend(); i++) {
+ m_currentFrame = *i;
+ m_reader->decode(*m_data, *i);
+ if (failed())
+ return;
+
+ // If the frame is not yet complete, we need more data to continue.
+ if (m_frameBufferCache[*i].getStatus() != ImageFrame::FrameComplete)
+ break;
+
+ if (m_purgeAggressively)
+ clearCacheExceptFrame(*i);
+ }
}
-void PNGAPI pngRowAvailable(png_structp png,
- png_bytep row,
- png_uint_32 rowIndex,
- int state) {
- imageDecoder(png)->rowAvailable(row, rowIndex, state);
+// @TODO(joostouwerling) Consolidate this with a proposed change in
+// ImageDecoder::clearCacheExceptFrame. See
+// crrev.com/2468233002
+size_t PNGImageDecoder::clearCacheExceptFrame(size_t clearExceptFrame) {
+ // As per the comments at ImageDecoder::clearCacheExceptFrame
+ if (m_frameBufferCache.size() <= 1)
+ return 0;
+
+ // We expect that after this call, we'll be asked to decode frames after
+ // this one. So we want to avoid clearing frames such that those requests
+ // would force re-decoding from the beginning of the image.
+ //
+ // When |clearExceptFrame| is e.g. DisposeKeep, simply not clearing that
+ // frame is sufficient, as the next frame will be based on it, and in
+ // general future frames can't be based on anything previous.
+ //
+ // However, if this frame is DisposeOverwritePrevious, then subsequent
+ // frames will depend on this frame's required previous frame. In this
+ // case, we need to preserve both this frame and that one.
+ size_t clearExceptFrame2 = kNotFound;
+ if (clearExceptFrame < m_frameBufferCache.size()) {
+ const ImageFrame& frame = m_frameBufferCache[clearExceptFrame];
+ if (frame.getStatus() != ImageFrame::FrameEmpty &&
+ frame.getDisposalMethod() == ImageFrame::DisposeOverwritePrevious) {
+ clearExceptFrame2 = clearExceptFrame;
+ clearExceptFrame = frame.requiredPreviousFrameIndex();
+ }
+ }
+
+ // Now |clearExceptFrame| indicates the frame that future frames will
+ // depend on. But if decoding is skipping forward past intermediate frames,
+ // this frame may be FrameEmpty. So we need to keep traversing back through
+ // the required previous frames until we find the nearest non-empty
+ // ancestor. Preserving that will minimize the amount of future decoding
+ // needed.
+ while (clearExceptFrame < m_frameBufferCache.size() &&
+ m_frameBufferCache[clearExceptFrame].getStatus() ==
+ ImageFrame::FrameEmpty)
+ clearExceptFrame =
+ m_frameBufferCache[clearExceptFrame].requiredPreviousFrameIndex();
+
+ return clearCacheExceptTwoFrames(clearExceptFrame, clearExceptFrame2);
}
-void PNGAPI pngComplete(png_structp png, png_infop) {
- imageDecoder(png)->complete();
+size_t PNGImageDecoder::clearCacheExceptTwoFrames(size_t clearExceptFrame1,
+ size_t clearExceptFrame2) {
+ size_t frameBytesCleared = 0;
+ for (size_t i = 0; i < m_frameBufferCache.size(); ++i) {
+ if (m_frameBufferCache[i].getStatus() != ImageFrame::FrameEmpty &&
+ i != clearExceptFrame1 && i != clearExceptFrame2) {
+ frameBytesCleared += frameBytesAtIndex(i);
+ clearFrameBuffer(i);
+ }
+ }
+ return frameBytesCleared;
}
-void PNGAPI pngFailed(png_structp png, png_const_charp) {
- longjmp(JMPBUF(png), 1);
+void PNGImageDecoder::clearFrameBuffer(size_t frameIndex) {
+ if (m_frameBufferCache[frameIndex].getStatus() == ImageFrame::FramePartial)
+ m_reader->clearDecodeState(frameIndex);
+
+ m_frameBufferCache[frameIndex].clearPixelData();
}
-} // 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) {
- 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_reader->parseCompleted() && 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;
}
+}
+
+// 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;
+}
+
+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));
+ buffer->setRequiredPreviousFrameIndex(
+ findRequiredPreviousFrame(index, false));
+}
- png_structp pngPtr() const { return m_png; }
- png_infop infoPtr() const { return m_info; }
+// Initialize the frame buffer before decoding. The returned boolean indicates
+// whether initialisation succeeded when it is true, false otherwise.
+bool PNGImageDecoder::initFrameBuffer(size_t index) {
+ ImageFrame* const buffer = &m_frameBufferCache[index];
- 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; }
+ // Return true if the frame is already initialised.
+ if (buffer->getStatus() != ImageFrame::FrameEmpty)
+ return true;
- png_bytep interlaceBuffer() const { return m_interlaceBuffer.get(); }
- void createInterlaceBuffer(int size) {
- m_interlaceBuffer = wrapArrayUnique(new png_byte[size]);
+ if (!buffer->setSizeAndColorSpace(size().width(), size().height(),
+ colorSpace()))
+ return false;
+
+ // Create the image buffer if:
+ // A) The image is encoded with interlacing, or
+ // B) frameShouldBeDecodedIncrementally(m_frameBufferCache) == false. In this
+ // case, the image buffer is used to store the row data, so it can be
+ // written to the frame buffer at once in PNGImageDecoder::complete().
+ // This prevents partially overwriting the content of previous frames.
+ unsigned colorChannels = m_reader->hasAlpha() ? 4 : 3;
+ png_structp png = m_reader->pngPtr();
+ if (PNG_INTERLACE_ADAM7 == png_get_interlace_type(png, m_reader->infoPtr()) ||
+ !frameShouldBeDecodedIncrementally(m_currentFrame)) {
+ m_reader->createImageBuffer(colorChannels * size().width() *
+ size().height());
+ if (!m_reader->imageBuffer())
+ return false;
}
- 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;
-};
+ buffer->setHasAlpha(false);
+ size_t requiredPreviousFrameIndex = buffer->requiredPreviousFrameIndex();
-PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
- ColorSpaceOption colorOptions,
- size_t maxDecodedBytes,
- size_t offset)
- : ImageDecoder(alphaOption, colorOptions, maxDecodedBytes),
- m_offset(offset) {}
+ // If frame |index| does not depend on any other frame, ensure the frame is
+ // fully transparent black after initialisation.
+ if (requiredPreviousFrameIndex == kNotFound) {
+ buffer->zeroFillPixelData();
+ } else {
+ ImageFrame* prevBuffer = &m_frameBufferCache[requiredPreviousFrameIndex];
+ ASSERT(prevBuffer->getStatus() == ImageFrame::FrameComplete);
+
+ // We try to reuse |prevBuffer| as starting state to avoid copying.
+ // For DisposeOverwritePrevious, the next frame will also use
+ // |prevBuffer| as its starting state, so we can't take over its image
+ // data using takeBitmapDataIfWritable. Copy the data instead.
+ if ((buffer->getDisposalMethod() == ImageFrame::DisposeOverwritePrevious ||
+ !buffer->takeBitmapDataIfWritable(prevBuffer)) &&
+ !buffer->copyBitmapData(*prevBuffer))
+ return false;
+
+ // We want to clear the previous frame to transparent, without affecting
+ // pixels in the image outside of the frame.
+ if (prevBuffer->getDisposalMethod() ==
+ ImageFrame::DisposeOverwriteBgcolor) {
+ const IntRect& prevRect = prevBuffer->originalFrameRect();
+ ASSERT(!prevRect.contains(IntRect(IntPoint(), size())));
+ buffer->zeroFillFrameRect(prevRect);
+ }
+ }
-PNGImageDecoder::~PNGImageDecoder() {}
+ buffer->setStatus(ImageFrame::FramePartial);
+ return true;
+}
inline float pngFixedToFloat(png_fixed_point x) {
return ((float)x) * 0.00001f;
@@ -223,17 +368,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;
@@ -299,7 +448,9 @@ void PNGImageDecoder::headerAvailable() {
}
}
- // Tell libpng to send us rows for interlaced pngs.
+ // Tell libpng to send us rows for interlaced pngs. Make sure the interlace
scroggo_chromium 2016/11/09 13:42:50 image* buffer
joostouwerling 2016/11/11 20:22:18 In the revisited patch, this is correct.
+ // buffer is cleared before decoding.
+ m_reader->clearImageBuffer();
if (interlaceType == PNG_INTERLACE_ADAM7)
png_set_interlace_handling(png);
@@ -309,19 +460,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)
- // 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
- }
}
void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
@@ -330,88 +468,77 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
if (m_frameBufferCache.isEmpty())
return;
- // Initialize the framebuffer if needed.
- ImageFrame& buffer = m_frameBufferCache[0];
- if (buffer.getStatus() == ImageFrame::FrameEmpty) {
- png_structp png = m_reader->pngPtr();
- if (!buffer.setSizeAndColorSpace(size().width(), size().height(),
- colorSpace())) {
- longjmp(JMPBUF(png), 1);
- return;
- }
-
- 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 (!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()));
+ ImageFrame& buffer = m_frameBufferCache[m_currentFrame];
+ if (!initFrameBuffer(m_currentFrame)) {
+ setFailed();
+ return;
}
+ // 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
- * image is interlacing, and you turned on the interlace handler,
- * this function will be called for every row in every pass.
- * Some of these rows will not be changed from the previous pass.
- * When the row is not changed, the new_row variable will be NULL.
- * The rows and passes are called in order, so you don't really
- * need the row_num and pass, but I'm supplying them because it
- * may make your life easier.
- */
+ *
+ * this function is called for every row in the image. If the
+ * image is interlacing, and you turned on the interlace handler,
+ * this function will be called for every row in every pass.
+ * Some of these rows will not be changed from the previous pass.
+ * When the row is not changed, the new_row variable will be NULL.
+ * The rows and passes are called in order, so you don't really
+ * need the row_num and pass, but I'm supplying them because it
+ * 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.
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).
- *
- * For the non-NULL rows of interlaced images, you must call
- * png_progressive_combine_row() passing in the row and the
- * old row. You can call this function for NULL rows (it will
- * just return) and for non-interlaced images (it just does the
- * memcpy for you) if it will make the code easier. Thus, you
- * can just do this for all cases:
- *
- * png_progressive_combine_row(png_ptr, old_row, new_row);
- *
- * where old_row is what was displayed for previous rows. Note
- * that the first pass (pass == 0 really) will completely cover
- * the old row, so the rows do not have to be initialized. After
- * the first pass (and only for interlaced images), you will have
- * to pass the current row, and the function will combine the
- * old row and the new row.
- */
+ *
+ * For the non-NULL rows of interlaced images, you must call
+ * png_progressive_combine_row() passing in the row and the
+ * old row. You can call this function for NULL rows (it will
+ * just return) and for non-interlaced images (it just does the
+ * memcpy for you) if it will make the code easier. Thus, you
+ * can just do this for all cases:
+ *
+ * png_progressive_combine_row(png_ptr, old_row, new_row);
+ *
+ * where old_row is what was displayed for previous rows. Note
+ * that the first pass (pass == 0 really) will completely cover
+ * the old row, so the rows do not have to be initialized. After
+ * the first pass (and only for interlaced images), you will have
+ * to pass the current row, and the function will combine the
+ * old row and the new row.
+ */
bool hasAlpha = m_reader->hasAlpha();
png_bytep row = rowBuffer;
- if (png_bytep interlaceBuffer = m_reader->interlaceBuffer()) {
+ if (png_bytep imageBuffer = m_reader->imageBuffer()) {
unsigned colorChannels = hasAlpha ? 4 : 3;
- row = interlaceBuffer + (rowIndex * colorChannels * size().width());
+ row = imageBuffer + (rowIndex * colorChannels * size().width());
png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer);
}
+ // For non-first frames, don't write rows incrementally to the buffer, since
scroggo_chromium 2016/11/09 13:42:50 This comment is no longer correct. This now applie
joostouwerling 2016/11/11 20:22:18 This is now removed in the revisited patch.
+ // this may result in partial frames being displayed. Instead, write the
+ // rows in the complete() callback.
+ if (!frameShouldBeDecodedIncrementally(m_currentFrame))
+ return;
+
// 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);
+ ImageFrame::PixelData* const dstRow = buffer.getAddr(frameRect.x(), y);
unsigned alphaMask = 255;
- int width = size().width();
+ int width = frameRect.width();
png_bytep srcPtr = row;
if (hasAlpha) {
@@ -468,32 +595,120 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
buffer.setPixelsChanged(true);
}
+bool PNGImageDecoder::frameIsCompleteAtIndex(size_t index) const {
+ // @TODO(joostouwerling): show complete frames even if a later frame fails.
+ if (failed())
+ return false;
+ if (index == 0)
+ return ImageDecoder::frameIsCompleteAtIndex(index);
+
+ // For non-first frames, the frame is considered complete if all frame data
+ // has been received. Non-first frames are reported by |m_reader| once it has
+ // parsed all data for that frame, so we can simply return if the index
+ // exists in |m_frameBufferCache| here.
+ return (index < m_frameBufferCache.size());
+}
+
+float PNGImageDecoder::frameDurationAtIndex(size_t index) const {
+ return (index < m_frameBufferCache.size()
+ ? m_frameBufferCache[index].duration()
+ : 0);
+}
+
void PNGImageDecoder::complete() {
if (m_frameBufferCache.isEmpty())
return;
- m_frameBufferCache[0].setStatus(ImageFrame::FrameComplete);
-}
+ // @TODO(joostouwerling) if necessary, do a check if all expected data has
+ // been received. This is because the IEND chunk is sent
+ // artificially. The necessity of this check depends on
+ // how libpng handles in- and overcomplete frame data.
+ ImageFrame* buffer = &m_frameBufferCache[m_currentFrame];
+
+ // The pixels for the first frame or any non-first frame with no required
+ // previous frame are already written to the framebuffer in rowAvailable.
+ if (frameShouldBeDecodedIncrementally(m_currentFrame)) {
+ m_reader->clearImageBuffer();
+ buffer->setStatus(ImageFrame::FrameComplete);
+ return;
+ }
-inline bool isComplete(const PNGImageDecoder* decoder) {
- return decoder->frameIsCompleteAtIndex(0);
-}
+ // For non-first frames, the frame data was written to the image buffer in
+ // rowAvailable. Write the decoded pixels from the the image buffer to the
+ // the frame buffer.
+ png_bytep image = m_reader->imageBuffer();
-void PNGImageDecoder::decode(bool onlySize) {
- if (failed())
- return;
+ IntRect frameRect = buffer->originalFrameRect();
+ bool hasAlpha = m_reader->hasAlpha();
+ unsigned colorChannels = hasAlpha ? 4 : 3;
+ unsigned alphaMask = 255;
- if (!m_reader)
- m_reader = wrapUnique(new PNGImageReader(this, m_offset));
+ // TODO(joostouwerling): for now, this is a straight copy of the code in
+ // rowAvailable, except for writing multiple rows. Alpha
+ // blending and possible code sharing need to be added.
+ for (int y = frameRect.y(); y < frameRect.maxY();
+ ++y, image += colorChannels * size().width()) {
+ ImageFrame::PixelData* const dstRow = buffer->getAddr(frameRect.x(), y);
+ unsigned alphaMask = 255;
+ int width = frameRect.width();
+
+ png_bytep srcPtr = image;
+ if (hasAlpha) {
+ // Here we apply the color space transformation to the dst space.
+ // It does not really make sense to transform to a gamma-encoded
+ // space and then immediately after, perform a linear premultiply.
+ // Ideally we would pass kPremul_SkAlphaType to xform->apply(),
+ // instructing SkColorSpaceXform to perform the linear premultiply
+ // while the pixels are a linear space.
+ // We cannot do this because when we apply the gamma encoding after
+ // the premultiply, we will very likely end up with valid pixels
+ // where R, G, and/or B are greater than A. The legacy drawing
+ // pipeline does not know how to handle this.
+ if (SkColorSpaceXform* xform = colorTransform()) {
+ SkColorSpaceXform::ColorFormat colorFormat =
+ SkColorSpaceXform::kRGBA_8888_ColorFormat;
+ xform->apply(colorFormat, dstRow, colorFormat, srcPtr, size().width(),
+ kUnpremul_SkAlphaType);
+ srcPtr = (png_bytep)dstRow;
+ }
- // 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 (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;
+ dstPixel++, srcPtr += 3) {
+ buffer->setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], 255);
+ }
+
+ // We'll apply the color space xform to opaque pixels after they have been
+ // written to the ImageFrame, purely because SkColorSpaceXform supports
+ // RGBA (and not RGB).
+ if (SkColorSpaceXform* xform = colorTransform()) {
+ xform->apply(xformColorFormat(), dstRow, xformColorFormat(), dstRow,
+ size().width(), kOpaque_SkAlphaType);
+ }
+ }
+ }
+
+ if (alphaMask != 255 && !buffer->hasAlpha())
+ buffer->setHasAlpha(true);
- // If decoding is done or failed, we don't need the PNGImageReader anymore.
- if (isComplete(this) || failed())
- m_reader.reset();
+ m_reader->clearImageBuffer();
+ buffer->setPixelsChanged(true);
+ buffer->setStatus(ImageFrame::FrameComplete);
}
} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698