| 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
|
|
|