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

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

Issue 1567053002: Animated PNG implementation Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Moved some code to PNGImageReader.cpp and PNGImageReader.h 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 1aa5b1f62ec31626cce8f7f99840de72abfc0791..eff0a2bfbdd8f9c864395db240f4244784dda2ba 100644
--- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoder.cpp
@@ -50,10 +50,57 @@ PNGImageDecoder::PNGImageDecoder(AlphaOption alphaOption,
size_t maxDecodedBytes,
size_t offset)
: ImageDecoder(alphaOption, colorOptions, maxDecodedBytes),
- m_offset(offset) {}
+ m_offset(offset),
+ m_currentFrame(0) {}
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[prevFrame].getDisposalMethod() ==
+ ImageFrame::DisposeOverwritePrevious)
+ prevFrame = m_frameBufferCache[prevFrame].requiredPreviousFrameIndex();
+
+ while (clearExceptFrame != kNotFound &&
+ m_frameBufferCache[clearExceptFrame].getStatus() !=
+ 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;
+}
+
inline float pngFixedToFloat(png_fixed_point x) {
return ((float)x) * 0.00001f;
}
@@ -111,22 +158,7 @@ inline sk_sp<SkColorSpace> readColorSpace(png_structp png, png_infop info) {
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);
@@ -200,19 +232,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,
@@ -222,7 +241,7 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
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.setSizeAndColorSpace(size().width(), size().height(),
@@ -233,9 +252,12 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
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());
+ 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;
@@ -245,8 +267,10 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
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).
@@ -298,6 +322,10 @@ void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer,
png_progressive_combine_row(m_reader->pngPtr(), row, rowBuffer);
}
+ // Only do incremental image display for the first frame.
+ if (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);
@@ -363,28 +391,203 @@ 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.getAlphaBlendSource();
+
+ 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 srcPtr = row;
+ ImageFrame::PixelData* dstRow = buffer.getAddr(rect.x(), y);
+ if (hasAlpha) {
+ if (SkColorSpaceXform* xform = colorTransform()) {
+ SkColorSpaceXform::ColorFormat colorFormat =
+ SkColorSpaceXform::kRGBA_8888_ColorFormat;
+ xform->apply(colorFormat, srcPtr, colorFormat, srcPtr, rect.width(),
+ kUnpremul_SkAlphaType);
+ }
+
+ if (blendMethod == ImageFrame::BlendAtopBgcolor) {
+ if (buffer.premultiplyAlpha()) {
+ for (auto *dstPixel = dstRow; dstPixel < dstRow + rect.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 + rect.width();
+ dstPixel++, srcPtr += 4) {
+ buffer.setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2],
+ srcPtr[3]);
+ alphaMask &= srcPtr[3];
+ }
+ }
+ } else {
+ if (buffer.premultiplyAlpha()) {
+ for (auto *dstPixel = dstRow; dstPixel < dstRow + rect.width();
+ dstPixel++, srcPtr += 4) {
+ buffer.overRGBAPremultiply(dstPixel, srcPtr[0], srcPtr[1],
+ srcPtr[2], srcPtr[3]);
+ alphaMask &= srcPtr[3];
+ }
+ } else {
+ for (auto *dstPixel = dstRow; dstPixel < dstRow + rect.width();
+ dstPixel++, srcPtr += 4) {
+ buffer.overRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2],
+ srcPtr[3]);
+ alphaMask &= srcPtr[3];
+ }
+ }
+ }
+ } else {
+ for (auto *dstPixel = dstRow; dstPixel < dstRow + rect.width();
+ dstPixel++, srcPtr += 3) {
+ buffer.setRGBARaw(dstPixel, srcPtr[0], srcPtr[1], srcPtr[2], 255);
+ }
+
+ if (SkColorSpaceXform* xform = colorTransform()) {
+ xform->apply(xformColorFormat(), dstRow, xformColorFormat(), dstRow,
+ rect.width(), kOpaque_SkAlphaType);
+ }
+ }
+ }
+
+ 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->getDisposalMethod() ==
+ ImageFrame::DisposeOverwritePrevious))
+ prevBuffer = &m_frameBufferCache[--frameIndex];
+ if ((prevBuffer->getDisposalMethod() ==
+ ImageFrame::DisposeOverwriteBgcolor) &&
+ !prevBuffer->hasAlpha() &&
+ buffer.originalFrameRect().contains(prevBuffer->originalFrameRect()))
+ buffer.setHasAlpha(false);
+ }
+ } else if (blendMethod == ImageFrame::BlendAtopBgcolor &&
+ !buffer.hasAlpha()) {
+ buffer.setHasAlpha(true);
+ }
}
-inline bool isComplete(const PNGImageDecoder* decoder) {
- return decoder->frameIsCompleteAtIndex(0);
+size_t PNGImageDecoder::decodeFrameCount() {
+ return parse() ? m_reader->imagesCount() : m_frameBufferCache.size();
}
-void PNGImageDecoder::decode(bool onlySize) {
- if (failed())
+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].getStatus() !=
+ 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].getStatus() !=
+ ImageFrame::FrameComplete)
+ break;
+ }
+}
+
+void PNGImageDecoder::initializeNewFrame(size_t index) {
+ ImageFrame* buffer = &m_frameBufferCache[index];
+ bool frameRectIsOpaque = buffer->getStatus() == ImageFrame::FrameComplete
+ ? !m_reader->hasAlpha()
+ : false;
+ buffer->setRequiredPreviousFrameIndex(
+ findRequiredPreviousFrame(index, frameRectIsOpaque));
+ const PNGImageReader::FrameInfo* frame = m_reader->frameInfo(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);
+}
+
+bool PNGImageDecoder::initFrameBuffer() {
+ 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 {
+ ImageFrame& prevBuffer = m_frameBufferCache[requiredPreviousFrameIndex];
+ DCHECK(prevBuffer.getStatus() == ImageFrame::FrameComplete);
+
+ // Preserve the last frame as the starting state for this frame.
+ if (!buffer.takeBitmapDataIfWritable(&prevBuffer)) {
+ if (!buffer.copyBitmapData(prevBuffer))
+ return setFailed();
+ }
+
+ if (prevBuffer.getDisposalMethod() == 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();
+ DCHECK(!prevRect.contains(IntRect(IntPoint(), size())));
+ buffer.zeroFillFrameRect(prevRect);
+ }
+ }
+ return true;
+}
+
+bool PNGImageDecoder::parse() {
+ if (failed())
+ return false;
+
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())
+ 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.reset();
+ return false;
+ }
+ return true;
}
} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698