blob: dbff9287ebfe72cf994cc459a827ed2b345a9dc2 [file] [log] [blame]
/*
* 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 "SkCodecAnimation.h"
#include "SkCodecPriv.h"
#include "SkColorData.h"
#include "SkColorTable.h"
#include "SkGifCodec.h"
#include "SkMakeUnique.h"
#include "SkStream.h"
#include "SkSwizzler.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.
const auto encodedInfo = SkEncodedInfo::Make(SkEncodedInfo::kPalette_Color, alpha, 8);
// The choice of unpremul versus premul is arbitrary, since all colors are either fully
// opaque or fully transparent (i.e. kBinary), but we stored the transparent colors as all
// zeroes, which is arguably premultiplied.
const auto alphaType = reader->firstFrameHasAlpha() ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType;
const auto imageInfo = SkImageInfo::Make(reader->screenWidth(), reader->screenHeight(),
kN32_SkColorType, alphaType,
SkColorSpace::MakeSRGB());
return std::unique_ptr<SkCodec>(new SkGifCodec(encodedInfo, imageInfo, reader.release()));
}
bool SkGifCodec::onRewind() {
fReader->clearDecodeState();
return true;
}
SkGifCodec::SkGifCodec(const SkEncodedInfo& encodedInfo, const SkImageInfo& imageInfo,
SkGifImageReader* reader)
: INHERITED(encodedInfo, imageInfo, SkColorSpaceXform::kRGBA_8888_ColorFormat, 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;
static constexpr SkAlphaType kXformAlphaType = kUnpremul_SkAlphaType;
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(),
kXformAlphaType);
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.reset(SkSwizzler::CreateSwizzler(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->getInfo().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() == kNone) {
// 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->getInfo().bounds()
|| frameContext->interlaced() || !fCurrColorTableIsReal) {
// fill ignores the width (replaces it with the actual, scaled width).
// But we need to scale in Y.
auto fillInfo = dstInfo.makeWH(0, scaledHeight);
fSwizzler->fill(fillInfo, fDst, fDstRowBytes, this->getFillValue(dstInfo),
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;
}
uint64_t SkGifCodec::onGetFillValue(const SkImageInfo& dstInfo) const {
// Using transparent as the fill value matches the behavior in Chromium,
// which ignores the background color.
return SK_ColorTRANSPARENT;
}
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, kXformAlphaType);
} 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->getInfo().width());
const int yEnd = std::min(yBegin + rowNumber + repeatCount, this->getInfo().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);
}
}
}