Chromium Code Reviews| 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 cc5954edf9de97a6391a4c904dc4826cc341741d..298081f42d337afebc2ce6e53838240a413e1272 100644 |
| --- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
| +++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
| @@ -28,13 +28,13 @@ |
| #include <limits> |
| #include "platform/image-decoders/gif/GIFImageReader.h" |
| #include "wtf/NotFound.h" |
| -#include "wtf/PassOwnPtr.h" |
|
scroggo_chromium
2016/04/29 19:48:14
Unrelated change?
|
| 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) |
| , m_repetitionCount(cAnimationLoopOnce) |
| + , m_requestedColorMode(colorType) |
| { |
| } |
| @@ -101,36 +101,51 @@ 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) |
| +static inline const GIFColorMap& getColorMap(const GIFFrameContext& frameContext, const GIFImageReader& reader) |
| +{ |
| + return (frameContext.localColorMap().isDefined() ? frameContext.localColorMap() : reader.globalColorMap()); |
| +} |
| + |
| +void GIFImageDecoder::haveDecodedRow(const GIFFrameContext& frameContext, GIFRow::const_iterator rowBegin, size_t rowNumber, unsigned repeatCount, bool writeTransparentPixels) |
| { |
| - 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()); |
| + const size_t width = frameContext.width(); |
| + const int xBegin = frameContext.xOffset(); |
| + const int yBegin = frameContext.yOffset() + rowNumber; |
| + const int xEnd = std::min(static_cast<int>(xBegin + width), size().width()); |
| + const int yEnd = std::min(static_cast<int>(yBegin + repeatCount), size().height()); |
| if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin)) |
| - return true; |
| + return; |
| - const GIFColorMap::Table& colorTable = frameContext->localColorMap().isDefined() ? frameContext->localColorMap().table() : m_reader->globalColorMap().table(); |
| + const GIFColorTable& colorTable = getColorMap(frameContext, *m_reader).table(); |
| if (colorTable.isEmpty()) |
| - return true; |
| + return; |
| - GIFColorMap::Table::const_iterator colorTableIter = colorTable.begin(); |
| + ImageFrame& buffer = m_frameBufferCache[frameContext.frameId()]; |
|
scroggo_chromium
2016/04/29 19:48:15
As I understand it, we will now have always initia
|
| + GIFRow::const_iterator rowEnd = rowBegin + (xEnd - xBegin); |
| - // Initialize the frame if necessary. |
| - ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
| - if ((buffer.status() == ImageFrame::FrameEmpty) && !initFrameBuffer(frameIndex)) |
| - return false; |
| + (this->*m_writeRowFunction)(frameContext, buffer, colorTable, rowBegin, rowEnd, xBegin, yBegin, writeTransparentPixels); |
| - const size_t transparentPixel = frameContext->transparentPixel(); |
| - GIFRow::const_iterator rowEnd = rowBegin + (xEnd - xBegin); |
| + // Tell the frame to copy the row data if need be. |
| + if (repeatCount > 1) |
| + (*m_copyRowNTimesFunction)(buffer, xBegin, xEnd, yBegin, yEnd); |
|
scroggo_chromium
2016/04/29 19:48:14
I found it weird that this method does not take N
|
| + |
| + buffer.setPixelsChanged(true); |
| +} |
| + |
| + |
| +void GIFImageDecoder::writeRowN32(const GIFFrameContext& frameContext, ImageFrame& buffer, const GIFColorTable& colorTable, GIFRow::const_iterator rowBegin, GIFRow::const_iterator rowEnd, int xBegin, int yBegin, bool writeTransparentPixels) |
| +{ |
| + ASSERT(m_frameBufferCache[frameContext.frameId()].bitmap().colorType() == kN32_SkColorType); |
| + |
| + GIFColorTable::const_iterator colorTableIter = colorTable.begin(); |
| + const size_t transparentPixel = frameContext.transparentPixel(); |
| ImageFrame::PixelData* currentAddress = buffer.getAddr(xBegin, yBegin); |
| // We may or may not need to write transparent pixels to the buffer. |
| @@ -164,13 +179,72 @@ bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator r |
| m_currentBufferSawAlpha = true; |
| } |
| } |
| +} |
| - // Tell the frame to copy the row data if need be. |
| - if (repeatCount > 1) |
| - buffer.copyRowNTimes(xBegin, xEnd, yBegin, yEnd); |
| +void GIFImageDecoder::writeRowIndex8(const GIFFrameContext& frameContext, ImageFrame& buffer, const GIFColorTable& colorTable, GIFRow::const_iterator rowBegin, GIFRow::const_iterator rowEnd, int xBegin, int yBegin, bool writeTransparentPixels) |
| +{ |
| + ASSERT(m_frameBufferCache[frameContext.frameId()].bitmap().colorType() == kIndex_8_SkColorType); |
| + |
| + const size_t transparentPixel = frameContext.transparentPixel(); |
| + ImageFrame::PixelData8* currentAddress = buffer.getAddr8(xBegin, yBegin); |
| + |
| + bool opaque = true; |
| + // writeTransparentPixels is writing without check, but calculates if there |
|
scroggo_chromium
2016/04/29 19:48:15
What does this mean? It sure looks like if (writeT
|
| + // is transparency (m_currentBufferSawAlpha). If transparency was found in |
| + // previous rows, no need to calculate here. |
|
scroggo_chromium
2016/04/29 19:48:15
Would writeRowN32 benefit from this optimization?
|
| + writeTransparentPixels = writeTransparentPixels || buffer.requiredPreviousFrameIndex() == kNotFound; |
| + if (transparentPixel < colorTable.size() && !(writeTransparentPixels && m_currentBufferSawAlpha)) { |
| + if (writeTransparentPixels) { |
| + while (rowBegin != rowEnd) { |
| + opaque = opaque && (*rowBegin ^ transparentPixel); |
| + *currentAddress++ = *rowBegin++; |
| + } |
| + } else { |
| + for (; rowBegin != rowEnd; ++rowBegin, ++currentAddress) { |
| + size_t index = *rowBegin; |
| + if (index == transparentPixel) { |
|
scroggo_chromium
2016/04/29 19:48:15
I personally prefer braces here, but the style gui
|
| + opaque = false; |
| + } else { |
| + *currentAddress = index; |
| + } |
| + } |
| + } |
| + } else { |
| + // No transparency to deal with. |
|
scroggo_chromium
2016/04/29 19:48:15
I think this comment is misleading. We're not keep
|
| + if (rowEnd - rowBegin == size().width()) { |
| + size_t* destination = (size_t*)currentAddress; |
| + const size_t* source = (const size_t*)rowBegin; |
| + const size_t count = (rowEnd - rowBegin + sizeof(size_t) - 1) / sizeof(size_t); |
| + const size_t* sourceEnd = source + count; |
| + while (source != sourceEnd) |
|
scroggo_chromium
2016/04/29 19:48:14
Why is this not a memcpy?
|
| + *destination++ = *source++; |
| + } else { |
| + while (rowBegin != rowEnd) |
|
scroggo_chromium
2016/04/29 19:48:15
Can this be a memcpy?
|
| + *currentAddress++ = *rowBegin++; |
| + } |
| + } |
| - buffer.setPixelsChanged(true); |
| - return true; |
| + if (!opaque) |
| + m_currentBufferSawAlpha = true; |
| +} |
| + |
| +// Copies the pixel data at [(xBegin, yBegin), (xEnd, yEnd)) to the |
|
scroggo_chromium
2016/04/29 19:48:15
Don't these actually copy the data from [(xBegin,
|
| +// same X-coordinates on each subsequent row up to but not including |
| +// yEnd. |
| +static void copyRowNTimesFunctionN32(ImageFrame& buffer, int xBegin, int xEnd, int yBegin, int yEnd) |
| +{ |
| + const int rowBytes = (xEnd - xBegin) * sizeof(ImageFrame::PixelData); |
|
scroggo_chromium
2016/04/29 19:48:15
I'm not sure "rowBytes" is the correct name here.
|
| + const ImageFrame::PixelData* const startAddr = buffer.getAddr(xBegin, yBegin); |
| + for (int destY = yBegin + 1; destY < yEnd; ++destY) |
| + memcpy(buffer.getAddr(xBegin, destY), startAddr, rowBytes); |
| +} |
| + |
| +static void copyRowNTimesFunctionIndex8(ImageFrame& buffer, int xBegin, int xEnd, int yBegin, int yEnd) |
| +{ |
| + const int rowBytes = (xEnd - xBegin) * sizeof(ImageFrame::PixelData8); |
| + const ImageFrame::PixelData8* const startAddr = buffer.getAddr8(xBegin, yBegin); |
| + for (int destY = yBegin + 1; destY < yEnd; ++destY) |
| + memcpy(buffer.getAddr8(xBegin, destY), startAddr, rowBytes); |
| } |
| bool GIFImageDecoder::parseCompleted() const |
| @@ -292,6 +366,17 @@ void GIFImageDecoder::decode(size_t index) |
| setFailed(); |
| } |
| +void GIFImageDecoder::setupHaveDecodedRowCallbacks(bool isIndex8) |
| +{ |
| + if (isIndex8) { |
| + m_writeRowFunction = &GIFImageDecoder::writeRowIndex8; |
| + m_copyRowNTimesFunction = ©RowNTimesFunctionIndex8; |
| + } else { |
| + m_writeRowFunction = &GIFImageDecoder::writeRowN32; |
| + m_copyRowNTimesFunction = ©RowNTimesFunctionN32; |
| + } |
| +} |
| + |
| void GIFImageDecoder::parse(GIFParseQuery query) |
| { |
| if (failed()) |
| @@ -306,32 +391,169 @@ void GIFImageDecoder::parse(GIFParseQuery query) |
| setFailed(); |
| } |
| -bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
| +bool GIFImageDecoder::initFrameBufferFromPrevious(ImageFrame* buffer, const ImageFrame& previousBuffer, ImageFrame::ColorType targetType, size_t transparentIndex) |
| +{ |
| + // Preserve the last frame as the starting state for this frame. |
| + if (!buffer->copyBitmapData(previousBuffer, targetType)) |
| + return false; |
| + |
| + if (previousBuffer.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 = previousBuffer.originalFrameRect(); |
| + ASSERT(!prevRect.contains(IntRect(IntPoint(), size()))); |
| + if (targetType == ImageFrame::Index8) |
| + buffer->zeroFillFrameRectIndex8(prevRect, (transparentIndex > 255) ? 255 : transparentIndex); |
|
scroggo_chromium
2016/04/29 19:48:14
I think I've brought this up before - this looks l
|
| + else |
| + buffer->zeroFillFrameRect(prevRect); |
| + } |
| + return true; |
| +} |
| + |
| +bool GIFImageDecoder::initFrameBufferN32(size_t frameIndex) |
| { |
| + setupHaveDecodedRowCallbacks(false); |
| + |
| // Initialize the frame rect in our buffer. |
| - ImageFrame* const buffer = &m_frameBufferCache[frameIndex]; |
| + ImageFrame* buffer = &m_frameBufferCache[frameIndex]; |
| size_t requiredPreviousFrameIndex = buffer->requiredPreviousFrameIndex(); |
| if (requiredPreviousFrameIndex == kNotFound) { |
| // This frame doesn't rely on any previous data. |
|
scroggo_chromium
2016/04/29 19:48:15
If it does not rely on previous data, can we not s
|
| - if (!buffer->setSize(size().width(), size().height())) |
| + if (!buffer->setSize(size().width(), size().height(), getBackgroundColor(frameIndex))) |
| 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. |
| - if (!buffer->copyBitmapData(*prevBuffer)) |
| + if (!initFrameBufferFromPrevious(buffer, *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::isIndex8Applicable(const GIFFrameContext& frame, size_t requiredPreviousFrameIndex) const |
| +{ |
| + const GIFColorMap& colorMap = getColorMap(frame, *m_reader); |
| + const bool useGlobalColorMap = !frame.localColorMap().isDefined(); |
| + const bool transparencySupported = colorMap.getTableSize() < 256 || frame.transparentPixel() >= colorMap.getTableSize(); |
| + |
| + if (requiredPreviousFrameIndex == kNotFound) { |
| + if (!transparencySupported |
|
scroggo_chromium
2016/04/29 19:48:14
Why not switch this from:
if (condition) {
retu
|
| + && useGlobalColorMap && m_reader->backgroundIndex() >= colorMap.getTableSize() |
| + && (!isAllDataReceived() || !frame.frameRect().contains(IntRect(IntPoint(), size())))) { |
| + // Background is filled with transparency or with background color - |
| + // background color is used for opaque gifs. |
| + // Return false when there is a need for transparency and no space |
| + // in colormap for it; when colormap is opaque and full (it has 256 |
| + // entries) and at the same time |
| + // - image is not fully received, so missing part would need to be |
| + // presented as transparent, or |
| + // - the frame is not fullscreen, i.e. it would be decoded to a sub |
| + // rectangle of the image rectangle. |
| + return false; |
| + } |
| + return true; |
| + } |
| + |
| + const GIFFrameContext& previousFrame(*(m_reader->frameContext(requiredPreviousFrameIndex))); |
| + |
| + // Skip Index8 for DisposeOverwriteBgcolor disposal method if there is no transparency. |
| + if (previousFrame.disposalMethod() == ImageFrame::DisposeOverwriteBgcolor && !transparencySupported) |
| + return false; |
| + |
| + // If frame shares colormap with required previous frame, it is possible to keep using Index8. |
| + const GIFColorMap& previousColorMap = getColorMap(previousFrame, *m_reader); |
| + return ((colorMap.getPosition() == previousColorMap.getPosition()) && (frame.transparentPixel() == previousFrame.transparentPixel())); |
| +} |
| + |
| +// Helper method to check if a previous frame dependency can be removed. |
| +void GIFImageDecoder::updateRequiredPreviousFrame(ImageFrame* buffer, const GIFFrameContext& frameContext) |
| +{ |
| + ASSERT(m_requestedColorMode == ImageFrame::Index8); |
| + if (buffer->requiredPreviousFrameIndex() == kNotFound) |
| + return; |
| + |
| + bool isFullScreen = frameContext.frameRect().contains(IntRect(IntPoint(), size())); |
| + |
| + // If fullscreen and opaque, the frame is independent of previous frame. |
|
scroggo_chromium
2016/04/29 19:48:15
This comment seems a little out of place to me. Ho
|
| + if (!isFullScreen) |
| + return; |
| + |
| + // If transparent pixel is outside color table, there is no transparent |
| + // pixel, e.g. color table in some of the GIFs has 40 elements and |
| + // transparentPixel value is 0xFF. |
| + if (frameContext.transparentPixel() >= getColorMap(frameContext, *m_reader).getTableSize()) |
| + buffer->setRequiredPreviousFrameIndex(kNotFound); |
| +} |
| + |
| +// Returns transparent index or background index. |
| +unsigned GIFImageDecoder::getBackgroundIndex(const GIFFrameContext& frameContext) const |
| +{ |
| + const size_t background = frameContext.transparentPixel(); |
| + const GIFColorMap& colorMap = getColorMap(frameContext, *m_reader); |
| + if (background < colorMap.getTableSize()) |
| + return background; |
| + |
| + if (!frameContext.localColorMap().isDefined() && m_reader->backgroundIndex() < colorMap.getTableSize()) |
| + return m_reader->backgroundIndex(); |
| + return -1; |
|
scroggo_chromium
2016/04/29 19:48:15
Again, I think it's weird to return -1 as an unsig
|
| +} |
| + |
| +SkColor GIFImageDecoder::getBackgroundColor(size_t frameIndex) const |
| +{ |
| + const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
| + unsigned backgroundIndex = getBackgroundIndex(*frameContext); |
| + if (frameContext->transparentPixel() == backgroundIndex) |
| + return SK_ColorTRANSPARENT; |
| + const GIFColorTable& table = getColorMap(*frameContext, *m_reader).table(); |
| + return (backgroundIndex < table.size() ? table[backgroundIndex]: SK_ColorTRANSPARENT); |
|
scroggo_chromium
2016/04/29 19:48:15
How does this fall back to transparent? It seems l
|
| +} |
| + |
| +bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
| +{ |
| + ImageFrame* buffer = &m_frameBufferCache[frameIndex]; |
| + if (buffer->status() != ImageFrame::FrameEmpty) { |
| + // If it is partial, decode to the same bitmap. |
| + setupHaveDecodedRowCallbacks(buffer->getSkBitmap().colorType() == kIndex_8_SkColorType); |
| + return true; |
| + } |
| + |
| + if (m_requestedColorMode == ImageFrame::N32) { |
| + return initFrameBufferN32(frameIndex); |
| + } |
| + const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
| + updateRequiredPreviousFrame(buffer, *frameContext); |
| + const size_t requiredPreviousFrameIndex = buffer->requiredPreviousFrameIndex(); |
| + |
| + bool useIndex8 = true; |
| + if (requiredPreviousFrameIndex == kNotFound) { |
| + if (isIndex8Applicable(*frameContext, requiredPreviousFrameIndex)) { |
| + const unsigned backgroundIndex = getBackgroundIndex(*frameContext); |
| + if (!buffer->setSizeIndex8( |
| + size().width(), size().height(), |
| + getColorMap(*frameContext, *m_reader).table(), backgroundIndex, |
| + backgroundIndex == frameContext->transparentPixel())) |
| + return setFailed(); |
| + } else { |
| + return initFrameBufferN32(frameIndex); |
| } |
| + } else { |
| + const ImageFrame* prevBuffer = &m_frameBufferCache[requiredPreviousFrameIndex]; |
| + ASSERT(prevBuffer->status() == ImageFrame::FrameComplete); |
| + |
| + // Copy the required completed frame as the starting state for this frame. |
| + useIndex8 = (prevBuffer->getSkBitmap().colorType() == kIndex_8_SkColorType) && isIndex8Applicable(*frameContext, requiredPreviousFrameIndex); |
| + if (!initFrameBufferFromPrevious(buffer, *prevBuffer, useIndex8 ? ImageFrame::Index8 : ImageFrame::N32, frameContext->transparentPixel())) |
| + return setFailed(); |
| + ASSERT(useIndex8 == (buffer->getSkBitmap().colorType() == kIndex_8_SkColorType)); |
| } |
| + setupHaveDecodedRowCallbacks(useIndex8); |
| // Update our status to be partially complete. |
| buffer->setStatus(ImageFrame::FramePartial); |
| @@ -341,4 +563,30 @@ bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
| return true; |
| } |
| +bool GIFImageDecoder::canDecodeTo(size_t index, ImageFrame::ColorType outputType) |
| +{ |
| + if ((index >= frameCount()) || (m_requestedColorMode == ImageFrame::N32) || failed()) |
|
scroggo_chromium
2016/04/29 19:48:15
if (failed()), should this just return false? Othe
|
| + return (outputType == m_requestedColorMode); |
| + |
| + // Go from one to previous to calculate 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 (!isIndex8Applicable(*frameContext, buffer->requiredPreviousFrameIndex())) { |
| + calculatedOutput = ImageFrame::N32; |
| + break; |
| + } |
| + frameToDecode = buffer->requiredPreviousFrameIndex(); |
| + } |
| + return (calculatedOutput == outputType); |
| +} |
| + |
| } // namespace blink |