Replace third_party/gif with new DEPS entry.
- BUILD.gn `gif` optional source_set: use new skia_libgifcodec_path code, not third_party/gif code. - SkCodec.cpp: use third_party version of SkGifCodec.h. - rm src/codec/SkGifCodec.* third_party/gif/* Original CL: https://review.skia.org/254582 Cq-Include-Trybots: luci.skia.skia.primary:Build-Debian9-Clang-x86-devrel-Android_SKQP Bug: skia:9654 Change-Id: Ia0379af70a5ec5656a7d123b8b6816b9fa24eaee Reviewed-on: https://skia-review.googlesource.com/c/skia/+/255780 Reviewed-by: Hal Canary <halcanary@google.com> Commit-Queue: Hal Canary <halcanary@google.com>
This commit is contained in:
parent
19cb22e3d4
commit
1ec9a28a28
21
BUILD.gn
21
BUILD.gn
@ -612,11 +612,22 @@ optional("gpu") {
|
||||
|
||||
optional("gif") {
|
||||
enabled = !skia_use_wuffs && skia_use_libgifcodec
|
||||
public_defines = [ "SK_USE_LIBGIFCODEC" ]
|
||||
sources = [
|
||||
"src/codec/SkGifCodec.cpp",
|
||||
"third_party/gif/SkGifImageReader.cpp",
|
||||
]
|
||||
_libgifcodec_gni_path = "third_party/externals/libgifcodec/libgifcodec.gni"
|
||||
if ("True" ==
|
||||
exec_script("gn/checkpath.py",
|
||||
[ rebase_path(_libgifcodec_gni_path, root_build_dir) ],
|
||||
"trim string")) {
|
||||
public_defines = [ "SK_USE_LIBGIFCODEC" ]
|
||||
public_include_dirs = [
|
||||
".",
|
||||
skia_libgifcodec_path,
|
||||
]
|
||||
include_dirs = public_include_dirs
|
||||
import(_libgifcodec_gni_path)
|
||||
sources = rebase_path(libgifcodec_sources + libgifcodec_public,
|
||||
".",
|
||||
skia_libgifcodec_path)
|
||||
}
|
||||
}
|
||||
|
||||
optional("heif") {
|
||||
|
@ -27,7 +27,7 @@
|
||||
#ifdef SK_HAS_WUFFS_LIBRARY
|
||||
#include "src/codec/SkWuffsCodec.h"
|
||||
#elif defined(SK_USE_LIBGIFCODEC)
|
||||
#include "src/codec/SkGifCodec.h"
|
||||
#include "SkGifCodec.h"
|
||||
#endif
|
||||
|
||||
struct DecoderProc {
|
||||
|
@ -1,533 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE 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 "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/SkGifCodec.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,
|
||||
Result* result) {
|
||||
std::unique_ptr<SkGifImageReader> reader(new SkGifImageReader(std::move(stream)));
|
||||
*result = reader->parse(SkGifImageReader::SkGIFSizeQuery);
|
||||
if (*result != 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 = 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 SkGifCodec(std::move(encodedInfo), reader.release()));
|
||||
}
|
||||
|
||||
bool SkGifCodec::onRewind() {
|
||||
fReader->clearDecodeState();
|
||||
return true;
|
||||
}
|
||||
|
||||
SkGifCodec::SkGifCodec(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 SkGifCodec::onGetFrameCount() {
|
||||
fReader->parse(SkGifImageReader::SkGIFFrameCountQuery);
|
||||
return fReader->imagesCount();
|
||||
}
|
||||
|
||||
bool SkGifCodec::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 SkGifCodec::onGetRepetitionCount() {
|
||||
fReader->parse(SkGifImageReader::SkGIFLoopCountQuery);
|
||||
return fReader->loopCount();
|
||||
}
|
||||
|
||||
static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType;
|
||||
|
||||
void SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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 SkGifCodec::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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
#ifndef SkGifCodec_DEFINED
|
||||
#define SkGifCodec_DEFINED
|
||||
|
||||
#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"
|
||||
|
||||
#include "third_party/gif/SkGifImageReader.h"
|
||||
|
||||
/*
|
||||
*
|
||||
* This class implements the decoding for gif images
|
||||
*
|
||||
*/
|
||||
class SkGifCodec : 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);
|
||||
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;
|
||||
|
||||
/*
|
||||
* Creates an instance of the decoder
|
||||
* Called only by NewFromStream
|
||||
* Takes ownership of the SkGifImageReader
|
||||
*/
|
||||
SkGifCodec(SkEncodedInfo&&, SkGifImageReader*);
|
||||
|
||||
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 // SkGifCodec_DEFINED
|
37
third_party/gif/LICENSE
vendored
37
third_party/gif/LICENSE
vendored
@ -1,37 +0,0 @@
|
||||
/* ***** 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 ***** */
|
958
third_party/gif/SkGifImageReader.cpp
vendored
958
third_party/gif/SkGifImageReader.cpp
vendored
@ -1,958 +0,0 @@
|
||||
/* -*- 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 "include/core/SkColorPriv.h"
|
||||
#include "src/codec/SkGifCodec.h"
|
||||
#include "third_party/gif/SkGifImageReader.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, SkGifCodec* client,
|
||||
bool* frameComplete)
|
||||
{
|
||||
*frameComplete = false;
|
||||
if (!m_lzwContext) {
|
||||
// Wait for more data to properly initialize SkGIFLZWContext.
|
||||
if (!isDataSizeDefined() || !isHeaderDefined())
|
||||
return true;
|
||||
|
||||
m_lzwContext.reset(new SkGIFLZWContext(client, this));
|
||||
if (!m_lzwContext->prepareToDecode()) {
|
||||
m_lzwContext.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_currentLzwBlock = 0;
|
||||
}
|
||||
|
||||
// Some bad GIFs have extra blocks beyond the last row, which we don't want to decode.
|
||||
while (m_currentLzwBlock < m_lzwBlocks.count() && m_lzwContext->hasRemainingRows()) {
|
||||
const auto& block = m_lzwBlocks[m_currentLzwBlock];
|
||||
const size_t len = block.blockSize;
|
||||
|
||||
sk_sp<SkData> data(streamBuffer->getDataAtPosition(block.blockPosition, len));
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
if (!m_lzwContext->doLZW(reinterpret_cast<const unsigned char*>(data->data()), len)) {
|
||||
return false;
|
||||
}
|
||||
++m_currentLzwBlock;
|
||||
}
|
||||
|
||||
// If this frame is data complete then the previous loop must have completely decoded all LZW blocks.
|
||||
// There will be no more decoding for this frame so it's time to cleanup.
|
||||
if (isComplete()) {
|
||||
*frameComplete = true;
|
||||
m_lzwContext.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decode a frame.
|
||||
// This method uses SkGIFFrameContext:decode() to decode the frame; decoding error is reported to client as a critical failure.
|
||||
// Return true if decoding has progressed. Return false if an error has occurred.
|
||||
bool SkGifImageReader::decode(int frameIndex, bool* frameComplete)
|
||||
{
|
||||
SkGIFFrameContext* currentFrame = m_frames[frameIndex].get();
|
||||
|
||||
return currentFrame->decode(&m_streamBuffer, m_client, frameComplete);
|
||||
}
|
||||
|
||||
// Parse incoming GIF data stream into internal data structures.
|
||||
SkCodec::Result SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
|
||||
{
|
||||
if (m_parseCompleted) {
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
|
||||
if (SkGIFLoopCountQuery == query && m_loopCount != cLoopCountNotSeen) {
|
||||
// Loop count has already been parsed.
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
|
||||
// SkGIFSizeQuery and SkGIFFrameCountQuery are negative, so this is only meaningful when >= 0.
|
||||
const int lastFrameToParse = (int) query;
|
||||
if (lastFrameToParse >= 0 && m_frames.count() > lastFrameToParse
|
||||
&& m_frames[lastFrameToParse]->isComplete()) {
|
||||
// We have already parsed this frame.
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (!m_streamBuffer.buffer(m_bytesToConsume)) {
|
||||
// The stream does not yet have enough data.
|
||||
return SkCodec::kIncompleteInput;
|
||||
}
|
||||
|
||||
switch (m_state) {
|
||||
case SkGIFLZW: {
|
||||
SkASSERT(!m_frames.empty());
|
||||
auto* frame = m_frames.back().get();
|
||||
frame->addLzwBlock(m_streamBuffer.markPosition(), m_bytesToConsume);
|
||||
GETN(1, SkGIFSubBlock);
|
||||
break;
|
||||
}
|
||||
case SkGIFLZWStart: {
|
||||
SkASSERT(!m_frames.empty());
|
||||
auto* currentFrame = m_frames.back().get();
|
||||
|
||||
currentFrame->setDataSize(this->getOneByte());
|
||||
GETN(1, SkGIFSubBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFType: {
|
||||
const char* currentComponent = m_streamBuffer.get();
|
||||
|
||||
// All GIF files begin with "GIF87a" or "GIF89a".
|
||||
if (!memcmp(currentComponent, "GIF89a", 6))
|
||||
m_version = 89;
|
||||
else if (!memcmp(currentComponent, "GIF87a", 6))
|
||||
m_version = 87;
|
||||
else {
|
||||
// This prevents attempting to continue reading this invalid stream.
|
||||
GETN(0, SkGIFDone);
|
||||
return SkCodec::kInvalidInput;
|
||||
}
|
||||
GETN(7, SkGIFGlobalHeader);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFGlobalHeader: {
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
// This is the height and width of the "screen" or frame into which
|
||||
// images are rendered. The individual images can be smaller than
|
||||
// the screen size and located with an origin anywhere within the
|
||||
// screen.
|
||||
// Note that we don't inform the client of the size yet, as it might
|
||||
// change after we read the first frame's image header.
|
||||
fScreenWidth = GETINT16(currentComponent);
|
||||
fScreenHeight = GETINT16(currentComponent + 2);
|
||||
|
||||
const int globalColorMapColors = 2 << (currentComponent[4] & 0x07);
|
||||
|
||||
if ((currentComponent[4] & 0x80) && globalColorMapColors > 0) { /* global map */
|
||||
m_globalColorMap.setNumColors(globalColorMapColors);
|
||||
GETN(SK_BYTES_PER_COLORMAP_ENTRY * globalColorMapColors, SkGIFGlobalColormap);
|
||||
break;
|
||||
}
|
||||
|
||||
GETN(1, SkGIFImageStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFGlobalColormap: {
|
||||
m_globalColorMap.setTablePosition(m_streamBuffer.markPosition());
|
||||
GETN(1, SkGIFImageStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFImageStart: {
|
||||
const char currentComponent = m_streamBuffer.get()[0];
|
||||
|
||||
if (currentComponent == '!') { // extension.
|
||||
GETN(2, SkGIFExtension);
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentComponent == ',') { // image separator.
|
||||
GETN(9, SkGIFImageHeader);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we get anything other than ',' (image separator), '!'
|
||||
// (extension), or ';' (trailer), there is extraneous data
|
||||
// between blocks. The GIF87a spec tells us to keep reading
|
||||
// until we find an image separator, but GIF89a says such
|
||||
// a file is corrupt. We follow Mozilla's implementation and
|
||||
// proceed as if the file were correctly terminated, so the
|
||||
// GIF will display.
|
||||
GETN(0, SkGIFDone);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFExtension: {
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
size_t bytesInBlock = currentComponent[1];
|
||||
SkGIFState exceptionState = SkGIFSkipBlock;
|
||||
|
||||
switch (*currentComponent) {
|
||||
case 0xf9:
|
||||
// The GIF spec mandates that the GIFControlExtension header block length is 4 bytes,
|
||||
exceptionState = SkGIFControlExtension;
|
||||
// and the parser for this block reads 4 bytes, so we must enforce that the buffer
|
||||
// contains at least this many bytes. If the GIF specifies a different length, we
|
||||
// allow that, so long as it's larger; the additional data will simply be ignored.
|
||||
bytesInBlock = std::max(bytesInBlock, static_cast<size_t>(4));
|
||||
break;
|
||||
|
||||
// The GIF spec also specifies the lengths of the following two extensions' headers
|
||||
// (as 12 and 11 bytes, respectively). Because we ignore the plain text extension entirely
|
||||
// and sanity-check the actual length of the application extension header before reading it,
|
||||
// we allow GIFs to deviate from these values in either direction. This is important for
|
||||
// real-world compatibility, as GIFs in the wild exist with application extension headers
|
||||
// that are both shorter and longer than 11 bytes.
|
||||
case 0x01:
|
||||
// ignoring plain text extension
|
||||
break;
|
||||
|
||||
case 0xff:
|
||||
exceptionState = SkGIFApplicationExtension;
|
||||
break;
|
||||
|
||||
case 0xfe:
|
||||
exceptionState = SkGIFConsumeComment;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytesInBlock)
|
||||
GETN(bytesInBlock, exceptionState);
|
||||
else
|
||||
GETN(1, SkGIFImageStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFConsumeBlock: {
|
||||
const unsigned char currentComponent = this->getOneByte();
|
||||
if (!currentComponent)
|
||||
GETN(1, SkGIFImageStart);
|
||||
else
|
||||
GETN(currentComponent, SkGIFSkipBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFSkipBlock: {
|
||||
GETN(1, SkGIFConsumeBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFControlExtension: {
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
addFrameIfNecessary();
|
||||
SkGIFFrameContext* currentFrame = m_frames.back().get();
|
||||
if (*currentComponent & 0x1)
|
||||
currentFrame->setTransparentPixel(currentComponent[3]);
|
||||
|
||||
// We ignore the "user input" bit.
|
||||
|
||||
// NOTE: This relies on the values in the FrameDisposalMethod enum
|
||||
// matching those in the GIF spec!
|
||||
int rawDisposalMethod = ((*currentComponent) >> 2) & 0x7;
|
||||
switch (rawDisposalMethod) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
currentFrame->setDisposalMethod((SkCodecAnimation::DisposalMethod) rawDisposalMethod);
|
||||
break;
|
||||
case 4:
|
||||
// Some specs say that disposal method 3 is "overwrite previous", others that setting
|
||||
// the third bit of the field (i.e. method 4) is. We map both to the same value.
|
||||
currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kRestorePrevious);
|
||||
break;
|
||||
default:
|
||||
// Other values use the default.
|
||||
currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep);
|
||||
break;
|
||||
}
|
||||
currentFrame->setDuration(GETINT16(currentComponent + 1) * 10);
|
||||
GETN(1, SkGIFConsumeBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFCommentExtension: {
|
||||
const unsigned char currentComponent = this->getOneByte();
|
||||
if (currentComponent)
|
||||
GETN(currentComponent, SkGIFConsumeComment);
|
||||
else
|
||||
GETN(1, SkGIFImageStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFConsumeComment: {
|
||||
GETN(1, SkGIFCommentExtension);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFApplicationExtension: {
|
||||
// Check for netscape application extension.
|
||||
if (m_bytesToConsume == 11) {
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
if (!memcmp(currentComponent, "NETSCAPE2.0", 11) || !memcmp(currentComponent, "ANIMEXTS1.0", 11))
|
||||
GETN(1, SkGIFNetscapeExtensionBlock);
|
||||
}
|
||||
|
||||
if (m_state != SkGIFNetscapeExtensionBlock)
|
||||
GETN(1, SkGIFConsumeBlock);
|
||||
break;
|
||||
}
|
||||
|
||||
// Netscape-specific GIF extension: animation looping.
|
||||
case SkGIFNetscapeExtensionBlock: {
|
||||
const int currentComponent = this->getOneByte();
|
||||
// SkGIFConsumeNetscapeExtension always reads 3 bytes from the stream; we should at least wait for this amount.
|
||||
if (currentComponent)
|
||||
GETN(std::max(3, currentComponent), SkGIFConsumeNetscapeExtension);
|
||||
else
|
||||
GETN(1, SkGIFImageStart);
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse netscape-specific application extensions
|
||||
case SkGIFConsumeNetscapeExtension: {
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
int netscapeExtension = currentComponent[0] & 7;
|
||||
|
||||
// Loop entire animation specified # of times. Only read the loop count during the first iteration.
|
||||
if (netscapeExtension == 1) {
|
||||
m_loopCount = GETINT16(currentComponent + 1);
|
||||
|
||||
// Zero loop count is infinite animation loop request.
|
||||
if (!m_loopCount)
|
||||
m_loopCount = SkCodec::kRepetitionCountInfinite;
|
||||
|
||||
GETN(1, SkGIFNetscapeExtensionBlock);
|
||||
|
||||
if (SkGIFLoopCountQuery == query) {
|
||||
m_streamBuffer.flush();
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
} else if (netscapeExtension == 2) {
|
||||
// Wait for specified # of bytes to enter buffer.
|
||||
|
||||
// Don't do this, this extension doesn't exist (isn't used at all)
|
||||
// and doesn't do anything, as our streaming/buffering takes care of it all...
|
||||
// See: http://semmix.pl/color/exgraf/eeg24.htm
|
||||
GETN(1, SkGIFNetscapeExtensionBlock);
|
||||
} else {
|
||||
// 0,3-7 are yet to be defined netscape extension codes
|
||||
// This prevents attempting to continue reading this invalid stream.
|
||||
GETN(0, SkGIFDone);
|
||||
return SkCodec::kInvalidInput;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFImageHeader: {
|
||||
int height, width, xOffset, yOffset;
|
||||
const unsigned char* currentComponent =
|
||||
reinterpret_cast<const unsigned char*>(m_streamBuffer.get());
|
||||
|
||||
/* Get image offsets, with respect to the screen origin */
|
||||
xOffset = GETINT16(currentComponent);
|
||||
yOffset = GETINT16(currentComponent + 2);
|
||||
|
||||
/* Get image width and height. */
|
||||
width = GETINT16(currentComponent + 4);
|
||||
height = GETINT16(currentComponent + 6);
|
||||
|
||||
// Some GIF files have frames that don't fit in the specified
|
||||
// overall image size. For the first frame, we can simply enlarge
|
||||
// the image size to allow the frame to be visible. We can't do
|
||||
// this on subsequent frames because the rest of the decoding
|
||||
// infrastructure assumes the image size won't change as we
|
||||
// continue decoding, so any subsequent frames that are even
|
||||
// larger will be cropped.
|
||||
// Luckily, handling just the first frame is sufficient to deal
|
||||
// with most cases, e.g. ones where the image size is erroneously
|
||||
// set to zero, since usually the first frame completely fills
|
||||
// the image.
|
||||
if (currentFrameIsFirstFrame()) {
|
||||
fScreenHeight = std::max(fScreenHeight, yOffset + height);
|
||||
fScreenWidth = std::max(fScreenWidth, xOffset + width);
|
||||
}
|
||||
|
||||
// NOTE: Chromium placed this block after setHeaderDefined, down
|
||||
// below we returned true when asked for the size. So Chromium
|
||||
// created an image which would fail. Is this the correct behavior?
|
||||
// We choose to return false early, so we will not create an
|
||||
// SkCodec.
|
||||
|
||||
// Work around more broken GIF files that have zero image width or
|
||||
// height.
|
||||
if (!height || !width) {
|
||||
height = fScreenHeight;
|
||||
width = fScreenWidth;
|
||||
if (!height || !width) {
|
||||
// This prevents attempting to continue reading this invalid stream.
|
||||
GETN(0, SkGIFDone);
|
||||
return SkCodec::kInvalidInput;
|
||||
}
|
||||
}
|
||||
|
||||
const bool isLocalColormapDefined = SkToBool(currentComponent[8] & 0x80);
|
||||
// The three low-order bits of currentComponent[8] specify the bits per pixel.
|
||||
const int numColors = 2 << (currentComponent[8] & 0x7);
|
||||
if (currentFrameIsFirstFrame()) {
|
||||
const int transPix = m_frames.empty() ? SkGIFColorMap::kNotFound
|
||||
: m_frames[0]->transparentPixel();
|
||||
if (is_palette_index_valid(transPix)) {
|
||||
m_firstFrameHasAlpha = true;
|
||||
} else {
|
||||
const bool frameIsSubset = xOffset > 0 || yOffset > 0
|
||||
|| width < fScreenWidth
|
||||
|| height < fScreenHeight;
|
||||
m_firstFrameHasAlpha = frameIsSubset;
|
||||
}
|
||||
}
|
||||
|
||||
addFrameIfNecessary();
|
||||
SkGIFFrameContext* currentFrame = m_frames.back().get();
|
||||
currentFrame->setHeaderDefined();
|
||||
|
||||
if (query == SkGIFSizeQuery) {
|
||||
// The decoder needs to stop, so we return here, before
|
||||
// flushing the buffer. Next time through, we'll be in the same
|
||||
// state, requiring the same amount in the buffer.
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
|
||||
|
||||
currentFrame->setXYWH(xOffset, yOffset, width, height);
|
||||
currentFrame->setInterlaced(SkToBool(currentComponent[8] & 0x40));
|
||||
|
||||
// Overlaying interlaced, transparent GIFs over
|
||||
// existing image data using the Haeberli display hack
|
||||
// requires saving the underlying image in order to
|
||||
// avoid jaggies at the transparency edges. We are
|
||||
// unprepared to deal with that, so don't display such
|
||||
// images progressively. Which means only the first
|
||||
// frame can be progressively displayed.
|
||||
// FIXME: It is possible that a non-transparent frame
|
||||
// can be interlaced and progressively displayed.
|
||||
currentFrame->setProgressiveDisplay(currentFrameIsFirstFrame());
|
||||
|
||||
if (isLocalColormapDefined) {
|
||||
currentFrame->localColorMap().setNumColors(numColors);
|
||||
GETN(SK_BYTES_PER_COLORMAP_ENTRY * numColors, SkGIFImageColormap);
|
||||
break;
|
||||
}
|
||||
|
||||
setAlphaAndRequiredFrame(currentFrame);
|
||||
GETN(1, SkGIFLZWStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFImageColormap: {
|
||||
SkASSERT(!m_frames.empty());
|
||||
auto* currentFrame = m_frames.back().get();
|
||||
auto& cmap = currentFrame->localColorMap();
|
||||
cmap.setTablePosition(m_streamBuffer.markPosition());
|
||||
setAlphaAndRequiredFrame(currentFrame);
|
||||
GETN(1, SkGIFLZWStart);
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFSubBlock: {
|
||||
const size_t bytesInBlock = this->getOneByte();
|
||||
if (bytesInBlock)
|
||||
GETN(bytesInBlock, SkGIFLZW);
|
||||
else {
|
||||
// Finished parsing one frame; Process next frame.
|
||||
SkASSERT(!m_frames.empty());
|
||||
// Note that some broken GIF files do not have enough LZW blocks to fully
|
||||
// decode all rows but we treat it as frame complete.
|
||||
m_frames.back()->setComplete();
|
||||
GETN(1, SkGIFImageStart);
|
||||
if (lastFrameToParse >= 0 && m_frames.count() > lastFrameToParse) {
|
||||
m_streamBuffer.flush();
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SkGIFDone: {
|
||||
m_parseCompleted = true;
|
||||
return SkCodec::kSuccess;
|
||||
}
|
||||
|
||||
default:
|
||||
// We shouldn't ever get here.
|
||||
// This prevents attempting to continue reading this invalid stream.
|
||||
GETN(0, SkGIFDone);
|
||||
return SkCodec::kInvalidInput;
|
||||
break;
|
||||
} // switch
|
||||
m_streamBuffer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
void SkGifImageReader::addFrameIfNecessary()
|
||||
{
|
||||
if (m_frames.empty() || m_frames.back()->isComplete()) {
|
||||
const int i = m_frames.count();
|
||||
m_frames.emplace_back(new SkGIFFrameContext(i));
|
||||
}
|
||||
}
|
||||
|
||||
SkEncodedInfo::Alpha SkGIFFrameContext::onReportedAlpha() const {
|
||||
// Note: We could correct these after decoding - i.e. some frames may turn out to be
|
||||
// independent and opaque if they do not use the transparent pixel, but that would require
|
||||
// checking whether each pixel used the transparent index.
|
||||
return is_palette_index_valid(this->transparentPixel()) ? SkEncodedInfo::kBinary_Alpha
|
||||
: SkEncodedInfo::kOpaque_Alpha;
|
||||
}
|
||||
|
||||
// FIXME: Move this method to close to doLZW().
|
||||
bool SkGIFLZWContext::prepareToDecode()
|
||||
{
|
||||
SkASSERT(m_frameContext->isDataSizeDefined() && m_frameContext->isHeaderDefined());
|
||||
|
||||
// Since we use a codesize of 1 more than the datasize, we need to ensure
|
||||
// that our datasize is strictly less than the SK_MAX_DICTIONARY_ENTRY_BITS.
|
||||
if (m_frameContext->dataSize() >= SK_MAX_DICTIONARY_ENTRY_BITS)
|
||||
return false;
|
||||
clearCode = 1 << m_frameContext->dataSize();
|
||||
avail = clearCode + 2;
|
||||
oldcode = -1;
|
||||
codesize = m_frameContext->dataSize() + 1;
|
||||
codemask = (1 << codesize) - 1;
|
||||
datum = bits = 0;
|
||||
ipass = m_frameContext->interlaced() ? 1 : 0;
|
||||
irow = 0;
|
||||
|
||||
// We want to know the longest sequence encodable by a dictionary with
|
||||
// SK_MAX_DICTIONARY_ENTRIES entries. If we ignore the need to encode the base
|
||||
// values themselves at the beginning of the dictionary, as well as the need
|
||||
// for a clear code or a termination code, we could use every entry to
|
||||
// encode a series of multiple values. If the input value stream looked
|
||||
// like "AAAAA..." (a long string of just one value), the first dictionary
|
||||
// entry would encode AA, the next AAA, the next AAAA, and so forth. Thus
|
||||
// the longest sequence would be SK_MAX_DICTIONARY_ENTRIES + 1 values.
|
||||
//
|
||||
// However, we have to account for reserved entries. The first |datasize|
|
||||
// bits are reserved for the base values, and the next two entries are
|
||||
// reserved for the clear code and termination code. In theory a GIF can
|
||||
// set the datasize to 0, meaning we have just two reserved entries, making
|
||||
// the longest sequence (SK_MAX_DICTIONARY_ENTIRES + 1) - 2 values long. Since
|
||||
// each value is a byte, this is also the number of bytes in the longest
|
||||
// encodable sequence.
|
||||
constexpr size_t kMaxSequence = SK_MAX_DICTIONARY_ENTRIES - 1;
|
||||
constexpr size_t kMaxBytes = (kMaxSequence + SK_DICTIONARY_WORD_SIZE - 1)
|
||||
& ~(SK_DICTIONARY_WORD_SIZE - 1);
|
||||
|
||||
// Now allocate the output buffer. We decode directly into this buffer
|
||||
// until we have at least one row worth of data, then call outputRow().
|
||||
// This means worst case we may have (row width - 1) bytes in the buffer
|
||||
// and then decode a sequence |kMaxBytes| long to append.
|
||||
rowBuffer.reset(m_frameContext->width() - 1 + kMaxBytes);
|
||||
rowIter = rowBuffer.begin();
|
||||
rowsRemaining = m_frameContext->height();
|
||||
|
||||
// Clearing the whole suffix table lets us be more tolerant of bad data.
|
||||
for (int i = 0; i < clearCode; ++i) {
|
||||
std::fill_n(suffix[i].begin(), SK_DICTIONARY_WORD_SIZE, 0);
|
||||
suffix[i][0] = i;
|
||||
suffixLength[i] = 1;
|
||||
prefix[i] = i; // ensure that we have a place to find firstchar
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
398
third_party/gif/SkGifImageReader.h
vendored
398
third_party/gif/SkGifImageReader.h
vendored
@ -1,398 +0,0 @@
|
||||
/* -*- 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 SkGifCodec;
|
||||
|
||||
#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(SkGifCodec* 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;
|
||||
|
||||
SkGifCodec* 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*, SkGifCodec* 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(SkGifCodec* 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
|
||||
SkGifCodec* 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
|
Loading…
Reference in New Issue
Block a user