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 07dd9de56970d610b5895b1f6ebbb0dc0d9b8ca7..1ac9920afb9d5c0bcd4003f60ccff0fef6b7268a 100644 |
| --- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
| +++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp |
| @@ -35,7 +35,10 @@ namespace blink { |
| GIFImageDecoder::GIFImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption colorOptions, size_t maxDecodedBytes) |
| : ImageDecoder(alphaOption, colorOptions, maxDecodedBytes) |
| + , phaveDecodedRow(&GIFImageDecoder::haveDecodedRowIndex8) |
| , m_repetitionCount(cAnimationLoopOnce) |
| + , m_colorMode(ImageFrame::Index8) |
| + , 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); |
|
scroggo_chromium
2015/12/03 21:47:21
It's weird to me that m_colorMode might *not* be N
aleksandar.stojiljkovic
2015/12/04 00:07:35
Yes. m_colorMode holds information about what was
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| 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. |
| @@ -127,8 +131,17 @@ bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator r |
| // Initialize the frame if necessary. |
| ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
| - if ((buffer.status() == ImageFrame::FrameEmpty) && !initFrameBuffer(frameIndex)) |
| - return false; |
| + if (buffer.status() == ImageFrame::FrameEmpty) { |
| + if (!initFrameBuffer(frameIndex)) |
| + return false; |
| + // Tricky part here - decoding first row inits frame, and recognized that the frame doesnt require |
|
scroggo_chromium
2015/12/03 21:47:22
This is tricky. Maybe it would be better for initF
aleksandar.stojiljkovic
2015/12/04 01:27:27
I actually plan to move init out of looping throug
aleksandar.stojiljkovic
2015/12/07 19:24:22
done (removed), init happens before outputting row
scroggo_chromium
2015/12/08 22:24:51
Yeah, I think that has to be a judgment call. Chan
|
| + // forced N32 decoding (. initFrameBuffer sets proper haveDecodedRow callback, but for this |
| + // first row, we need manually to redirect. |
| + // It would be much cleaner to move initFrameBuffer before calling haveDecodedRow, but the |
| + // intention here is not to change original code path. |
| + if (m_colorMode == ImageFrame::Index8 && !m_forceN32Decoding) |
|
scroggo_chromium
2015/12/03 21:47:22
IIUC, if this is true, initFrameBuffer called init
aleksandar.stojiljkovic
2015/12/04 01:27:27
yes, for every row check if initialized then initi
aleksandar.stojiljkovic
2015/12/07 19:24:21
Done.
|
| + return haveDecodedRowIndex8(frameIndex, rowBegin, width, rowNumber, repeatCount, writeTransparentPixels); |
| + } |
| const size_t transparentPixel = frameContext->transparentPixel(); |
| GIFRow::const_iterator rowEnd = rowBegin + (xEnd - xBegin); |
| @@ -149,6 +162,11 @@ bool GIFImageDecoder::haveDecodedRow(size_t frameIndex, GIFRow::const_iterator r |
| if (writeTransparentPixels) { |
| for (; rowBegin != rowEnd; ++rowBegin, ++currentAddress) { |
| const size_t sourceValue = *rowBegin; |
| + // FIXME - potential optimization: extend colorTable to 256 elems, write 0 |
|
scroggo_chromium
2015/12/03 21:47:21
So this optimization is when the colortable is les
aleksandar.stojiljkovic
2015/12/04 01:27:27
no; i meant that for colortables of e.g. 32 elemen
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| + // in colortable on index transparentPixel so there would be no need to to this |
| + // branching bellow. See index8 implementation. |
| + // FIXME optimization: do separate branching for when there is no transparentPixel |
| + // (value outside colorTable.size()) |
| if ((sourceValue != transparentPixel) && (sourceValue < colorTable.size())) { |
| *currentAddress = colorTableIter[sourceValue]; |
| } else { |
| @@ -174,6 +192,86 @@ 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) |
|
scroggo_chromium
2015/12/03 21:47:21
It looks like this is largely the same as haveDeco
aleksandar.stojiljkovic
2015/12/04 01:27:27
Agree, duplication for purpose that reviewer easie
|
| +{ |
| + 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; |
| + |
| + |
| + // Initialize the frame if necessary. |
| + ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
| + if (buffer.status() == ImageFrame::FrameEmpty) { |
| + if (!initFrameBufferIndex8(frameIndex)) |
| + return false; |
| + // Tricky part here - decoding first row inits frame, and recognised that the frame requires |
| + // N32 decoding. initFrameBuffer sets proper haveDecodedRow callback, but for this |
| + // first row, we need manually to redirect. |
| + // It would be much cleaner to move initFrameBuffer before calling haveDecodedRow, but the |
| + // intention here is not to change original code path. |
| + if (m_forceN32Decoding) |
| + return haveDecodedRowN32(frameIndex, rowBegin, width, rowNumber, repeatCount, writeTransparentPixels); |
| + } |
| + |
| + 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, suitable for parallel uint32_t write later. |
| + 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; |
|
scroggo_chromium
2015/12/03 21:47:22
Why do we not want to put index into currentAddres
aleksandar.stojiljkovic
2015/12/04 01:27:26
It is faster this way (at least for examples I use
|
| + } 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(); |
| @@ -183,6 +281,7 @@ bool GIFImageDecoder::frameComplete(size_t frameIndex) |
| { |
| // Initialize the frame if necessary. Some GIFs insert do-nothing frames, |
| // in which case we never reach haveDecodedRow() before getting here. |
| + // FIXME do nothing frames for Index8 are probably copy of previous frame. |
|
scroggo_chromium
2015/12/03 21:47:21
What does this mean, and what is the FIXME? (i.e.
aleksandar.stojiljkovic
2015/12/04 01:27:27
All fine now, to be removed.
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| ImageFrame& buffer = m_frameBufferCache[frameIndex]; |
| if ((buffer.status() == ImageFrame::FrameEmpty) && !initFrameBuffer(frameIndex)) |
| return false; // initFrameBuffer() has already called setFailed(). |
| @@ -264,6 +363,9 @@ void GIFImageDecoder::initializeNewFrame(size_t index) |
| void GIFImageDecoder::decode(size_t index) |
|
scroggo_chromium
2015/12/03 21:47:21
So IIUC, decodeTo (which you added) lets the calle
aleksandar.stojiljkovic
2015/12/04 01:27:27
decodeto wraps decode - decode() is not called dir
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done - removed decodeTo method. Decoding output se
|
| { |
| + phaveDecodedRow = (m_colorMode == ImageFrame::Index8 && !m_forceN32Decoding) |
| + ? (&GIFImageDecoder::haveDecodedRowIndex8) |
| + : (&GIFImageDecoder::haveDecodedRowN32); |
| parse(GIFFrameCountQuery); |
| if (failed()) |
| @@ -277,9 +379,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)) { |
|
scroggo_chromium
2015/12/03 21:47:21
So decode() may now fail for another reason (wrong
aleksandar.stojiljkovic
2015/12/04 01:27:27
it could fail for previous reasons (setFailed() ca
aleksandar.stojiljkovic
2015/12/22 15:19:55
Done. This code removed.
|
| - 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) { |
|
scroggo_chromium
2015/12/03 21:47:21
So GIFImageReader::decode modified m_forceN32Decod
aleksandar.stojiljkovic
2015/12/04 01:27:27
Acknowledged.
aleksandar.stojiljkovic
2015/12/04 01:27:27
This relates to that init inside haveDecodedRow an
aleksandar.stojiljkovic
2015/12/22 15:19:55
Done. Simplified and removed 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 +410,30 @@ void GIFImageDecoder::decode(size_t index) |
| setFailed(); |
| } |
| +void GIFImageDecoder::decodeTo(size_t index, ImageFrame::ColorType outputColor) |
| +{ |
| + ASSERT(outputColor == ImageFrame::N32 || outputColor == ImageFrame::Index8); |
| + ImageFrame::ColorType c = m_colorMode; |
| + m_colorMode = outputColor; |
| + decode(index); |
| + m_colorMode = c; |
| +} |
| + |
| +bool GIFImageDecoder::canDecodeTo(size_t index, ImageFrame::ColorType outputType) |
| +{ |
| + return ((outputType == ImageFrame::N32) |
| + || (outputType == ImageFrame::Index8 && !m_forceN32Decoding)); |
| +} |
| + |
| +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()) |
| @@ -309,6 +450,11 @@ void GIFImageDecoder::parse(GIFParseQuery query) |
| bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
| { |
| + if (m_colorMode == ImageFrame::Index8) { |
| + // Ended up here, since previous frame didn't support Index8 decoding. |
| + // Try to switch back to Index( decoding. |
| + return initFrameBufferIndex8(frameIndex); |
| + } |
| // Initialize the frame rect in our buffer. |
| ImageFrame* const buffer = &m_frameBufferCache[frameIndex]; |
| @@ -342,4 +488,97 @@ bool GIFImageDecoder::initFrameBuffer(size_t frameIndex) |
| return true; |
| } |
| +static bool framesUseSameColorTable(const GIFFrameContext *frame1, const GIFFrameContext *frame2) |
|
scroggo_chromium
2015/12/03 21:47:22
I think these should be const references?
aleksandar.stojiljkovic
2015/12/04 01:27:27
Acknowledged.
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| +{ |
| + if (frame1->localColorMap().isDefined() ^ frame2->localColorMap().isDefined()) |
| + return false; |
| + if (!frame1->localColorMap().isDefined()) { |
| + // They both use global ColorMap. |
|
scroggo_chromium
2015/12/03 21:47:22
ASSERT(!frame2->localColorMap().isDefined())?
aleksandar.stojiljkovic
2015/12/04 01:27:27
previous ^ should be resolving it, but it is compl
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| + return (frame1->transparentPixel() == frame1->transparentPixel()); |
| + } |
| + if (frame1->localColorMap().getPosition() == frame2->localColorMap().getPosition()) { |
|
scroggo_chromium
2015/12/03 21:47:21
This block of code is tough to follow. I think som
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| + if (frame1->transparentPixel() == frame2->transparentPixel() |
| + || (frame1->transparentPixel() >= frame1->localColorMap().table().size() |
| + && frame2->transparentPixel() >= frame2->localColorMap().table().size())) |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +bool GIFImageDecoder::initFrameBufferIndex8(size_t frameIndex) |
| +{ |
| + ASSERT(m_colorMode == ImageFrame::Index8); |
| + // Initialize the frame rect in our buffer. |
| + ImageFrame* buffer = &m_frameBufferCache[frameIndex]; |
| + |
| + const GIFFrameContext* frameContext = m_reader->frameContext(frameIndex); |
| + bool isFullScreen = frameContext->frameRect().contains(IntRect(IntPoint(), size())); |
| + const GIFColorMap::Table& colorTable = frameContext->localColorMap().isDefined() |
| + ? frameContext->localColorMap().table() : m_reader->globalColorMap().table(); |
| + |
| + // Checking frameContext->transparentPixel() != kNotFound is not precise, since |
| + // it also needs to include colortable information - if transparent pixels is outside colortable |
| + // there is no transparent pixel. |
| + // e.g. Colortable in some of the GIFs is having 40 elements andtransparentPixel set to 0xFF. |
| + // This is changed here only to affect Index8 and the intention is to keep the N32 code unchanged. |
|
scroggo_chromium
2015/12/03 21:47:21
Are you saying that updating required previous fra
aleksandar.stojiljkovic
2015/12/04 01:27:27
Originally didn't want to change it, tried to deli
|
| + bool opaque = frameContext->transparentPixel() >= colorTable.size(); |
| + |
| + // The same check - if fullscreen and opaque, then previus is not required to copy - happens in |
| + // frameComplete -move it earlier so it affects also current run (not only starting from next loop). |
| + if (isFullScreen && opaque) |
| + buffer->setRequiredPreviousFrameIndex(kNotFound); |
| + |
| + size_t requiredPreviousFrameIndex = buffer->requiredPreviousFrameIndex(); |
| + |
| + if (requiredPreviousFrameIndex == kNotFound) { |
| + // This frame doesn't rely on any previous data. |
| + // Reset not suitable and table changed information as this is full new frame. |
|
scroggo_chromium
2015/12/03 21:47:21
What does "Reset not suitable" mean?
aleksandar.stojiljkovic
2015/12/04 01:27:27
m_forceN32Decoding can be reset to false, and resu
aleksandar.stojiljkovic
2015/12/07 19:24:22
Done.
|
| + // It is OK to switch back from N32 to Index8 if it was originally requested. |
| + setForceN32Decoding(false); |
| + phaveDecodedRow = &GIFImageDecoder::haveDecodedRowIndex8; |
| + if (!buffer->setSizeIndex8(size().width(), size().height(), colorTable, frameContext->transparentPixel())) |
|
aleksandar.stojiljkovic
2015/12/04 01:27:27
This call is prefilling it so that all pixels have
|
| + return setFailed(); |
| + } else { |
| + const ImageFrame* prevBuffer = &m_frameBufferCache[requiredPreviousFrameIndex]; |
| + ASSERT(prevBuffer->status() == ImageFrame::FrameComplete); |
| + ASSERT(!isFullScreen || !opaque); |
| + |
| + // Preserve the last frame as the starting state for this frame. |
| + // If in Index8 mode, and colorTable is not changed nor setForceN32Decoding set, |
| + // preserve frame same way as for N32. |
| + bool useN32 = true; |
| + if (!m_forceN32Decoding) { |
|
scroggo_chromium
2015/12/03 21:47:21
Wait, should we reach this method (or get this far
aleksandar.stojiljkovic
2015/12/07 19:24:22
I think it looks more logical now - initFrameBuffe
aleksandar.stojiljkovic
2015/12/22 15:19:55
Done. Simplified and removed m_forceN32Decoding.
|
| + // If table is changed and in the same time, there is transparency or following frame |
| + // is subrect, cannot do Index8. Otherwise, with the same table, no problem to continue |
| + // with Index8 copy of dependent frame as starting point for this 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; |
| +} |
| } // namespace blink |