diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-03-11 11:32:04 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-03-18 13:40:17 +0000 |
commit | 31ccca0778db85c159634478b4ec7997f6704860 (patch) | |
tree | 3d33fc3afd9d5ec95541e1bbe074a9cf8da12a0e /chromium/third_party/libgifcodec | |
parent | 248b70b82a40964d5594eb04feca0fa36716185d (diff) | |
download | qtwebengine-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.md | 109 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/README.chromium | 9 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/README.md | 16 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/SkGifCodec.h | 20 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/SkGifImageReader.cpp | 958 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/SkGifImageReader.h | 398 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/SkLibGifCodec.cpp | 532 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/SkLibGifCodec.h | 153 | ||||
-rw-r--r-- | chromium/third_party/libgifcodec/libgifcodec.gni | 14 |
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", +] |