Index: third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
diff --git a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
index 07dd9de56970d610b5895b1f6ebbb0dc0d9b8ca7..5527cafcfd98293d59573b405d2f0b9d594ddf23 100644 |
--- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
@@ -33,9 +33,12 @@ |
namespace blink { |
-GIFImageDecoder::GIFImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes) |
+GIFImageDecoder::GIFImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes, ImageFrame::ColorType colorType) |
: ImageDecoder(alphaOption, colorOptions, maxDecodedBytes) |
+ , phaveDecodedRow(&GIFImageDecoder::haveDecodedRowIndex8) |
, m_repetitionCount(cAnimationLoopOnce) |
+ , m_colorMode(colorType) |
+ , m_forceN32Decoding(false) |
{ |
} |
@@ -102,8 +105,9 @@ bool GIFImageDecoder::setFailed() |
return ImageDecoder::setFailed(); |
} |
-bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator rowBegin, size_t width, size_t rowNumber, unsigned repeatCount, bool writeTransparentPixels) |
+bool GIFImageDecoder::haveDecodedRowN32(size_t frameIndex, GIFRow::const_iterator rowBegin, size_t width, size_t rowNumber, unsigned repeatCount, bool writeTransparentPixels) |
{ |
+ ASSERT(m_colorMode == ImageFrame::N32 || m_forceN32Decoding); |
const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
// The pixel data and coordinates supplied to us are relative to the frame's |
// origin within the entire image size, i.e. |
@@ -125,11 +129,7 @@ bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator r |
GIFColorMap::Table::const_iterator colorTableIter = colorTable.begin(); |
- // Initialize the frame if necessary. |
ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
- if ((buffer.status() == ImageFrame::FrameEmpty) && !initFrameBuffer(frameIndex)) |
- return false; |
- |
const size_t transparentPixel = frameContext->transparentPixel(); |
GIFRow::const_iterator rowEnd = rowBegin + (xEnd - xBegin); |
ImageFrame::PixelData* currentAddress = buffer.getAddr(xBegin, yBegin); |
@@ -174,6 +174,73 @@ bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator r |
return true; |
} |
+bool GIFImageDecoder::haveDecodedRowIndex8(size_t frameIndex, GIFRow::const_iterator rowBegin, size_t width, size_t rowNumber, unsigned repeatCount, bool writeTransparentPixels) |
+{ |
+ ASSERT(m_colorMode == ImageFrame::Index8 && !m_forceN32Decoding); |
+ const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
+ // The pixel data and coordinates supplied to us are relative to the frame's |
+ // origin within the entire image size, i.e. |
+ // (frameContext->xOffset, frameContext->yOffset). There is no guarantee |
+ // that width == (size().width() - frameContext->xOffset), so |
+ // we must ensure we don't run off the end of either the source data or the |
+ // row's X-coordinates. |
+ const int xBegin = frameContext->xOffset(); |
+ const int yBegin = frameContext->yOffset() + rowNumber; |
+ const int xEnd = std::min(static_cast<int>(frameContext->xOffset() + width), size().width()); |
+ const int yEnd = std::min(static_cast<int>(frameContext->yOffset() + rowNumber + repeatCount), size().height()); |
+ if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin)) |
+ return true; |
+ |
+ const GIFColorMap::Table& colorTable = frameContext->localColorMap().isDefined() ? frameContext->localColorMap().table() : m_reader->globalColorMap().table(); |
+ |
+ if (colorTable.isEmpty()) |
+ return true; |
+ |
+ |
+ ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
+ const size_t transparentPixel = frameContext->transparentPixel(); |
+ GIFRow::const_iterator rowEnd = rowBegin + (xEnd - xBegin); |
+ ImageFrame::PixelData8* currentAddress = buffer.getAddr8(xBegin, yBegin); |
+ |
+ bool opaque = true; |
+ if (transparentPixel < colorTable.size()) { |
+ // writeTransparentPixels is writing without check. |
+ writeTransparentPixels = writeTransparentPixels || buffer.requiredPreviousFrameIndex() == kNotFound; |
+ if (writeTransparentPixels) { |
+ for (; rowBegin != rowEnd;) { |
+ opaque = opaque && (*rowBegin ^ transparentPixel); |
+ *currentAddress++ = *rowBegin++; |
+ } |
+ } else { |
+ for (; rowBegin != rowEnd; ++rowBegin, ++currentAddress) { |
+ size_t index = *rowBegin; |
+ if (index == transparentPixel) { |
+ opaque = false; |
+ } else { |
+ *currentAddress = index; |
+ } |
+ } |
+ } |
+ } else { |
+ // No transparency to deal with. |
+ for (; rowBegin != rowEnd;) |
+ *currentAddress++ = *rowBegin++; |
+ } |
+ |
+ m_currentBufferSawAlpha = !opaque; |
+ |
+ // Tell the frame to copy the row data if specified. |
+ if (repeatCount > 1) { |
+ const int rowBytes = (xEnd - xBegin) * sizeof(uint8_t); |
+ const ImageFrame::PixelData8* const startAddr = buffer.getAddr8(xBegin, yBegin); |
+ for (int destY = yBegin + 1; destY < yEnd; ++destY) |
+ memcpy(buffer.getAddr8(xBegin, destY), startAddr, rowBytes); |
+ } |
+ |
+ buffer.setPixelsChanged(true); |
+ return true; |
+} |
+ |
bool GIFImageDecoder::parseCompleted() const |
{ |
return m_reader && m_reader->parseCompleted(); |
@@ -264,6 +331,9 @@ void GIFImageDecoder::initializeNewFrame(size_t index) |
void GIFImageDecoder::decode(size_t index) |
{ |
+ phaveDecodedRow = (m_colorMode == ImageFrame::Index8 && !m_forceN32Decoding) |
+ ? (&GIFImageDecoder::haveDecodedRowIndex8) |
+ : (&GIFImageDecoder::haveDecodedRowN32); |
parse(GIFFrameCountQuery); |
if (failed()) |
@@ -277,9 +347,24 @@ void GIFImageDecoder::decode(size_t index) |
} while (frameToDecode != kNotFound && m_frameBufferCache[frameToDecode].status() != ImageFrame::FrameComplete); |
for (auto i = framesToDecode.rbegin(); i != framesToDecode.rend(); ++i) { |
+ bool notSuitableForIndex8(m_forceN32Decoding); |
if (!m_reader->decode(*i)) { |
- setFailed(); |
- return; |
+ // Decoder asks to switch color mode as Index8 is not applicable. Try to decode using N32. |
+ // This happens only when transparent pixels showing previous frames or offsets are detected |
+ // and table is changed. |
+ if (notSuitableForIndex8 != m_forceN32Decoding) { |
+ ASSERT(m_colorMode == ImageFrame::Index8); |
+ clearFrameBuffer(*i); |
+ // N32 decoding would continue until there is non dependent Index8 frame detected, see initFrameBuffer(). |
+ phaveDecodedRow = &GIFImageDecoder::haveDecodedRowN32; |
+ if (!m_reader->decode(*i)) { |
+ setFailed(); |
+ return; |
+ } |
+ } else { |
+ setFailed(); |
+ return; |
+ } |
} |
// We need more data to continue decoding. |
@@ -293,6 +378,15 @@ void GIFImageDecoder::decode(size_t index) |
setFailed(); |
} |
+void GIFImageDecoder::setForceN32Decoding(bool value) |
+{ |
+ ASSERT(m_colorMode == ImageFrame::Index8); |
+ m_forceN32Decoding = value; |
+ phaveDecodedRow = (!value && m_colorMode == ImageFrame::Index8) |
+ ? (&GIFImageDecoder::haveDecodedRowIndex8) |
+ : (&GIFImageDecoder::haveDecodedRowN32); |
+} |
+ |
void GIFImageDecoder::parse(GIFParseQuery query) |
{ |
if (failed()) |
@@ -307,7 +401,7 @@ void GIFImageDecoder::parse(GIFParseQuery query) |
setFailed(); |
} |
-bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
+bool GIFImageDecoder::initFrameBufferN32(size_t frameIndex) |
{ |
// Initialize the frame rect in our buffer. |
ImageFrame* const buffer = &m_frameBufferCache[frameIndex]; |
@@ -342,4 +436,135 @@ bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
return true; |
} |
+static bool framesUseSameColorTable(const GIFFrameContext& frame1, const GIFFrameContext& frame2) |
+{ |
+ auto frame1Local = frame1.localColorMap(); |
+ auto frame2Local = frame2.localColorMap(); |
+ bool useFrame1Local = frame1Local.isDefined(); |
+ bool useFrame2Local = frame2Local.isDefined(); |
+ if (useFrame1Local != useFrame2Local) |
+ return false; |
+ size_t transparent1 = frame1.transparentPixel(); |
+ size_t transparent2 = frame2.transparentPixel(); |
+ if (!useFrame1Local) { |
+ // They both use global ColorMap, check if transparent pixels are the same. |
+ ASSERT(!useFrame2Local); |
+ return (transparent1 == transparent2); |
+ } |
+ // Both use local color maps, check if it is the same. |
+ return ((frame1Local.getPosition() == frame2Local.getPosition()) && (transparent1 == transparent2)); |
+} |
+ |
+// Helper method for recomputing if frame decoding has no dependency to previous frames. |
+void GIFImageDecoder::updateRequiredPreviousFrame(ImageFrame* buffer, const GIFFrameContext& frameContext) |
+{ |
+ ASSERT(m_colorMode == ImageFrame::Index8); |
+ if (buffer->requiredPreviousFrameIndex() == kNotFound) |
+ return; |
+ |
+ bool isFullScreen = frameContext.frameRect().contains(IntRect(IntPoint(), size())); |
+ const GIFColorMap::Table& colorTable = frameContext.localColorMap().isDefined() |
+ ? frameContext.localColorMap().table() : m_reader->globalColorMap().table(); |
+ |
+ // If transparent pixel is outside color table, there is no transparent pixel. |
+ // e.g. Color table in some of the GIFs is having 40 elements and transparentPixel set to 0xFF. |
+ bool opaque = frameContext.transparentPixel() >= colorTable.size(); |
+ |
+ // If fullscreen and opaque, the frame is independent of previous frame. |
+ if (isFullScreen && opaque) |
+ buffer->setRequiredPreviousFrameIndex(kNotFound); |
+} |
+ |
+bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
+{ |
+ ImageFrame* buffer = &m_frameBufferCache[frameIndex]; |
+ if (buffer->status() != ImageFrame::FrameEmpty) |
+ return true; |
+ |
+ if (m_colorMode == ImageFrame::N32) { |
+ return initFrameBufferN32(frameIndex); |
+ } |
+ const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
+ updateRequiredPreviousFrame(buffer, *frameContext); |
+ size_t requiredPreviousFrameIndex = buffer->requiredPreviousFrameIndex(); |
+ |
+ if (requiredPreviousFrameIndex == kNotFound) { |
+ // This frame doesn't rely on any previous data and it is OK to |
+ // switch back from forced N32 to Index8. |
+ setForceN32Decoding(false); |
+ // phaveDecodedRow = &GIFImageDecoder::haveDecodedRowIndex8; |
+ const GIFColorMap::Table& colorTable = frameContext->localColorMap().isDefined() |
+ ? frameContext->localColorMap().table() : m_reader->globalColorMap().table(); |
+ if (!buffer->setSizeIndex8(size().width(), size().height(), colorTable, frameContext->transparentPixel())) |
+ return setFailed(); |
+ } else { |
+ const ImageFrame* prevBuffer = &m_frameBufferCache[requiredPreviousFrameIndex]; |
+ ASSERT(prevBuffer->status() == ImageFrame::FrameComplete); |
+ |
+ // Preserve the last frame as the starting state for this frame. |
+ bool useN32 = true; |
+ if (!m_forceN32Decoding) { |
+ // If color table is different from previous frame and in the same time, there is transparency |
+ // or the frame is subrect, cannot do Index8. Otherwise, with the same table, no problem to continue |
+ // with Index8 and start decoding from a copy of required frame. |
+ if (framesUseSameColorTable(*frameContext, *(m_reader->frameContext(requiredPreviousFrameIndex)))) { |
+ useN32 = false; |
+ if (!buffer->copyBitmapData(*prevBuffer)) |
+ return setFailed(); |
+ ASSERT(buffer->getSkBitmap().colorType() == kIndex_8_SkColorType); |
+ } else { |
+ setForceN32Decoding(true); |
+ useN32 = true; |
+ } |
+ } |
+ // For unsupported Index8 and N32, next frame starts as previous frame copy to N32. |
+ if (useN32 && !buffer->copyBitmapData(*prevBuffer, ImageFrame::N32)) |
+ 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); |
+ } |
+ } |
+ |
+ // Update our status to be partially complete. |
+ buffer->setStatus(ImageFrame::FramePartial); |
+ |
+ // Reset the alpha pixel tracker for this frame. |
+ m_currentBufferSawAlpha = false; |
+ return true; |
+} |
+ |
+bool GIFImageDecoder::canDecodeTo(size_t index, ImageFrame::ColorType outputType) |
+{ |
+ if ((index >= frameCount()) || (m_colorMode == ImageFrame::N32) || failed()) |
+ return (outputType == m_colorMode); |
+ |
+ // Go from one to previous until calculating if Index8 is supported. |
+ size_t frameToDecode = index; |
+ ImageFrame::ColorType calculatedOutput = ImageFrame::Index8; |
+ while (frameToDecode != kNotFound) { |
+ ImageFrame* buffer = &m_frameBufferCache[frameToDecode]; |
+ if (buffer->status() == ImageFrame::FrameComplete) { |
+ // In this case, color type from complete frame is kept. |
+ calculatedOutput = static_cast<ImageFrame::ColorType>(buffer->getSkBitmap().colorType()); |
+ break; |
+ } |
+ const GIFFrameContext* frameContext = m_reader->frameContext(frameToDecode); |
+ updateRequiredPreviousFrame(buffer, *frameContext); |
+ if (buffer->requiredPreviousFrameIndex() == kNotFound) |
+ break; |
+ frameToDecode = m_frameBufferCache[frameToDecode].requiredPreviousFrameIndex(); |
+ if (frameToDecode != kNotFound && !framesUseSameColorTable(*frameContext, *(m_reader->frameContext(frameToDecode)))) { |
+ // If dependent to previous frame and they don't use the same color table, not possible to use Index8. |
+ calculatedOutput = ImageFrame::N32; |
+ break; |
+ } |
+ } |
+ return (calculatedOutput == outputType); |
+} |
+ |
} // namespace blink |