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

Unified Diff: third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp

Issue 1460523002: GIF decoding to Index8, unit tests and misusing unit test as benchmark (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cleanup. tableChanged was wrong - do proper check. Created 5 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/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

Powered by Google App Engine
This is Rietveld 408576698