summaryrefslogtreecommitdiff
path: root/chromium/third_party/libgifcodec
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-03-11 11:32:04 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-03-18 13:40:17 +0000
commit31ccca0778db85c159634478b4ec7997f6704860 (patch)
tree3d33fc3afd9d5ec95541e1bbe074a9cf8da12a0e /chromium/third_party/libgifcodec
parent248b70b82a40964d5594eb04feca0fa36716185d (diff)
downloadqtwebengine-chromium-31ccca0778db85c159634478b4ec7997f6704860.tar.gz
BASELINE: Update Chromium to 80.0.3987.136
Change-Id: I98e1649aafae85ba3a83e67af00bb27ef301db7b Reviewed-by: Jüri Valdmann <juri.valdmann@qt.io>
Diffstat (limited to 'chromium/third_party/libgifcodec')
-rw-r--r--chromium/third_party/libgifcodec/LICENSE.md109
-rw-r--r--chromium/third_party/libgifcodec/README.chromium9
-rw-r--r--chromium/third_party/libgifcodec/README.md16
-rw-r--r--chromium/third_party/libgifcodec/SkGifCodec.h20
-rw-r--r--chromium/third_party/libgifcodec/SkGifImageReader.cpp958
-rw-r--r--chromium/third_party/libgifcodec/SkGifImageReader.h398
-rw-r--r--chromium/third_party/libgifcodec/SkLibGifCodec.cpp532
-rw-r--r--chromium/third_party/libgifcodec/SkLibGifCodec.h153
-rw-r--r--chromium/third_party/libgifcodec/libgifcodec.gni14
9 files changed, 2209 insertions, 0 deletions
diff --git a/chromium/third_party/libgifcodec/LICENSE.md b/chromium/third_party/libgifcodec/LICENSE.md
new file mode 100644
index 00000000000..d1f576354a5
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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 2019 Google LLC. 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 the copyright holder 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/chromium/third_party/libgifcodec/README.chromium b/chromium/third_party/libgifcodec/README.chromium
new file mode 100644
index 00000000000..1ee6a0d8365
--- /dev/null
+++ b/chromium/third_party/libgifcodec/README.chromium
@@ -0,0 +1,9 @@
+Name: libgif codec for Skia
+Short Name: libgifcodec
+URL: https://skia.googlesource.com/libgifcodec/
+Version: unknown
+License: MPL-1.1/GPL-2.0/LGPL-2.1 + BSD-3-Clause + BSD-2-Clause
+License File: LICENSE.md
+Security Critical: yes
+Description: See file README.md
+Local Modifications: None
diff --git a/chromium/third_party/libgifcodec/README.md b/chromium/third_party/libgifcodec/README.md
new file mode 100644
index 00000000000..0acefc6065b
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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/chromium/third_party/libgifcodec/SkGifCodec.h b/chromium/third_party/libgifcodec/SkGifCodec.h
new file mode 100644
index 00000000000..532e7b1aecd
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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/chromium/third_party/libgifcodec/SkGifImageReader.cpp b/chromium/third_party/libgifcodec/SkGifImageReader.cpp
new file mode 100644
index 00000000000..c5900b311a2
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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_ENTRIES + 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/chromium/third_party/libgifcodec/SkGifImageReader.h b/chromium/third_party/libgifcodec/SkGifImageReader.h
new file mode 100644
index 00000000000..9b0225594b0
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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/chromium/third_party/libgifcodec/SkLibGifCodec.cpp b/chromium/third_party/libgifcodec/SkLibGifCodec.cpp
new file mode 100644
index 00000000000..d4f8790aecc
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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/chromium/third_party/libgifcodec/SkLibGifCodec.h b/chromium/third_party/libgifcodec/SkLibGifCodec.h
new file mode 100644
index 00000000000..014df5f7b5d
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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/chromium/third_party/libgifcodec/libgifcodec.gni b/chromium/third_party/libgifcodec/libgifcodec.gni
new file mode 100644
index 00000000000..aa94603b8e6
--- /dev/null
+++ b/chromium/third_party/libgifcodec/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",
+]