Populate new repository with content from skia/third_party/gif

This is the third_party/gif/ directory from the Skia repository
as of https://review.skia.org/254436 .

Remove references to `third_party/gif/` from `#include` statements.

Add SkGifCodec.h header exposing two functions.

Rename SkGifCodec class SkLibGifCodec.

Add README.md

Make LICENSE file as clear as possible.

Add libgifcodec.gni

Change-Id: I13f2a4390a8e20e27ef7f980e0cc04365d89c23c
Reviewed-on: https://skia-review.googlesource.com/c/libgifcodec/+/254420
Reviewed-by: Hal Canary <halcanary@google.com>
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..139810c
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,109 @@
+MPL-1.1 / GPL-2.0 / LGPL-2.1
+============================
+
+SkGifImageReader.cpp and SkGifImageReader.h:
+
+    ***** BEGIN LICENSE BLOCK *****
+    Version: MPL 1.1/GPL 2.0/LGPL 2.1
+
+    The contents of this file are subject to the Mozilla Public License Version
+    1.1 (the "License"); you may not use this file except in compliance with
+    the License. You may obtain a copy of the License at
+    http://www.mozilla.org/MPL/
+
+    Software distributed under the License is distributed on an "AS IS" basis,
+    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+    for the specific language governing rights and limitations under the
+    License.
+
+    The Original Code is mozilla.org code.
+
+    The Initial Developer of the Original Code is
+    Netscape Communications Corporation.
+    Portions created by the Initial Developer are Copyright (C) 1998
+    the Initial Developer. All Rights Reserved.
+
+    Contributor(s):
+      Chris Saari <saari@netscape.com>
+      Apple Computer
+
+    Alternatively, the contents of this file may be used under the terms of
+    either the GNU General Public License Version 2 or later (the "GPL"), or
+    the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+    in which case the provisions of the GPL or the LGPL are applicable instead
+    of those above. If you wish to allow use of your version of this file only
+    under the terms of either the GPL or the LGPL, and not to allow others to
+    use your version of this file under the terms of the MPL, indicate your
+    decision by deleting the provisions above and replace them with the notice
+    and other provisions required by the GPL or the LGPL. If you do not delete
+    the provisions above, a recipient may use your version of this file under
+    the terms of any one of the MPL, the GPL or the LGPL.
+
+    ***** END LICENSE BLOCK ***** */
+
+
+BSD-3-Clause
+============
+
+libgifcodec.gni, SkGifCodec.h, SkLibGifCodec.cpp, SkLibGifCodec.h:
+
+    Copyright (c) 2011 Google Inc. All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+      * Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+      * Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials provided
+        with the distribution.
+
+      * Neither the name of Google Inc. nor the names of its
+        contributors may be used to endorse or promote products derived
+        from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+BSD-2-Clause
+============
+
+SkLibGifCodec.cpp:
+
+    Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+    PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+    OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0acefc6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+LIBGIF CODEC FOR SKIA
+=====================
+
+libgifcodec is based on a fork of libgif made by Chromium.  It was copied into
+Skia with <https://codereview.chromium.org/2045293002>, as
+<https://skia.googlesource.com/skia/+/19b91531e912283d237435d94516575b28713cba>.
+
+The header file `SkGifCodec.h` exposes two functions:
+
+  * `bool SkGifCodec::IsGif(const void*, size_t);`
+
+  * `std::unique_ptr<SkCodec> SkGifCodec::MakeFromStream(std::unique_ptr<SkStream>, SkCodec::Result*);`
+
+Which can be used by Skia's `SkCodec::MakeFromStream` to implement GIF Decoding.
+
+See [`LICENSE.md`](LICENSE.md) for the license information.
diff --git a/SkGifCodec.h b/SkGifCodec.h
new file mode 100644
index 0000000..532e7b1
--- /dev/null
+++ b/SkGifCodec.h
@@ -0,0 +1,20 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by the BSD-3-Clause license that can be
+// found in the LICENSE.md file.
+
+#ifndef SkGifCodec_DEFINED
+#define SkGifCodec_DEFINED
+
+#include "include/codec/SkCodec.h"
+
+namespace SkGifCodec {
+
+// Returns true if the span of bytes appears to be GIF encoded data.
+bool IsGif(const void*, size_t);
+
+// Assumes IsGif was called and returned true.
+// Reads enough of the stream to determine the image format.
+std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, SkCodec::Result*);
+
+}  // namespace SkGifCodec
+#endif  // SkGifCodec_DEFINED
diff --git a/SkGifImageReader.cpp b/SkGifImageReader.cpp
new file mode 100644
index 0000000..da9119a
--- /dev/null
+++ b/SkGifImageReader.cpp
@@ -0,0 +1,958 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Chris Saari <saari@netscape.com>
+ *   Apple Computer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+The Graphics Interchange Format(c) is the copyright property of CompuServe
+Incorporated. Only CompuServe Incorporated is authorized to define, redefine,
+enhance, alter, modify or change in any way the definition of the format.
+
+CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free
+license for the use of the Graphics Interchange Format(sm) in computer
+software; computer software utilizing GIF(sm) must acknowledge ownership of the
+Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in
+User and Technical Documentation. Computer software utilizing GIF, which is
+distributed or may be distributed without User or Technical Documentation must
+display to the screen or printer a message acknowledging ownership of the
+Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in
+this case, the acknowledgement may be displayed in an opening screen or leading
+banner, or a closing screen or trailing banner. A message such as the following
+may be used:
+
+    "The Graphics Interchange Format(c) is the Copyright property of
+    CompuServe Incorporated. GIF(sm) is a Service Mark property of
+    CompuServe Incorporated."
+
+For further information, please contact :
+
+    CompuServe Incorporated
+    Graphics Technology Department
+    5000 Arlington Center Boulevard
+    Columbus, Ohio  43220
+    U. S. A.
+
+CompuServe Incorporated maintains a mailing list with all those individuals and
+organizations who wish to receive copies of this document when it is corrected
+or revised. This service is offered free of charge; please provide us with your
+mailing address.
+*/
+
+#include "SkGifImageReader.h"
+#include "SkLibGifCodec.h"
+
+#include "include/core/SkColorPriv.h"
+
+#include <algorithm>
+#include <string.h>
+
+
+// GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's'.
+//
+// Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
+// as each GIF block (except colormaps) can never be bigger than 256 bytes.
+// Colormaps are directly copied in the resp. global_colormap or dynamically allocated local_colormap.
+// So a fixed buffer in SkGifImageReader is good enough.
+// This buffer is only needed to copy left-over data from one GifWrite call to the next
+#define GETN(n, s) \
+    do { \
+        m_bytesToConsume = (n); \
+        m_state = (s); \
+    } while (0)
+
+// Get a 16-bit value stored in little-endian format.
+#define GETINT16(p)   ((p)[1]<<8|(p)[0])
+
+namespace {
+    bool is_palette_index_valid(int transparentIndex) {
+        // -1 is a signal that there is no transparent index.
+        // Otherwise, it is encoded in 8 bits, and all 256 values are considered
+        // valid since a GIF may use an index outside of the palette to be
+        // transparent.
+        return transparentIndex >= 0;
+    }
+} // anonymous namespace
+
+// Send the data to the display front-end.
+void SkGIFLZWContext::outputRow(const unsigned char* rowBegin)
+{
+    int drowStart = irow;
+    int drowEnd = irow;
+
+    // Haeberli-inspired hack for interlaced GIFs: Replicate lines while
+    // displaying to diminish the "venetian-blind" effect as the image is
+    // loaded. Adjust pixel vertical positions to avoid the appearance of the
+    // image crawling up the screen as successive passes are drawn.
+    if (m_frameContext->progressiveDisplay() && m_frameContext->interlaced() && ipass < 4) {
+        unsigned rowDup = 0;
+        unsigned rowShift = 0;
+
+        switch (ipass) {
+        case 1:
+            rowDup = 7;
+            rowShift = 3;
+            break;
+        case 2:
+            rowDup = 3;
+            rowShift = 1;
+            break;
+        case 3:
+            rowDup = 1;
+            rowShift = 0;
+            break;
+        default:
+            break;
+        }
+
+        drowStart -= rowShift;
+        drowEnd = drowStart + rowDup;
+
+        // Extend if bottom edge isn't covered because of the shift upward.
+        if ((unsigned)((m_frameContext->height() - 1) - drowEnd) <= rowShift)
+            drowEnd = m_frameContext->height() - 1;
+
+        // Clamp first and last rows to upper and lower edge of image.
+        if (drowStart < 0)
+            drowStart = 0;
+
+        if (drowEnd >= m_frameContext->height())
+            drowEnd = m_frameContext->height() - 1;
+    }
+
+    // Protect against too much image data.
+    if (drowStart >= m_frameContext->height())
+        return;
+
+    // CALLBACK: Let the client know we have decoded a row.
+    const bool writeTransparentPixels =
+            SkCodec::kNoFrame == m_frameContext->getRequiredFrame();
+    m_client->haveDecodedRow(m_frameContext->frameId(), rowBegin,
+            drowStart, drowEnd - drowStart + 1, writeTransparentPixels);
+
+    if (!m_frameContext->interlaced())
+        irow++;
+    else {
+        do {
+            switch (ipass) {
+            case 1:
+                irow += 8;
+                if (irow >= (unsigned) m_frameContext->height()) {
+                    ipass++;
+                    irow = 4;
+                }
+                break;
+
+            case 2:
+                irow += 8;
+                if (irow >= (unsigned) m_frameContext->height()) {
+                    ipass++;
+                    irow = 2;
+                }
+                break;
+
+            case 3:
+                irow += 4;
+                if (irow >= (unsigned) m_frameContext->height()) {
+                    ipass++;
+                    irow = 1;
+                }
+                break;
+
+            case 4:
+                irow += 2;
+                if (irow >= (unsigned) m_frameContext->height()) {
+                    ipass++;
+                    irow = 0;
+                }
+                break;
+
+            default:
+                break;
+            }
+        } while (irow > (unsigned) (m_frameContext->height() - 1));
+    }
+}
+
+// Perform Lempel-Ziv-Welch decoding.
+// Returns true if decoding was successful. In this case the block will have been completely consumed and/or rowsRemaining will be 0.
+// Otherwise, decoding failed; returns false in this case, which will always cause the SkGifImageReader to set the "decode failed" flag.
+bool SkGIFLZWContext::doLZW(const unsigned char* block, size_t bytesInBlock)
+{
+    if (rowIter == rowBuffer.end())
+        return true;
+    const int width = m_frameContext->width();
+
+    for (const unsigned char* ch = block; bytesInBlock-- > 0; ch++) {
+        // Feed the next byte into the decoder's 32-bit input buffer.
+        datum += ((int) *ch) << bits;
+        bits += 8;
+
+        // Check for underflow of decoder's 32-bit input buffer.
+        while (bits >= codesize) {
+            // Get the leading variable-length symbol from the data stream.
+            int code = datum & codemask;
+            datum >>= codesize;
+            bits -= codesize;
+
+            // Reset the dictionary to its original state, if requested.
+            if (code == clearCode) {
+                codesize = m_frameContext->dataSize() + 1;
+                codemask = (1 << codesize) - 1;
+                avail = clearCode + 2;
+                oldcode = -1;
+                continue;
+            }
+
+            // Check for explicit end-of-stream code.
+            if (code == (clearCode + 1)) {
+                // end-of-stream should only appear after all image data.
+                if (!rowsRemaining)
+                    return true;
+                return false;
+            }
+
+            const int tempCode = code;
+            if (code > avail) {
+                // This is an invalid code. The dictionary is just initialized
+                // and the code is incomplete. We don't know how to handle
+                // this case.
+                return false;
+            }
+
+            if (code == avail) {
+                if (oldcode != -1) {
+                    // This is a new code just being added to the dictionary.
+                    // It must encode the contents of the previous code, plus
+                    // the first character of the previous code again.
+                    // Now we know avail is the new code we can use oldcode
+                    // value to get the code related to that.
+                    code = oldcode;
+                } else {
+                    // This is an invalid code. The dictionary is just initialized
+                    // and the code is incomplete. We don't know how to handle
+                    // this case.
+                    return false;
+                }
+            }
+
+            // code length of the oldcode for new code which is
+            // avail = oldcode + firstchar of the oldcode
+            int remaining = suffixLength[code];
+
+            // Round remaining up to multiple of SK_DICTIONARY_WORD_SIZE, because that's
+            // the granularity of the chunks we copy.  The last chunk may contain
+            // some garbage but it'll be overwritten by the next code or left unused.
+            // The working buffer is padded to account for this.
+            remaining += -remaining & (SK_DICTIONARY_WORD_SIZE - 1) ;
+            unsigned char* p = rowIter + remaining;
+
+            // Place rowIter so that after writing pixels rowIter can be set to firstchar, thereby
+            // completing the code.
+            rowIter += suffixLength[code];
+
+            while (remaining > 0) {
+                p -= SK_DICTIONARY_WORD_SIZE;
+                std::copy_n(suffix[code].begin(), SK_DICTIONARY_WORD_SIZE, p);
+                code = prefix[code];
+                remaining -= SK_DICTIONARY_WORD_SIZE;
+            }
+            const int firstchar = static_cast<unsigned char>(code);  // (strictly `suffix[code][0]`)
+
+            // This completes the new code avail and writing the corresponding
+            // pixels on target.
+            if (tempCode == avail) {
+                *rowIter++ = firstchar;
+            }
+
+            // Define a new codeword in the dictionary as long as we've read
+            // more than one value from the stream.
+            if (avail < SK_MAX_DICTIONARY_ENTRIES && oldcode != -1) {
+                // now add avail to the dictionary for future reference
+                unsigned short codeLength = suffixLength[oldcode] + 1;
+                int l = (codeLength - 1) & (SK_DICTIONARY_WORD_SIZE - 1);
+                // If the suffix buffer is full (l == 0) then oldcode becomes the new
+                // prefix, otherwise copy and extend oldcode's buffer and use the same
+                // prefix as oldcode used.
+                prefix[avail] = (l == 0) ? oldcode : prefix[oldcode];
+                suffix[avail] = suffix[oldcode];
+                suffix[avail][l] = firstchar;
+                suffixLength[avail] = codeLength;
+                ++avail;
+
+                // If we've used up all the codewords of a given length
+                // increase the length of codewords by one bit, but don't
+                // exceed the specified maximum codeword size.
+                if (!(avail & codemask) && avail < SK_MAX_DICTIONARY_ENTRIES) {
+                    ++codesize;
+                    codemask += avail;
+                }
+            }
+            oldcode = tempCode;
+
+            // Output as many rows as possible.
+            unsigned char* rowBegin = rowBuffer.begin();
+            for (; rowBegin + width <= rowIter; rowBegin += width) {
+                outputRow(rowBegin);
+                rowsRemaining--;
+                if (!rowsRemaining)
+                    return true;
+            }
+
+            if (rowBegin != rowBuffer.begin()) {
+                // Move the remaining bytes to the beginning of the buffer.
+                const size_t bytesToCopy = rowIter - rowBegin;
+                memcpy(&rowBuffer.front(), rowBegin, bytesToCopy);
+                rowIter = rowBuffer.begin() + bytesToCopy;
+            }
+        }
+    }
+    return true;
+}
+
+sk_sp<SkColorTable> SkGIFColorMap::buildTable(SkStreamBuffer* streamBuffer, SkColorType colorType,
+                                              int transparentPixel) const
+{
+    if (!m_isDefined)
+        return nullptr;
+
+    const PackColorProc proc = choose_pack_color_proc(false, colorType);
+    if (m_table && proc == m_packColorProc && m_transPixel == transparentPixel) {
+        SkASSERT(transparentPixel == kNotFound || transparentPixel > m_table->count()
+                || m_table->operator[](transparentPixel) == SK_ColorTRANSPARENT);
+        // This SkColorTable has already been built with the same transparent color and
+        // packing proc. Reuse it.
+        return m_table;
+    }
+    m_packColorProc = proc;
+    m_transPixel = transparentPixel;
+
+    const size_t bytes = m_colors * SK_BYTES_PER_COLORMAP_ENTRY;
+    sk_sp<SkData> rawData(streamBuffer->getDataAtPosition(m_position, bytes));
+    if (!rawData) {
+        return nullptr;
+    }
+
+    SkASSERT(m_colors <= SK_MAX_COLORS);
+    const uint8_t* srcColormap = rawData->bytes();
+    SkPMColor colorStorage[SK_MAX_COLORS];
+    for (int i = 0; i < m_colors; i++) {
+        if (i == transparentPixel) {
+            colorStorage[i] = SK_ColorTRANSPARENT;
+        } else {
+            colorStorage[i] = proc(255, srcColormap[0], srcColormap[1], srcColormap[2]);
+        }
+        srcColormap += SK_BYTES_PER_COLORMAP_ENTRY;
+    }
+    for (int i = m_colors; i < SK_MAX_COLORS; i++) {
+        colorStorage[i] = SK_ColorTRANSPARENT;
+    }
+    m_table = sk_sp<SkColorTable>(new SkColorTable(colorStorage, SK_MAX_COLORS));
+    return m_table;
+}
+
+sk_sp<SkColorTable> SkGifImageReader::getColorTable(SkColorType colorType, int index) {
+    if (index < 0 || index >= m_frames.count()) {
+        return nullptr;
+    }
+
+    const SkGIFFrameContext* frameContext = m_frames[index].get();
+    const SkGIFColorMap& localColorMap = frameContext->localColorMap();
+    const int transPix = frameContext->transparentPixel();
+    if (localColorMap.isDefined()) {
+        return localColorMap.buildTable(&m_streamBuffer, colorType, transPix);
+    }
+    if (m_globalColorMap.isDefined()) {
+        return m_globalColorMap.buildTable(&m_streamBuffer, colorType, transPix);
+    }
+    return nullptr;
+}
+
+// Perform decoding for this frame. frameComplete will be true if the entire frame is decoded.
+// Returns false if a decoding error occurred. This is a fatal error and causes the SkGifImageReader to set the "decode failed" flag.
+// Otherwise, either not enough data is available to decode further than before, or the new data has been decoded successfully; returns true in this case.
+bool SkGIFFrameContext::decode(SkStreamBuffer* streamBuffer, SkLibGifCodec* client,
+                               bool* frameComplete)
+{
+    *frameComplete = false;
+    if (!m_lzwContext) {
+        // Wait for more data to properly initialize SkGIFLZWContext.
+        if (!isDataSizeDefined() || !isHeaderDefined())
+            return true;
+
+        m_lzwContext.reset(new SkGIFLZWContext(client, this));
+        if (!m_lzwContext->prepareToDecode()) {
+            m_lzwContext.reset();
+            return false;
+        }
+
+        m_currentLzwBlock = 0;
+    }
+
+    // Some bad GIFs have extra blocks beyond the last row, which we don't want to decode.
+    while (m_currentLzwBlock < m_lzwBlocks.count() && m_lzwContext->hasRemainingRows()) {
+        const auto& block = m_lzwBlocks[m_currentLzwBlock];
+        const size_t len = block.blockSize;
+
+        sk_sp<SkData> data(streamBuffer->getDataAtPosition(block.blockPosition, len));
+        if (!data) {
+            return false;
+        }
+        if (!m_lzwContext->doLZW(reinterpret_cast<const unsigned char*>(data->data()), len)) {
+            return false;
+        }
+        ++m_currentLzwBlock;
+    }
+
+    // If this frame is data complete then the previous loop must have completely decoded all LZW blocks.
+    // There will be no more decoding for this frame so it's time to cleanup.
+    if (isComplete()) {
+        *frameComplete = true;
+        m_lzwContext.reset();
+    }
+    return true;
+}
+
+// Decode a frame.
+// This method uses SkGIFFrameContext:decode() to decode the frame; decoding error is reported to client as a critical failure.
+// Return true if decoding has progressed. Return false if an error has occurred.
+bool SkGifImageReader::decode(int frameIndex, bool* frameComplete)
+{
+    SkGIFFrameContext* currentFrame = m_frames[frameIndex].get();
+
+    return currentFrame->decode(&m_streamBuffer, m_client, frameComplete);
+}
+
+// Parse incoming GIF data stream into internal data structures.
+SkCodec::Result SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
+{
+    if (m_parseCompleted) {
+        return SkCodec::kSuccess;
+    }
+
+    if (SkGIFLoopCountQuery == query && m_loopCount != cLoopCountNotSeen) {
+        // Loop count has already been parsed.
+        return SkCodec::kSuccess;
+    }
+
+    // SkGIFSizeQuery and SkGIFFrameCountQuery are negative, so this is only meaningful when >= 0.
+    const int lastFrameToParse = (int) query;
+    if (lastFrameToParse >= 0 && m_frames.count() > lastFrameToParse
+                && m_frames[lastFrameToParse]->isComplete()) {
+        // We have already parsed this frame.
+        return SkCodec::kSuccess;
+    }
+
+    while (true) {
+        if (!m_streamBuffer.buffer(m_bytesToConsume)) {
+            // The stream does not yet have enough data.
+            return SkCodec::kIncompleteInput;
+        }
+
+        switch (m_state) {
+        case SkGIFLZW: {
+            SkASSERT(!m_frames.empty());
+            auto* frame = m_frames.back().get();
+            frame->addLzwBlock(m_streamBuffer.markPosition(), m_bytesToConsume);
+            GETN(1, SkGIFSubBlock);
+            break;
+        }
+        case SkGIFLZWStart: {
+            SkASSERT(!m_frames.empty());
+            auto* currentFrame = m_frames.back().get();
+
+            currentFrame->setDataSize(this->getOneByte());
+            GETN(1, SkGIFSubBlock);
+            break;
+        }
+
+        case SkGIFType: {
+            const char* currentComponent = m_streamBuffer.get();
+
+            // All GIF files begin with "GIF87a" or "GIF89a".
+            if (!memcmp(currentComponent, "GIF89a", 6))
+                m_version = 89;
+            else if (!memcmp(currentComponent, "GIF87a", 6))
+                m_version = 87;
+            else {
+                // This prevents attempting to continue reading this invalid stream.
+                GETN(0, SkGIFDone);
+                return SkCodec::kInvalidInput;
+            }
+            GETN(7, SkGIFGlobalHeader);
+            break;
+        }
+
+        case SkGIFGlobalHeader: {
+            const unsigned char* currentComponent =
+                reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+            // This is the height and width of the "screen" or frame into which
+            // images are rendered. The individual images can be smaller than
+            // the screen size and located with an origin anywhere within the
+            // screen.
+            // Note that we don't inform the client of the size yet, as it might
+            // change after we read the first frame's image header.
+            fScreenWidth = GETINT16(currentComponent);
+            fScreenHeight = GETINT16(currentComponent + 2);
+
+            const int globalColorMapColors = 2 << (currentComponent[4] & 0x07);
+
+            if ((currentComponent[4] & 0x80) && globalColorMapColors > 0) { /* global map */
+                m_globalColorMap.setNumColors(globalColorMapColors);
+                GETN(SK_BYTES_PER_COLORMAP_ENTRY * globalColorMapColors, SkGIFGlobalColormap);
+                break;
+            }
+
+            GETN(1, SkGIFImageStart);
+            break;
+        }
+
+        case SkGIFGlobalColormap: {
+            m_globalColorMap.setTablePosition(m_streamBuffer.markPosition());
+            GETN(1, SkGIFImageStart);
+            break;
+        }
+
+        case SkGIFImageStart: {
+            const char currentComponent = m_streamBuffer.get()[0];
+
+            if (currentComponent == '!') { // extension.
+                GETN(2, SkGIFExtension);
+                break;
+            }
+
+            if (currentComponent == ',') { // image separator.
+                GETN(9, SkGIFImageHeader);
+                break;
+            }
+
+            // If we get anything other than ',' (image separator), '!'
+            // (extension), or ';' (trailer), there is extraneous data
+            // between blocks. The GIF87a spec tells us to keep reading
+            // until we find an image separator, but GIF89a says such
+            // a file is corrupt. We follow Mozilla's implementation and
+            // proceed as if the file were correctly terminated, so the
+            // GIF will display.
+            GETN(0, SkGIFDone);
+            break;
+        }
+
+        case SkGIFExtension: {
+            const unsigned char* currentComponent =
+                reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+            size_t bytesInBlock = currentComponent[1];
+            SkGIFState exceptionState = SkGIFSkipBlock;
+
+            switch (*currentComponent) {
+            case 0xf9:
+                // The GIF spec mandates that the GIFControlExtension header block length is 4 bytes,
+                exceptionState = SkGIFControlExtension;
+                // and the parser for this block reads 4 bytes, so we must enforce that the buffer
+                // contains at least this many bytes. If the GIF specifies a different length, we
+                // allow that, so long as it's larger; the additional data will simply be ignored.
+                bytesInBlock = std::max(bytesInBlock, static_cast<size_t>(4));
+                break;
+
+            // The GIF spec also specifies the lengths of the following two extensions' headers
+            // (as 12 and 11 bytes, respectively). Because we ignore the plain text extension entirely
+            // and sanity-check the actual length of the application extension header before reading it,
+            // we allow GIFs to deviate from these values in either direction. This is important for
+            // real-world compatibility, as GIFs in the wild exist with application extension headers
+            // that are both shorter and longer than 11 bytes.
+            case 0x01:
+                // ignoring plain text extension
+                break;
+
+            case 0xff:
+                exceptionState = SkGIFApplicationExtension;
+                break;
+
+            case 0xfe:
+                exceptionState = SkGIFConsumeComment;
+                break;
+            }
+
+            if (bytesInBlock)
+                GETN(bytesInBlock, exceptionState);
+            else
+                GETN(1, SkGIFImageStart);
+            break;
+        }
+
+        case SkGIFConsumeBlock: {
+            const unsigned char currentComponent = this->getOneByte();
+            if (!currentComponent)
+                GETN(1, SkGIFImageStart);
+            else
+                GETN(currentComponent, SkGIFSkipBlock);
+            break;
+        }
+
+        case SkGIFSkipBlock: {
+            GETN(1, SkGIFConsumeBlock);
+            break;
+        }
+
+        case SkGIFControlExtension: {
+            const unsigned char* currentComponent =
+                reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+            addFrameIfNecessary();
+            SkGIFFrameContext* currentFrame = m_frames.back().get();
+            if (*currentComponent & 0x1)
+                currentFrame->setTransparentPixel(currentComponent[3]);
+
+            // We ignore the "user input" bit.
+
+            // NOTE: This relies on the values in the FrameDisposalMethod enum
+            // matching those in the GIF spec!
+            int rawDisposalMethod = ((*currentComponent) >> 2) & 0x7;
+            switch (rawDisposalMethod) {
+            case 1:
+            case 2:
+            case 3:
+                currentFrame->setDisposalMethod((SkCodecAnimation::DisposalMethod) rawDisposalMethod);
+                break;
+            case 4:
+                // Some specs say that disposal method 3 is "overwrite previous", others that setting
+                // the third bit of the field (i.e. method 4) is. We map both to the same value.
+                currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kRestorePrevious);
+                break;
+            default:
+                // Other values use the default.
+                currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep);
+                break;
+            }
+            currentFrame->setDuration(GETINT16(currentComponent + 1) * 10);
+            GETN(1, SkGIFConsumeBlock);
+            break;
+        }
+
+        case SkGIFCommentExtension: {
+            const unsigned char currentComponent = this->getOneByte();
+            if (currentComponent)
+                GETN(currentComponent, SkGIFConsumeComment);
+            else
+                GETN(1, SkGIFImageStart);
+            break;
+        }
+
+        case SkGIFConsumeComment: {
+            GETN(1, SkGIFCommentExtension);
+            break;
+        }
+
+        case SkGIFApplicationExtension: {
+            // Check for netscape application extension.
+            if (m_bytesToConsume == 11) {
+                const unsigned char* currentComponent =
+                    reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+                if (!memcmp(currentComponent, "NETSCAPE2.0", 11) || !memcmp(currentComponent, "ANIMEXTS1.0", 11))
+                    GETN(1, SkGIFNetscapeExtensionBlock);
+            }
+
+            if (m_state != SkGIFNetscapeExtensionBlock)
+                GETN(1, SkGIFConsumeBlock);
+            break;
+        }
+
+        // Netscape-specific GIF extension: animation looping.
+        case SkGIFNetscapeExtensionBlock: {
+            const int currentComponent = this->getOneByte();
+            // SkGIFConsumeNetscapeExtension always reads 3 bytes from the stream; we should at least wait for this amount.
+            if (currentComponent)
+                GETN(std::max(3, currentComponent), SkGIFConsumeNetscapeExtension);
+            else
+                GETN(1, SkGIFImageStart);
+            break;
+        }
+
+        // Parse netscape-specific application extensions
+        case SkGIFConsumeNetscapeExtension: {
+            const unsigned char* currentComponent =
+                reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+            int netscapeExtension = currentComponent[0] & 7;
+
+            // Loop entire animation specified # of times. Only read the loop count during the first iteration.
+            if (netscapeExtension == 1) {
+                m_loopCount = GETINT16(currentComponent + 1);
+
+                // Zero loop count is infinite animation loop request.
+                if (!m_loopCount)
+                    m_loopCount = SkCodec::kRepetitionCountInfinite;
+
+                GETN(1, SkGIFNetscapeExtensionBlock);
+
+                if (SkGIFLoopCountQuery == query) {
+                    m_streamBuffer.flush();
+                    return SkCodec::kSuccess;
+                }
+            } else if (netscapeExtension == 2) {
+                // Wait for specified # of bytes to enter buffer.
+
+                // Don't do this, this extension doesn't exist (isn't used at all)
+                // and doesn't do anything, as our streaming/buffering takes care of it all...
+                // See: http://semmix.pl/color/exgraf/eeg24.htm
+                GETN(1, SkGIFNetscapeExtensionBlock);
+            } else {
+                // 0,3-7 are yet to be defined netscape extension codes
+                // This prevents attempting to continue reading this invalid stream.
+                GETN(0, SkGIFDone);
+                return SkCodec::kInvalidInput;
+            }
+            break;
+        }
+
+        case SkGIFImageHeader: {
+            int height, width, xOffset, yOffset;
+            const unsigned char* currentComponent =
+                reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
+
+            /* Get image offsets, with respect to the screen origin */
+            xOffset = GETINT16(currentComponent);
+            yOffset = GETINT16(currentComponent + 2);
+
+            /* Get image width and height. */
+            width  = GETINT16(currentComponent + 4);
+            height = GETINT16(currentComponent + 6);
+
+            // Some GIF files have frames that don't fit in the specified
+            // overall image size. For the first frame, we can simply enlarge
+            // the image size to allow the frame to be visible.  We can't do
+            // this on subsequent frames because the rest of the decoding
+            // infrastructure assumes the image size won't change as we
+            // continue decoding, so any subsequent frames that are even
+            // larger will be cropped.
+            // Luckily, handling just the first frame is sufficient to deal
+            // with most cases, e.g. ones where the image size is erroneously
+            // set to zero, since usually the first frame completely fills
+            // the image.
+            if (currentFrameIsFirstFrame()) {
+                fScreenHeight = std::max(fScreenHeight, yOffset + height);
+                fScreenWidth = std::max(fScreenWidth, xOffset + width);
+            }
+
+            // NOTE: Chromium placed this block after setHeaderDefined, down
+            // below we returned true when asked for the size. So Chromium
+            // created an image which would fail. Is this the correct behavior?
+            // We choose to return false early, so we will not create an
+            // SkCodec.
+
+            // Work around more broken GIF files that have zero image width or
+            // height.
+            if (!height || !width) {
+                height = fScreenHeight;
+                width = fScreenWidth;
+                if (!height || !width) {
+                    // This prevents attempting to continue reading this invalid stream.
+                    GETN(0, SkGIFDone);
+                    return SkCodec::kInvalidInput;
+                }
+            }
+
+            const bool isLocalColormapDefined = SkToBool(currentComponent[8] & 0x80);
+            // The three low-order bits of currentComponent[8] specify the bits per pixel.
+            const int numColors = 2 << (currentComponent[8] & 0x7);
+            if (currentFrameIsFirstFrame()) {
+                const int transPix = m_frames.empty() ? SkGIFColorMap::kNotFound
+                                                      : m_frames[0]->transparentPixel();
+                if (is_palette_index_valid(transPix)) {
+                    m_firstFrameHasAlpha = true;
+                } else {
+                    const bool frameIsSubset = xOffset > 0 || yOffset > 0
+                            || width < fScreenWidth
+                            || height < fScreenHeight;
+                    m_firstFrameHasAlpha = frameIsSubset;
+                }
+            }
+
+            addFrameIfNecessary();
+            SkGIFFrameContext* currentFrame = m_frames.back().get();
+            currentFrame->setHeaderDefined();
+
+            if (query == SkGIFSizeQuery) {
+                // The decoder needs to stop, so we return here, before
+                // flushing the buffer. Next time through, we'll be in the same
+                // state, requiring the same amount in the buffer.
+                return SkCodec::kSuccess;
+            }
+
+
+            currentFrame->setXYWH(xOffset, yOffset, width, height);
+            currentFrame->setInterlaced(SkToBool(currentComponent[8] & 0x40));
+
+            // Overlaying interlaced, transparent GIFs over
+            // existing image data using the Haeberli display hack
+            // requires saving the underlying image in order to
+            // avoid jaggies at the transparency edges. We are
+            // unprepared to deal with that, so don't display such
+            // images progressively. Which means only the first
+            // frame can be progressively displayed.
+            // FIXME: It is possible that a non-transparent frame
+            // can be interlaced and progressively displayed.
+            currentFrame->setProgressiveDisplay(currentFrameIsFirstFrame());
+
+            if (isLocalColormapDefined) {
+                currentFrame->localColorMap().setNumColors(numColors);
+                GETN(SK_BYTES_PER_COLORMAP_ENTRY * numColors, SkGIFImageColormap);
+                break;
+            }
+
+            setAlphaAndRequiredFrame(currentFrame);
+            GETN(1, SkGIFLZWStart);
+            break;
+        }
+
+        case SkGIFImageColormap: {
+            SkASSERT(!m_frames.empty());
+            auto* currentFrame = m_frames.back().get();
+            auto& cmap = currentFrame->localColorMap();
+            cmap.setTablePosition(m_streamBuffer.markPosition());
+            setAlphaAndRequiredFrame(currentFrame);
+            GETN(1, SkGIFLZWStart);
+            break;
+        }
+
+        case SkGIFSubBlock: {
+            const size_t bytesInBlock = this->getOneByte();
+            if (bytesInBlock)
+                GETN(bytesInBlock, SkGIFLZW);
+            else {
+                // Finished parsing one frame; Process next frame.
+                SkASSERT(!m_frames.empty());
+                // Note that some broken GIF files do not have enough LZW blocks to fully
+                // decode all rows but we treat it as frame complete.
+                m_frames.back()->setComplete();
+                GETN(1, SkGIFImageStart);
+                if (lastFrameToParse >= 0 && m_frames.count() > lastFrameToParse) {
+                    m_streamBuffer.flush();
+                    return SkCodec::kSuccess;
+                }
+            }
+            break;
+        }
+
+        case SkGIFDone: {
+            m_parseCompleted = true;
+            return SkCodec::kSuccess;
+        }
+
+        default:
+            // We shouldn't ever get here.
+            // This prevents attempting to continue reading this invalid stream.
+            GETN(0, SkGIFDone);
+            return SkCodec::kInvalidInput;
+            break;
+        }   // switch
+        m_streamBuffer.flush();
+    }
+}
+
+void SkGifImageReader::addFrameIfNecessary()
+{
+    if (m_frames.empty() || m_frames.back()->isComplete()) {
+        const int i = m_frames.count();
+        m_frames.emplace_back(new SkGIFFrameContext(i));
+    }
+}
+
+SkEncodedInfo::Alpha SkGIFFrameContext::onReportedAlpha() const {
+    // Note: We could correct these after decoding - i.e. some frames may turn out to be
+    // independent and opaque if they do not use the transparent pixel, but that would require
+    // checking whether each pixel used the transparent index.
+    return is_palette_index_valid(this->transparentPixel()) ? SkEncodedInfo::kBinary_Alpha
+                                                            : SkEncodedInfo::kOpaque_Alpha;
+}
+
+// FIXME: Move this method to close to doLZW().
+bool SkGIFLZWContext::prepareToDecode()
+{
+    SkASSERT(m_frameContext->isDataSizeDefined() && m_frameContext->isHeaderDefined());
+
+    // Since we use a codesize of 1 more than the datasize, we need to ensure
+    // that our datasize is strictly less than the SK_MAX_DICTIONARY_ENTRY_BITS.
+    if (m_frameContext->dataSize() >= SK_MAX_DICTIONARY_ENTRY_BITS)
+        return false;
+    clearCode = 1 << m_frameContext->dataSize();
+    avail = clearCode + 2;
+    oldcode = -1;
+    codesize = m_frameContext->dataSize() + 1;
+    codemask = (1 << codesize) - 1;
+    datum = bits = 0;
+    ipass = m_frameContext->interlaced() ? 1 : 0;
+    irow = 0;
+
+    // We want to know the longest sequence encodable by a dictionary with
+    // SK_MAX_DICTIONARY_ENTRIES entries. If we ignore the need to encode the base
+    // values themselves at the beginning of the dictionary, as well as the need
+    // for a clear code or a termination code, we could use every entry to
+    // encode a series of multiple values. If the input value stream looked
+    // like "AAAAA..." (a long string of just one value), the first dictionary
+    // entry would encode AA, the next AAA, the next AAAA, and so forth. Thus
+    // the longest sequence would be SK_MAX_DICTIONARY_ENTRIES + 1 values.
+    //
+    // However, we have to account for reserved entries. The first |datasize|
+    // bits are reserved for the base values, and the next two entries are
+    // reserved for the clear code and termination code. In theory a GIF can
+    // set the datasize to 0, meaning we have just two reserved entries, making
+    // the longest sequence (SK_MAX_DICTIONARY_ENTIRES + 1) - 2 values long. Since
+    // each value is a byte, this is also the number of bytes in the longest
+    // encodable sequence.
+    constexpr size_t kMaxSequence = SK_MAX_DICTIONARY_ENTRIES - 1;
+    constexpr size_t kMaxBytes = (kMaxSequence + SK_DICTIONARY_WORD_SIZE - 1)
+                         & ~(SK_DICTIONARY_WORD_SIZE - 1);
+
+    // Now allocate the output buffer. We decode directly into this buffer
+    // until we have at least one row worth of data, then call outputRow().
+    // This means worst case we may have (row width - 1) bytes in the buffer
+    // and then decode a sequence |kMaxBytes| long to append.
+    rowBuffer.reset(m_frameContext->width() - 1 + kMaxBytes);
+    rowIter = rowBuffer.begin();
+    rowsRemaining = m_frameContext->height();
+
+    // Clearing the whole suffix table lets us be more tolerant of bad data.
+    for (int i = 0; i < clearCode; ++i) {
+        std::fill_n(suffix[i].begin(), SK_DICTIONARY_WORD_SIZE, 0);
+        suffix[i][0] = i;
+        suffixLength[i] = 1;
+        prefix[i] = i;  // ensure that we have a place to find firstchar
+    }
+    return true;
+}
diff --git a/SkGifImageReader.h b/SkGifImageReader.h
new file mode 100644
index 0000000..9b02255
--- /dev/null
+++ b/SkGifImageReader.h
@@ -0,0 +1,398 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef SkGifImageReader_h
+#define SkGifImageReader_h
+
+// Define ourselves as the clientPtr.  Mozilla just hacked their C++ callback class into this old C decoder,
+// so we will too.
+class SkLibGifCodec;
+
+#include "include/codec/SkCodec.h"
+#include "include/codec/SkCodecAnimation.h"
+#include "include/core/SkData.h"
+#include "include/core/SkImageInfo.h"
+#include "include/private/SkTArray.h"
+#include "src/codec/SkCodecPriv.h"
+#include "src/codec/SkColorTable.h"
+#include "src/codec/SkFrameHolder.h"
+#include "src/codec/SkStreamBuffer.h"
+
+#include <array>
+#include <memory>
+
+typedef SkTArray<unsigned char, true> SkGIFRow;
+
+
+#define SK_MAX_DICTIONARY_ENTRY_BITS 12
+#define SK_MAX_DICTIONARY_ENTRIES    4096 // 2^SK_MAX_DICTIONARY_ENTRY_BITS
+#define SK_MAX_COLORS                256
+#define SK_BYTES_PER_COLORMAP_ENTRY  3
+#define SK_DICTIONARY_WORD_SIZE      8
+
+// List of possible parsing states.
+enum SkGIFState {
+    SkGIFType,
+    SkGIFGlobalHeader,
+    SkGIFGlobalColormap,
+    SkGIFImageStart,
+    SkGIFImageHeader,
+    SkGIFImageColormap,
+    SkGIFImageBody,
+    SkGIFLZWStart,
+    SkGIFLZW,
+    SkGIFSubBlock,
+    SkGIFExtension,
+    SkGIFControlExtension,
+    SkGIFConsumeBlock,
+    SkGIFSkipBlock,
+    SkGIFDone,
+    SkGIFCommentExtension,
+    SkGIFApplicationExtension,
+    SkGIFNetscapeExtensionBlock,
+    SkGIFConsumeNetscapeExtension,
+    SkGIFConsumeComment
+};
+
+class SkGIFFrameContext;
+class SkGIFColorMap;
+
+// LZW decoder state machine.
+class SkGIFLZWContext final : public SkNoncopyable {
+public:
+    SkGIFLZWContext(SkLibGifCodec* client, const SkGIFFrameContext* frameContext)
+        : codesize(0)
+        , codemask(0)
+        , clearCode(0)
+        , avail(0)
+        , oldcode(0)
+        , bits(0)
+        , datum(0)
+        , ipass(0)
+        , irow(0)
+        , rowsRemaining(0)
+        , rowIter(nullptr)
+        , m_client(client)
+        , m_frameContext(frameContext)
+    { }
+
+    bool prepareToDecode();
+    void outputRow(const unsigned char* rowBegin);
+    bool doLZW(const unsigned char* block, size_t bytesInBlock);
+    bool hasRemainingRows() { return SkToBool(rowsRemaining); }
+
+private:
+    // LZW decoding states and output states.
+    int codesize;
+    int codemask;
+    int clearCode; // Codeword used to trigger dictionary reset.
+    int avail; // Index of next available slot in dictionary.
+    int oldcode;
+    int bits; // Number of unread bits in "datum".
+    int datum; // 32-bit input buffer.
+    int ipass; // Interlace pass; Ranges 1-4 if interlaced.
+    size_t irow; // Current output row, starting at zero.
+    size_t rowsRemaining; // Rows remaining to be output.
+
+    unsigned short prefix[SK_MAX_DICTIONARY_ENTRIES];
+    std::array<std::array<unsigned char, SK_DICTIONARY_WORD_SIZE>,
+                SK_MAX_DICTIONARY_ENTRIES> suffix;
+    unsigned short suffixLength[SK_MAX_DICTIONARY_ENTRIES];
+    SkGIFRow rowBuffer; // Single scanline temporary buffer.
+    unsigned char* rowIter;
+
+    SkLibGifCodec* const m_client;
+    const SkGIFFrameContext* m_frameContext;
+};
+
+struct SkGIFLZWBlock {
+ public:
+  SkGIFLZWBlock(size_t position, size_t size)
+      : blockPosition(position), blockSize(size) {}
+
+  size_t blockPosition;
+  size_t blockSize;
+};
+
+class SkGIFColorMap final {
+public:
+    static constexpr int kNotFound = -1;
+
+    SkGIFColorMap()
+        : m_isDefined(false)
+        , m_position(0)
+        , m_colors(0)
+        , m_transPixel(kNotFound)
+        , m_packColorProc(nullptr)
+    {
+    }
+
+    void setNumColors(int colors) {
+        SkASSERT(!m_colors);
+        SkASSERT(!m_position);
+
+        m_colors = colors;
+    }
+
+    void setTablePosition(size_t position) {
+        SkASSERT(!m_isDefined);
+
+        m_position = position;
+        m_isDefined = true;
+    }
+
+    int numColors() const { return m_colors; }
+
+    bool isDefined() const { return m_isDefined; }
+
+    // Build RGBA table using the data stream.
+    sk_sp<SkColorTable> buildTable(SkStreamBuffer*, SkColorType dstColorType,
+                                   int transparentPixel) const;
+
+private:
+    bool m_isDefined;
+    size_t m_position;
+    int m_colors;
+    // Cached values. If these match on a new request, we can reuse m_table.
+    mutable int m_transPixel;
+    mutable PackColorProc m_packColorProc;
+    mutable sk_sp<SkColorTable> m_table;
+};
+
+class SkGifImageReader;
+
+// LocalFrame output state machine.
+class SkGIFFrameContext : public SkFrame {
+public:
+    SkGIFFrameContext(int id)
+        : INHERITED(id)
+        , m_transparentPixel(SkGIFColorMap::kNotFound)
+        , m_dataSize(0)
+        , m_progressiveDisplay(false)
+        , m_interlaced(false)
+        , m_currentLzwBlock(0)
+        , m_isComplete(false)
+        , m_isHeaderDefined(false)
+        , m_isDataSizeDefined(false)
+    {
+    }
+
+    ~SkGIFFrameContext() override
+    {
+    }
+
+    void addLzwBlock(size_t position, size_t size)
+    {
+        m_lzwBlocks.emplace_back(position, size);
+    }
+
+    bool decode(SkStreamBuffer*, SkLibGifCodec* client, bool* frameDecoded);
+
+    int transparentPixel() const { return m_transparentPixel; }
+    void setTransparentPixel(int pixel) { m_transparentPixel = pixel; }
+
+    bool isComplete() const { return m_isComplete; }
+    void setComplete() { m_isComplete = true; }
+    bool isHeaderDefined() const { return m_isHeaderDefined; }
+    void setHeaderDefined() { m_isHeaderDefined = true; }
+    bool isDataSizeDefined() const { return m_isDataSizeDefined; }
+    int dataSize() const { return m_dataSize; }
+    void setDataSize(int size)
+    {
+        m_dataSize = size;
+        m_isDataSizeDefined = true;
+    }
+    bool progressiveDisplay() const { return m_progressiveDisplay; }
+    void setProgressiveDisplay(bool progressiveDisplay) { m_progressiveDisplay = progressiveDisplay; }
+    bool interlaced() const { return m_interlaced; }
+    void setInterlaced(bool interlaced) { m_interlaced = interlaced; }
+
+    void clearDecodeState() { m_lzwContext.reset(); }
+    const SkGIFColorMap& localColorMap() const { return m_localColorMap; }
+    SkGIFColorMap& localColorMap() { return m_localColorMap; }
+
+protected:
+    SkEncodedInfo::Alpha onReportedAlpha() const override;
+
+private:
+    int m_transparentPixel; // Index of transparent pixel. Value is kNotFound if there is no transparent pixel.
+    int m_dataSize;
+
+    bool m_progressiveDisplay; // If true, do Haeberli interlace hack.
+    bool m_interlaced; // True, if scanlines arrive interlaced order.
+
+    std::unique_ptr<SkGIFLZWContext> m_lzwContext;
+    // LZW blocks for this frame.
+    SkTArray<SkGIFLZWBlock> m_lzwBlocks;
+
+    SkGIFColorMap m_localColorMap;
+
+    int m_currentLzwBlock;
+    bool m_isComplete;
+    bool m_isHeaderDefined;
+    bool m_isDataSizeDefined;
+
+    typedef SkFrame INHERITED;
+};
+
+class SkGifImageReader final : public SkFrameHolder {
+public:
+    // This takes ownership of stream.
+    SkGifImageReader(std::unique_ptr<SkStream> stream)
+        : m_client(nullptr)
+        , m_state(SkGIFType)
+        , m_bytesToConsume(6) // Number of bytes for GIF type, either "GIF87a" or "GIF89a".
+        , m_version(0)
+        , m_loopCount(cLoopCountNotSeen)
+        , m_streamBuffer(std::move(stream))
+        , m_parseCompleted(false)
+        , m_firstFrameHasAlpha(false)
+    {
+    }
+
+    ~SkGifImageReader() override
+    {
+    }
+
+    void setClient(SkLibGifCodec* client) { m_client = client; }
+
+    // Option to pass to parse(). All enums are negative, because a non-negative value is used to
+    // indicate that the Reader should parse up to and including the frame indicated.
+    enum SkGIFParseQuery {
+        // Parse enough to determine the size. Note that this parses the first frame's header,
+        // since we may decide to expand based on the frame's dimensions.
+        SkGIFSizeQuery        = -1,
+        // Parse to the end, so we know about all frames.
+        SkGIFFrameCountQuery  = -2,
+        // Parse until we see the loop count.
+        SkGIFLoopCountQuery   = -3,
+    };
+
+    // Parse incoming GIF data stream into internal data structures.
+    // Non-negative values are used to indicate to parse through that frame.
+    SkCodec::Result parse(SkGIFParseQuery);
+
+    // Decode the frame indicated by frameIndex.
+    // frameComplete will be set to true if the frame is completely decoded.
+    // The method returns false if there is an error.
+    bool decode(int frameIndex, bool* frameComplete);
+
+    int imagesCount() const
+    {
+        const int frames = m_frames.count();
+        if (!frames) {
+            return 0;
+        }
+
+        // This avoids counting an empty frame when the file is truncated (or
+        // simply not yet complete) after receiving SkGIFControlExtension (and
+        // possibly SkGIFImageHeader) but before reading the color table. This
+        // ensures that we do not count a frame before we know its required
+        // frame.
+        return m_frames.back()->reachedStartOfData() ? frames : frames - 1;
+    }
+    int loopCount() const {
+        if (cLoopCountNotSeen == m_loopCount) {
+            return 0;
+        }
+        return m_loopCount;
+    }
+
+    const SkGIFColorMap& globalColorMap() const
+    {
+        return m_globalColorMap;
+    }
+
+    const SkGIFFrameContext* frameContext(int index) const
+    {
+        return index >= 0 && index < m_frames.count()
+                ? m_frames[index].get() : nullptr;
+    }
+
+    void clearDecodeState() {
+        for (int index = 0; index < m_frames.count(); index++) {
+            m_frames[index]->clearDecodeState();
+        }
+    }
+
+    // Return the color table for frame index (which may be the global color table).
+    sk_sp<SkColorTable> getColorTable(SkColorType dstColorType, int index);
+
+    bool firstFrameHasAlpha() const { return m_firstFrameHasAlpha; }
+
+protected:
+    const SkFrame* onGetFrame(int i) const override {
+        return static_cast<const SkFrame*>(this->frameContext(i));
+    }
+
+private:
+    // Requires that one byte has been buffered into m_streamBuffer.
+    unsigned char getOneByte() const {
+        return reinterpret_cast<const unsigned char*>(m_streamBuffer.get())[0];
+    }
+
+    void addFrameIfNecessary();
+    bool currentFrameIsFirstFrame() const
+    {
+        return m_frames.empty() || (m_frames.count() == 1 && !m_frames[0]->isComplete());
+    }
+
+    // Unowned pointer
+    SkLibGifCodec* m_client;
+
+    // Parsing state machine.
+    SkGIFState m_state; // Current decoder master state.
+    size_t m_bytesToConsume; // Number of bytes to consume for next stage of parsing.
+
+    // Global (multi-image) state.
+    int m_version; // Either 89 for GIF89 or 87 for GIF87.
+    SkGIFColorMap m_globalColorMap;
+
+    static constexpr int cLoopCountNotSeen = -2;
+    int m_loopCount; // Netscape specific extension block to control the number of animation loops a GIF renders.
+
+    SkTArray<std::unique_ptr<SkGIFFrameContext>> m_frames;
+
+    SkStreamBuffer m_streamBuffer;
+    bool m_parseCompleted;
+
+    // This value can be computed before we create a SkGIFFrameContext, so we
+    // store it here instead of on m_frames[0].
+    bool m_firstFrameHasAlpha;
+};
+
+#endif
diff --git a/SkLibGifCodec.cpp b/SkLibGifCodec.cpp
new file mode 100644
index 0000000..d4f8790
--- /dev/null
+++ b/SkLibGifCodec.cpp
@@ -0,0 +1,532 @@
+// Copyright 2015 Google Inc.
+// Use of this source code is governed by the BSD-3-Clause license that can be
+// found in the LICENSE.md file.
+
+/*
+ * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "SkGifCodec.h"
+#include "SkLibGifCodec.h"
+
+#include "include/codec/SkCodecAnimation.h"
+#include "include/core/SkStream.h"
+#include "include/private/SkColorData.h"
+#include "src/codec/SkCodecPriv.h"
+#include "src/codec/SkColorTable.h"
+#include "src/codec/SkSwizzler.h"
+#include "src/core/SkMakeUnique.h"
+
+#include <algorithm>
+
+#define GIF87_STAMP "GIF87a"
+#define GIF89_STAMP "GIF89a"
+#define GIF_STAMP_LEN 6
+
+/*
+ * Checks the start of the stream to see if the image is a gif
+ */
+bool SkGifCodec::IsGif(const void* buf, size_t bytesRead) {
+    if (bytesRead >= GIF_STAMP_LEN) {
+        if (memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+            memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * Error function
+ */
+static SkCodec::Result gif_error(const char* msg, SkCodec::Result result = SkCodec::kInvalidInput) {
+    SkCodecPrintf("Gif Error: %s\n", msg);
+    return result;
+}
+
+std::unique_ptr<SkCodec> SkGifCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
+                                                    SkCodec::Result* result) {
+    std::unique_ptr<SkGifImageReader> reader(new SkGifImageReader(std::move(stream)));
+    *result = reader->parse(SkGifImageReader::SkGIFSizeQuery);
+    if (*result != SkCodec::kSuccess) {
+        return nullptr;
+    }
+
+    // If no images are in the data, or the first header is not yet defined, we cannot
+    // create a codec. In either case, the width and height are not yet known.
+    auto* frame = reader->frameContext(0);
+    if (!frame || !frame->isHeaderDefined()) {
+        *result = SkCodec::kInvalidInput;
+        return nullptr;
+    }
+
+    // isHeaderDefined() will not return true if the screen size is empty.
+    SkASSERT(reader->screenHeight() > 0 && reader->screenWidth() > 0);
+
+    const auto alpha = reader->firstFrameHasAlpha() ? SkEncodedInfo::kBinary_Alpha
+                                                    : SkEncodedInfo::kOpaque_Alpha;
+    // Use kPalette since Gifs are encoded with a color table.
+    // FIXME: Gifs can actually be encoded with 4-bits per pixel. Using 8 works, but we could skip
+    //        expanding to 8 bits and take advantage of the SkSwizzler to work from 4.
+    auto encodedInfo = SkEncodedInfo::Make(reader->screenWidth(), reader->screenHeight(),
+                                           SkEncodedInfo::kPalette_Color, alpha, 8);
+    return std::unique_ptr<SkCodec>(new SkLibGifCodec(std::move(encodedInfo), reader.release()));
+}
+
+bool SkLibGifCodec::onRewind() {
+    fReader->clearDecodeState();
+    return true;
+}
+
+SkLibGifCodec::SkLibGifCodec(SkEncodedInfo&& encodedInfo, SkGifImageReader* reader)
+    : INHERITED(std::move(encodedInfo), skcms_PixelFormat_RGBA_8888, nullptr)
+    , fReader(reader)
+    , fTmpBuffer(nullptr)
+    , fSwizzler(nullptr)
+    , fCurrColorTable(nullptr)
+    , fCurrColorTableIsReal(false)
+    , fFilledBackground(false)
+    , fFirstCallToIncrementalDecode(false)
+    , fDst(nullptr)
+    , fDstRowBytes(0)
+    , fRowsDecoded(0)
+{
+    reader->setClient(this);
+}
+
+int SkLibGifCodec::onGetFrameCount() {
+    fReader->parse(SkGifImageReader::SkGIFFrameCountQuery);
+    return fReader->imagesCount();
+}
+
+bool SkLibGifCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const {
+    if (i >= fReader->imagesCount()) {
+        return false;
+    }
+
+    const SkGIFFrameContext* frameContext = fReader->frameContext(i);
+    SkASSERT(frameContext->reachedStartOfData());
+
+    if (frameInfo) {
+        frameInfo->fDuration = frameContext->getDuration();
+        frameInfo->fRequiredFrame = frameContext->getRequiredFrame();
+        frameInfo->fFullyReceived = frameContext->isComplete();
+        frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType
+                                                         : kOpaque_SkAlphaType;
+        frameInfo->fDisposalMethod = frameContext->getDisposalMethod();
+    }
+    return true;
+}
+
+int SkLibGifCodec::onGetRepetitionCount() {
+    fReader->parse(SkGifImageReader::SkGIFLoopCountQuery);
+    return fReader->loopCount();
+}
+
+static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
+
+void SkLibGifCodec::initializeColorTable(const SkImageInfo& dstInfo, int frameIndex) {
+    SkColorType colorTableColorType = dstInfo.colorType();
+    if (this->colorXform()) {
+        colorTableColorType = kXformSrcColorType;
+    }
+
+    sk_sp<SkColorTable> currColorTable = fReader->getColorTable(colorTableColorType, frameIndex);
+    fCurrColorTableIsReal = static_cast<bool>(currColorTable);
+    if (!fCurrColorTableIsReal) {
+        // This is possible for an empty frame. Create a dummy with one value (transparent).
+        SkPMColor color = SK_ColorTRANSPARENT;
+        fCurrColorTable.reset(new SkColorTable(&color, 1));
+    } else if (this->colorXform() && !this->xformOnDecode()) {
+        SkPMColor dstColors[256];
+        this->applyColorXform(dstColors, currColorTable->readColors(),
+                              currColorTable->count());
+        fCurrColorTable.reset(new SkColorTable(dstColors, currColorTable->count()));
+    } else {
+        fCurrColorTable = std::move(currColorTable);
+    }
+}
+
+
+SkCodec::Result SkLibGifCodec::prepareToDecode(const SkImageInfo& dstInfo, const Options& opts) {
+    if (opts.fSubset) {
+        return gif_error("Subsets not supported.\n", kUnimplemented);
+    }
+
+    const int frameIndex = opts.fFrameIndex;
+    if (frameIndex > 0 && kRGB_565_SkColorType == dstInfo.colorType()) {
+        // FIXME: In theory, we might be able to support this, but it's not clear that it
+        // is necessary (Chromium does not decode to 565, and Android does not decode
+        // frames beyond the first). Disabling it because it is somewhat difficult:
+        // - If there is a transparent pixel, and this frame draws on top of another frame
+        //   (if the frame is independent with a transparent pixel, we should not decode to
+        //   565 anyway, since it is not opaque), we need to skip drawing the transparent
+        //   pixels (see writeTransparentPixels in haveDecodedRow). We currently do this by
+        //   first swizzling into temporary memory, then copying into the destination. (We
+        //   let the swizzler handle it first because it may need to sample.) After
+        //   swizzling to 565, we do not know which pixels in our temporary memory
+        //   correspond to the transparent pixel, so we do not know what to skip. We could
+        //   special case the non-sampled case (no need to swizzle), but as this is
+        //   currently unused we can just not support it.
+        return gif_error("Cannot decode multiframe gif (except frame 0) as 565.\n",
+                         kInvalidConversion);
+    }
+
+    const auto* frame = fReader->frameContext(frameIndex);
+    SkASSERT(frame);
+    if (0 == frameIndex) {
+        // SkCodec does not have a way to just parse through frame 0, so we
+        // have to do so manually, here.
+        fReader->parse((SkGifImageReader::SkGIFParseQuery) 0);
+        if (!frame->reachedStartOfData()) {
+            // We have parsed enough to know that there is a color map, but cannot
+            // parse the map itself yet. Exit now, so we do not build an incorrect
+            // table.
+            return gif_error("color map not available yet\n", kIncompleteInput);
+        }
+    } else {
+        // Parsing happened in SkCodec::getPixels.
+        SkASSERT(frameIndex < fReader->imagesCount());
+        SkASSERT(frame->reachedStartOfData());
+    }
+
+    if (this->xformOnDecode()) {
+        fXformBuffer.reset(new uint32_t[dstInfo.width()]);
+        sk_bzero(fXformBuffer.get(), dstInfo.width() * sizeof(uint32_t));
+    }
+
+    fTmpBuffer.reset(new uint8_t[dstInfo.minRowBytes()]);
+
+    this->initializeColorTable(dstInfo, frameIndex);
+    this->initializeSwizzler(dstInfo, frameIndex);
+
+    SkASSERT(fCurrColorTable);
+    return kSuccess;
+}
+
+void SkLibGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex) {
+    const SkGIFFrameContext* frame = fReader->frameContext(frameIndex);
+    // This is only called by prepareToDecode, which ensures frameIndex is in range.
+    SkASSERT(frame);
+
+    const int xBegin = frame->xOffset();
+    const int xEnd = std::min(frame->frameRect().right(), fReader->screenWidth());
+
+    // CreateSwizzler only reads left and right of the frame. We cannot use the frame's raw
+    // frameRect, since it might extend beyond the edge of the frame.
+    SkIRect swizzleRect = SkIRect::MakeLTRB(xBegin, 0, xEnd, 0);
+
+    SkImageInfo swizzlerInfo = dstInfo;
+    if (this->colorXform()) {
+        swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType);
+        if (kPremul_SkAlphaType == dstInfo.alphaType()) {
+            swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType);
+        }
+    }
+
+    // The default Options should be fine:
+    // - we'll ignore if the memory is zero initialized - unless we're the first frame, this won't
+    //   matter anyway.
+    // - subsets are not supported for gif
+    // - the swizzler does not need to know about the frame.
+    // We may not be able to use the real Options anyway, since getPixels does not store it (due to
+    // a bug).
+    fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fCurrColorTable->readColors(),
+                                 swizzlerInfo, Options(), &swizzleRect);
+    SkASSERT(fSwizzler.get());
+}
+
+/*
+ * Initiates the gif decode
+ */
+SkCodec::Result SkLibGifCodec::onGetPixels(const SkImageInfo& dstInfo,
+                                        void* pixels, size_t dstRowBytes,
+                                        const Options& opts,
+                                        int* rowsDecoded) {
+    Result result = this->prepareToDecode(dstInfo, opts);
+    switch (result) {
+        case kSuccess:
+            break;
+        case kIncompleteInput:
+            // onStartIncrementalDecode treats this as incomplete, since it may
+            // provide more data later, but in this case, no more data will be
+            // provided, and there is nothing to draw. We also cannot return
+            // kIncompleteInput, which will make SkCodec attempt to fill
+            // remaining rows, but that requires an SkSwizzler, which we have
+            // not created.
+            return kInvalidInput;
+        default:
+            return result;
+    }
+
+    if (dstInfo.dimensions() != this->dimensions()) {
+        return gif_error("Scaling not supported.\n", kInvalidScale);
+    }
+
+    fDst = pixels;
+    fDstRowBytes = dstRowBytes;
+
+    return this->decodeFrame(true, opts, rowsDecoded);
+}
+
+SkCodec::Result SkLibGifCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo,
+                                                     void* pixels, size_t dstRowBytes,
+                                                     const SkCodec::Options& opts) {
+    Result result = this->prepareToDecode(dstInfo, opts);
+    if (result != kSuccess) {
+        return result;
+    }
+
+    fDst = pixels;
+    fDstRowBytes = dstRowBytes;
+
+    fFirstCallToIncrementalDecode = true;
+
+    return kSuccess;
+}
+
+SkCodec::Result SkLibGifCodec::onIncrementalDecode(int* rowsDecoded) {
+    // It is possible the client has appended more data. Parse, if needed.
+    const auto& options = this->options();
+    const int frameIndex = options.fFrameIndex;
+    fReader->parse((SkGifImageReader::SkGIFParseQuery) frameIndex);
+
+    const bool firstCallToIncrementalDecode = fFirstCallToIncrementalDecode;
+    fFirstCallToIncrementalDecode = false;
+    return this->decodeFrame(firstCallToIncrementalDecode, options, rowsDecoded);
+}
+
+SkCodec::Result SkLibGifCodec::decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded) {
+    const SkImageInfo& dstInfo = this->dstInfo();
+    const int scaledHeight = get_scaled_dimension(dstInfo.height(), fSwizzler->sampleY());
+
+    const int frameIndex = opts.fFrameIndex;
+    SkASSERT(frameIndex < fReader->imagesCount());
+    const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex);
+    if (firstAttempt) {
+        // rowsDecoded reports how many rows have been initialized, so a layer above
+        // can fill the rest. In some cases, we fill the background before decoding
+        // (or it is already filled for us), so we report rowsDecoded to be the full
+        // height.
+        bool filledBackground = false;
+        if (frameContext->getRequiredFrame() == kNoFrame) {
+            // We may need to clear to transparent for one of the following reasons:
+            // - The frameRect does not cover the full bounds. haveDecodedRow will
+            //   only draw inside the frameRect, so we need to clear the rest.
+            // - The frame is interlaced. There is no obvious way to fill
+            //   afterwards for an incomplete image. (FIXME: Does the first pass
+            //   cover all rows? If so, we do not have to fill here.)
+            // - There is no color table for this frame. In that case will not
+            //   draw anything, so we need to fill.
+            if (frameContext->frameRect() != this->bounds()
+                    || frameContext->interlaced() || !fCurrColorTableIsReal) {
+                auto fillInfo = dstInfo.makeWH(fSwizzler->fillWidth(), scaledHeight);
+                SkSampler::Fill(fillInfo, fDst, fDstRowBytes, opts.fZeroInitialized);
+                filledBackground = true;
+            }
+        } else {
+            // Not independent.
+            // SkCodec ensured that the prior frame has been decoded.
+            filledBackground = true;
+        }
+
+        fFilledBackground = filledBackground;
+        if (filledBackground) {
+            // Report the full (scaled) height, since the client will never need to fill.
+            fRowsDecoded = scaledHeight;
+        } else {
+            // This will be updated by haveDecodedRow.
+            fRowsDecoded = 0;
+        }
+    }
+
+    if (!fCurrColorTableIsReal) {
+        // Nothing to draw this frame.
+        return kSuccess;
+    }
+
+    bool frameDecoded = false;
+    const bool fatalError = !fReader->decode(frameIndex, &frameDecoded);
+    if (fatalError || !frameDecoded || fRowsDecoded != scaledHeight) {
+        if (rowsDecoded) {
+            *rowsDecoded = fRowsDecoded;
+        }
+        if (fatalError) {
+            return kErrorInInput;
+        }
+        return kIncompleteInput;
+    }
+
+    return kSuccess;
+}
+
+void SkLibGifCodec::applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const {
+    if (this->xformOnDecode()) {
+        SkASSERT(this->colorXform());
+        fSwizzler->swizzle(fXformBuffer.get(), src);
+
+        const int xformWidth = get_scaled_dimension(dstInfo.width(), fSwizzler->sampleX());
+        this->applyColorXform(dst, fXformBuffer.get(), xformWidth);
+    } else {
+        fSwizzler->swizzle(dst, src);
+    }
+}
+
+template <typename T>
+static void blend_line(void* dstAsVoid, const void* srcAsVoid, int width) {
+    T*       dst = reinterpret_cast<T*>(dstAsVoid);
+    const T* src = reinterpret_cast<const T*>(srcAsVoid);
+    while (width --> 0) {
+        if (*src != 0) {   // GIF pixels are either transparent (== 0) or opaque (!= 0).
+            *dst = *src;
+        }
+        src++;
+        dst++;
+    }
+}
+
+void SkLibGifCodec::haveDecodedRow(int frameIndex, const unsigned char* rowBegin,
+                                int rowNumber, int repeatCount, bool writeTransparentPixels)
+{
+    const SkGIFFrameContext* frameContext = fReader->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 width = frameContext->width();
+    const int xBegin = frameContext->xOffset();
+    const int yBegin = frameContext->yOffset() + rowNumber;
+    const int xEnd = std::min(xBegin + width, this->dimensions().width());
+    const int yEnd = std::min(yBegin + rowNumber + repeatCount, this->dimensions().height());
+    // FIXME: No need to make the checks on width/xBegin/xEnd for every row. We could instead do
+    // this once in prepareToDecode.
+    if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin))
+        return;
+
+    // yBegin is the first row in the non-sampled image. dstRow will be the row in the output,
+    // after potentially scaling it.
+    int dstRow = yBegin;
+
+    const int sampleY = fSwizzler->sampleY();
+    if (sampleY > 1) {
+        // Check to see whether this row or one that falls in the repeatCount is needed in the
+        // output.
+        bool foundNecessaryRow = false;
+        for (int i = 0; i < repeatCount; i++) {
+            const int potentialRow = yBegin + i;
+            if (fSwizzler->rowNeeded(potentialRow)) {
+                dstRow = potentialRow / sampleY;
+                const int scaledHeight = get_scaled_dimension(this->dstInfo().height(), sampleY);
+                if (dstRow >= scaledHeight) {
+                    return;
+                }
+
+                foundNecessaryRow = true;
+                repeatCount -= i;
+
+                repeatCount = (repeatCount - 1) / sampleY + 1;
+
+                // Make sure the repeatCount does not take us beyond the end of the dst
+                if (dstRow + repeatCount > scaledHeight) {
+                    repeatCount = scaledHeight - dstRow;
+                    SkASSERT(repeatCount >= 1);
+                }
+                break;
+            }
+        }
+
+        if (!foundNecessaryRow) {
+            return;
+        }
+    } else {
+        // Make sure the repeatCount does not take us beyond the end of the dst
+        SkASSERT(this->dstInfo().height() >= yBegin);
+        repeatCount = SkTMin(repeatCount, this->dstInfo().height() - yBegin);
+    }
+
+    if (!fFilledBackground) {
+        // At this point, we are definitely going to write the row, so count it towards the number
+        // of rows decoded.
+        // We do not consider the repeatCount, which only happens for interlaced, in which case we
+        // have already set fRowsDecoded to the proper value (reflecting that we have filled the
+        // background).
+        fRowsDecoded++;
+    }
+
+    // decodeFrame will early exit if this is false, so this method will not be
+    // called.
+    SkASSERT(fCurrColorTableIsReal);
+
+    // The swizzler takes care of offsetting into the dst width-wise.
+    void* dstLine = SkTAddOffset<void>(fDst, dstRow * fDstRowBytes);
+
+    // We may or may not need to write transparent pixels to the buffer.
+    // If we're compositing against a previous image, it's wrong, but if
+    // we're decoding an interlaced gif and displaying it "Haeberli"-style,
+    // we must write these for passes beyond the first, or the initial passes
+    // will "show through" the later ones.
+    const auto dstInfo = this->dstInfo();
+    if (writeTransparentPixels) {
+        this->applyXformRow(dstInfo, dstLine, rowBegin);
+    } else {
+        this->applyXformRow(dstInfo, fTmpBuffer.get(), rowBegin);
+
+        size_t offsetBytes = fSwizzler->swizzleOffsetBytes();
+        if (dstInfo.colorType() == kRGBA_F16_SkColorType) {
+            // Account for the fact that post-swizzling we converted to F16,
+            // which is twice as wide.
+            offsetBytes *= 2;
+        }
+        const void* src = SkTAddOffset<void>(fTmpBuffer.get(), offsetBytes);
+        void*       dst = SkTAddOffset<void>(dstLine, offsetBytes);
+
+        switch (dstInfo.colorType()) {
+            case kBGRA_8888_SkColorType:
+            case kRGBA_8888_SkColorType:
+                blend_line<uint32_t>(dst, src, fSwizzler->swizzleWidth());
+                break;
+            case kRGBA_F16_SkColorType:
+                blend_line<uint64_t>(dst, src, fSwizzler->swizzleWidth());
+                break;
+            default:
+                SkASSERT(false);
+                return;
+        }
+    }
+
+    // Tell the frame to copy the row data if need be.
+    if (repeatCount > 1) {
+        const size_t bytesPerPixel = this->dstInfo().bytesPerPixel();
+        const size_t bytesToCopy = fSwizzler->swizzleWidth() * bytesPerPixel;
+        void* copiedLine = SkTAddOffset<void>(dstLine, fSwizzler->swizzleOffsetBytes());
+        void* dst = copiedLine;
+        for (int i = 1; i < repeatCount; i++) {
+            dst = SkTAddOffset<void>(dst, fDstRowBytes);
+            memcpy(dst, copiedLine, bytesToCopy);
+        }
+    }
+}
diff --git a/SkLibGifCodec.h b/SkLibGifCodec.h
new file mode 100644
index 0000000..014df5f
--- /dev/null
+++ b/SkLibGifCodec.h
@@ -0,0 +1,153 @@
+// Copyright 2015 Google Inc.
+// Use of this source code is governed by the BSD-3-Clause license that can be
+// found in the LICENSE.md file.
+#ifndef SkLibGifCodec_DEFINED
+#define SkLibGifCodec_DEFINED
+
+#include "SkGifImageReader.h"
+
+#include "include/codec/SkCodec.h"
+#include "include/codec/SkCodecAnimation.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkImageInfo.h"
+#include "src/codec/SkColorTable.h"
+#include "src/codec/SkSwizzler.h"
+
+/*
+ *
+ * This class implements the decoding for gif images
+ *
+ */
+class SkLibGifCodec : public SkCodec {
+public:
+    static bool IsGif(const void*, size_t);
+
+    /*
+     * Assumes IsGif was called and returned true
+     * Reads enough of the stream to determine the image format
+     */
+    static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*);
+
+    // Callback for SkGifImageReader when a row is available.
+    void haveDecodedRow(int frameIndex, const unsigned char* rowBegin,
+                        int rowNumber, int repeatCount, bool writeTransparentPixels);
+    /*
+     * Creates an instance of the decoder
+     * Called only by NewFromStream
+     * Takes ownership of the SkGifImageReader
+     */
+    SkLibGifCodec(SkEncodedInfo&&, SkGifImageReader*);
+
+protected:
+    /*
+     * Performs the full gif decode
+     */
+    Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&,
+            int*) override;
+
+    SkEncodedImageFormat onGetEncodedFormat() const override {
+        return SkEncodedImageFormat::kGIF;
+    }
+
+    bool onRewind() override;
+
+    int onGetFrameCount() override;
+    bool onGetFrameInfo(int, FrameInfo*) const override;
+    int onGetRepetitionCount() override;
+
+    Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t,
+            const SkCodec::Options&) override;
+
+    Result onIncrementalDecode(int*) override;
+
+    const SkFrameHolder* getFrameHolder() const override {
+        return fReader.get();
+    }
+
+private:
+
+    /*
+     * Initializes the color table that we will use for decoding.
+     *
+     * @param dstInfo         Contains the requested dst color type.
+     * @param frameIndex      Frame whose color table to use.
+     */
+    void initializeColorTable(const SkImageInfo& dstInfo, int frameIndex);
+
+   /*
+    * Does necessary setup, including setting up the color table and swizzler.
+    */
+    Result prepareToDecode(const SkImageInfo& dstInfo, const Options& opts);
+
+    /*
+     * Initializes the swizzler.
+     *
+     * @param dstInfo    Output image information.  Dimensions may have been
+     *                   adjusted if the image frame size does not match the size
+     *                   indicated in the header.
+     * @param frameIndex Which frame we are decoding. This determines the frameRect
+     *                   to use.
+     */
+    void initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex);
+
+    SkSampler* getSampler(bool createIfNecessary) override {
+        SkASSERT(fSwizzler);
+        return fSwizzler.get();
+    }
+
+    /*
+     * Recursive function to decode a frame.
+     *
+     * @param firstAttempt Whether this is the first call to decodeFrame since
+     *                     starting. e.g. true in onGetPixels, and true in the
+     *                     first call to onIncrementalDecode after calling
+     *                     onStartIncrementalDecode.
+     *                     When true, this method may have to initialize the
+     *                     frame, for example by filling or decoding the prior
+     *                     frame.
+     * @param opts         Options for decoding. May be different from
+     *                     this->options() for decoding prior frames. Specifies
+     *                     the frame to decode and whether the prior frame has
+     *                     already been decoded to fDst. If not, and the frame
+     *                     is not independent, this method will recursively
+     *                     decode the frame it depends on.
+     * @param rowsDecoded  Out-parameter to report the total number of rows
+     *                     that have been decoded (or at least written to, if
+     *                     it had to fill), including rows decoded by prior
+     *                     calls to onIncrementalDecode.
+     * @return             kSuccess if the frame is complete, kIncompleteInput
+     *                     otherwise.
+     */
+    Result decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded);
+
+    /*
+     *  Swizzles and color xforms (if necessary) into dst.
+     */
+    void applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const;
+
+    std::unique_ptr<SkGifImageReader>   fReader;
+    std::unique_ptr<uint8_t[]>          fTmpBuffer;
+    std::unique_ptr<SkSwizzler>         fSwizzler;
+    sk_sp<SkColorTable>                 fCurrColorTable;
+    // We may create a dummy table if there is not a Map in the input data. In
+    // that case, we set this value to false, and we can skip a lot of decoding
+    // work (which would not be meaningful anyway). We create a "fake"/"dummy"
+    // one in that case, so the client and the swizzler have something to draw.
+    bool                                fCurrColorTableIsReal;
+    // Whether the background was filled.
+    bool                                fFilledBackground;
+    // True on the first call to onIncrementalDecode. This value is passed to
+    // decodeFrame.
+    bool                                fFirstCallToIncrementalDecode;
+
+    void*                               fDst;
+    size_t                              fDstRowBytes;
+
+    // Updated inside haveDecodedRow when rows are decoded, unless we filled
+    // the background, in which case it is set once and left alone.
+    int                                 fRowsDecoded;
+    std::unique_ptr<uint32_t[]>         fXformBuffer;
+
+    typedef SkCodec INHERITED;
+};
+#endif  // SkLibGifCodec_DEFINED
diff --git a/libgifcodec.gni b/libgifcodec.gni
new file mode 100644
index 0000000..aa94603
--- /dev/null
+++ b/libgifcodec.gni
@@ -0,0 +1,14 @@
+# Copyright 2019 Google LLC.
+# Use of this source code is governed by the BSD-3-Clause license that can be
+# found in the LICENSE.md file.
+
+libgifcodec_sources = [
+  "SkGifImageReader.cpp",
+  "SkGifImageReader.h",
+  "SkLibGifCodec.cpp",
+  "SkLibGifCodec.h",
+]
+
+libgifcodec_public = [
+  "SkGifCodec.h",
+]