Make SkCodec more flexible about its required frame

SkCodec sets fRequiredFrame to be the earliest possible frame that a
given frame can depend on. e.g.

- Frame A fills the screen, Keep
- Frame B does not cover A, Keep
- Frame C covers B but not A, and is opaque

Frame C can depend on either A or B. SkCodec already reports that C
depends on A. This CL allows a client of SkCodec to use either A or
B to create C.

Also expose the DisposalMethod. Since any frame between A and C can
be used to create C except for DisposePrevious frames, the client
needs to be able to know the disposal method so they do not try to
use such a frame to create C.

Further, the disposal method can be used to give the client a better
idea whether they will continue to need a frame. (e.g. if frame i is
DisposePrevious and depends on i-1, the client may not want to steal
i-1 to create i, since i+1 may also depend on i-1.)

TODO: Share code for decoding prior frames between GIF and WEBP

Change-Id: I91a5ae22ba3d8dfbe0bde833fa67ae3da0d81ed6
Reviewed-on: https://skia-review.googlesource.com/13722
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Chris Blume <cblume@chromium.org>
Reviewed-by: Matt Sarett <msarett@google.com>
Commit-Queue: Leon Scroggins <scroggo@google.com>
This commit is contained in:
Leon Scroggins III 2017-06-07 12:31:51 -04:00 committed by Skia Commit-Bot
parent 9982c4eb76
commit 33deb7ed4d
13 changed files with 255 additions and 147 deletions

View File

@ -504,9 +504,9 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
&& priorFramePixels.get()) {
// Copy into pixels
memcpy(pixels.get(), priorFramePixels.get(), safeSize);
options.fHasPriorFrame = true;
options.fPriorFrame = reqFrame;
} else {
options.fHasPriorFrame = false;
options.fPriorFrame = SkCodec::kNone;
}
SkCodec::Result result = codec->getPixels(decodeInfo, pixels.get(),
rowBytes, &options,

View File

@ -53,7 +53,6 @@ private:
SkCodec::Options opts;
opts.fFrameIndex = frameIndex;
opts.fHasPriorFrame = false;
const int requiredFrame = fFrameInfos[frameIndex].fRequiredFrame;
if (requiredFrame != SkCodec::kNone) {
SkASSERT(requiredFrame >= 0
@ -62,7 +61,7 @@ private:
// For simplicity, do not try to cache old frames
if (requiredBitmap.getPixels() &&
sk_tool_utils::copy_to(&bm, requiredBitmap.colorType(), requiredBitmap)) {
opts.fHasPriorFrame = true;
opts.fPriorFrame = requiredFrame;
}
}

View File

@ -9,6 +9,7 @@
#define SkCodec_DEFINED
#include "../private/SkTemplates.h"
#include "SkCodecAnimation.h"
#include "SkColor.h"
#include "SkColorSpaceXform.h"
#include "SkEncodedImageFormat.h"
@ -249,7 +250,7 @@ public:
: fZeroInitialized(kNo_ZeroInitialized)
, fSubset(nullptr)
, fFrameIndex(0)
, fHasPriorFrame(false)
, fPriorFrame(kNone)
, fPremulBehavior(SkTransferFunctionBehavior::kRespect)
{}
@ -281,23 +282,19 @@ public:
int fFrameIndex;
/**
* If true, the dst already contains the prior frame.
* If not kNone, the dst already contains the prior frame at this index.
*
* Only meaningful for multi-frame images.
*
* If fFrameIndex needs to be blended with a prior frame (as reported by
* getFrameInfo[fFrameIndex].fRequiredFrame), the client can set this to
* either true or false:
* any non-kRestorePrevious frame in [fRequiredFrame, fFrameIndex) to
* indicate that that frame is already in the dst. Options.fZeroInitialized
* is ignored in this case.
*
* true means that the prior frame is already in the dst, and this
* codec only needs to decode fFrameIndex and blend it with the dst.
* Options.fZeroInitialized is ignored in this case.
*
* false means that the dst does not contain the prior frame, so this
* codec needs to first decode the prior frame (which in turn may need
* to decode its prior frame).
* If set to kNone, the codec will decode any necessary required frame(s) first.
*/
bool fHasPriorFrame;
int fPriorFrame;
/**
* Indicates whether we should do a linear premultiply or a legacy premultiply.
@ -613,7 +610,11 @@ public:
struct FrameInfo {
/**
* The frame that this frame needs to be blended with, or
* kNone.
* kNone if this frame is independent.
*
* Note that this is the *earliest* frame that can be used
* for blending. Any frame from [fRequiredFrame, i) can be
* used, unless its fDisposalMethod is kRestorePrevious.
*/
int fRequiredFrame;
@ -635,6 +636,11 @@ public:
* color index-based frame has a color with alpha but does not use it.
*/
SkAlphaType fAlphaType;
/**
* How this frame should be modified before decoding the next one.
*/
SkCodecAnimation::DisposalMethod fDisposalMethod;
};
/**

View File

@ -8,11 +8,7 @@
#ifndef SkCodecAnimation_DEFINED
#define SkCodecAnimation_DEFINED
#include "SkCodec.h"
#include "SkRect.h"
class SkCodecAnimation {
public:
namespace SkCodecAnimation {
/**
* This specifies how the next frame is based on this frame.
*
@ -20,20 +16,20 @@ public:
*
* The numbers correspond to values in a GIF.
*/
enum DisposalMethod {
enum class DisposalMethod {
/**
* The next frame should be drawn on top of this one.
*
* In a GIF, a value of 0 (not specified) is also treated as Keep.
*/
Keep_DisposalMethod = 1,
kKeep = 1,
/**
* Similar to Keep, except the area inside this frame's rectangle
* should be cleared to the BackGround color (transparent) before
* drawing the next frame.
*/
RestoreBGColor_DisposalMethod = 2,
kRestoreBGColor = 2,
/**
* The next frame should be drawn on top of the previous frame - i.e.
@ -41,29 +37,7 @@ public:
*
* In a GIF, a value of 4 is also treated as RestorePrevious.
*/
RestorePrevious_DisposalMethod = 3,
kRestorePrevious = 3,
};
/**
* How to blend the current frame.
*/
enum class Blend {
/**
* Blend with the prior frame. This is the typical case, supported
* by all animated image types.
*/
kPriorFrame,
/**
* Do not blend.
*
* This frames pixels overwrite previous pixels "blending" with
* the background color of transparent.
*/
kBG,
};
private:
SkCodecAnimation();
};
#endif // SkCodecAnimation_DEFINED

BIN
resources/required.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
resources/required.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

@ -0,0 +1,32 @@
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkCodecAnimationPriv_DEFINED
#define SkCodecAnimationPriv_DEFINED
namespace SkCodecAnimation {
/**
* How to blend the current frame.
*/
enum class Blend {
/**
* Blend with the prior frame. This is the typical case, supported
* by all animated image types.
*/
kPriorFrame,
/**
* Do not blend.
*
* This frames pixels overwrite previous pixels "blending" with
* the background color of transparent.
*/
kBG,
};
}
#endif // SkCodecAnimationPriv_DEFINED

View File

@ -10,6 +10,7 @@
#include "SkTypes.h"
#include "SkCodecAnimation.h"
#include "SkCodecAnimationPriv.h"
/**
* Base class for a single frame of an animated image.
@ -23,7 +24,7 @@ public:
: fId(id)
, fHasAlpha(false)
, fRequiredFrame(kUninitialized)
, fDisposalMethod(SkCodecAnimation::Keep_DisposalMethod)
, fDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep)
, fDuration(0)
, fBlend(SkCodecAnimation::Blend::kPriorFrame)
{

View File

@ -153,6 +153,7 @@ bool SkGifCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const {
frameInfo->fFullyReceived = frameContext->isComplete();
frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType
: kOpaque_SkAlphaType;
frameInfo->fDisposalMethod = frameContext->getDisposalMethod();
}
return true;
}
@ -398,11 +399,24 @@ SkCodec::Result SkGifCodec::decodeFrame(bool firstAttempt, const Options& opts,
}
} else {
// Not independent
if (!opts.fHasPriorFrame) {
// FIXME: Share this code with WEBP
const int reqFrame = frameContext->getRequiredFrame();
if (opts.fPriorFrame != kNone) {
if (opts.fPriorFrame < reqFrame || opts.fPriorFrame >= frameIndex) {
// Alternatively, we could correct this to kNone.
return kInvalidParameters;
}
const auto* prevFrame = fReader->frameContext(opts.fPriorFrame);
if (prevFrame->getDisposalMethod()
== SkCodecAnimation::DisposalMethod::kRestorePrevious) {
// Similarly, this could be corrected to kNone.
return kInvalidParameters;
}
} else {
// Decode that frame into pixels.
Options prevFrameOpts(opts);
prevFrameOpts.fFrameIndex = frameContext->getRequiredFrame();
prevFrameOpts.fHasPriorFrame = false;
prevFrameOpts.fPriorFrame = kNone;
// The prior frame may have a different color table, so update it and the
// swizzler.
this->initializeColorTable(dstInfo, prevFrameOpts.fFrameIndex);
@ -424,24 +438,33 @@ SkCodec::Result SkGifCodec::decodeFrame(bool firstAttempt, const Options& opts,
this->initializeColorTable(dstInfo, frameIndex);
this->initializeSwizzler(dstInfo, frameIndex);
}
const auto* prevFrame = fReader->frameContext(frameContext->getRequiredFrame());
if (prevFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
SkIRect prevRect = prevFrame->frameRect();
if (prevRect.intersect(this->getInfo().bounds())) {
// Do the divide ourselves for left and top, since we do not want
// get_scaled_dimension to upgrade 0 to 1. (This is similar to SkSampledCodec's
// sampling of the subset.)
auto left = prevRect.fLeft / fSwizzler->sampleX();
auto top = prevRect.fTop / fSwizzler->sampleY();
void* const eraseDst = SkTAddOffset<void>(fDst, top * fDstRowBytes
+ left * SkColorTypeBytesPerPixel(dstInfo.colorType()));
auto width = get_scaled_dimension(prevRect.width(), fSwizzler->sampleX());
auto height = get_scaled_dimension(prevRect.height(), fSwizzler->sampleY());
// fSwizzler->fill() would fill to the scaled width of the frame, but we want to
// fill to the scaled with of the width of the PRIOR frame, so we do all the
// scaling ourselves and call the static version.
SkSampler::Fill(dstInfo.makeWH(width, height), eraseDst,
fDstRowBytes, this->getFillValue(dstInfo), kNo_ZeroInitialized);
// If the required frame is RestoreBG, we need to erase it. If a frame after the
// required frame is provided, there is no need to erase, since it will be covered
// anyway.
if (opts.fPriorFrame == reqFrame || opts.fPriorFrame == kNone) {
const auto* prevFrame = fReader->frameContext(reqFrame);
if (prevFrame->getDisposalMethod()
== SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
SkIRect prevRect = prevFrame->frameRect();
if (prevRect.intersect(this->getInfo().bounds())) {
// Do the divide ourselves for left and top, since we do not want
// get_scaled_dimension to upgrade 0 to 1. (This is similar to
// SkSampledCodec's sampling of the subset.)
const auto sampleX = fSwizzler->sampleX();
const auto sampleY = fSwizzler->sampleY();
auto left = prevRect.fLeft / sampleX;
auto top = prevRect.fTop / sampleY;
void* const eraseDst = SkTAddOffset<void>(fDst, top * fDstRowBytes
+ left * SkColorTypeBytesPerPixel(dstInfo.colorType()));
auto width = get_scaled_dimension(prevRect.width(), sampleX);
auto height = get_scaled_dimension(prevRect.height(), sampleY);
// fSwizzler->fill() would fill to the scaled width of the frame, but we
// want to fill to the scaled with of the width of the PRIOR frame, so we
// do all the scaling ourselves and call the static version.
SkSampler::Fill(dstInfo.makeWH(width, height), eraseDst, fDstRowBytes,
this->getFillValue(dstInfo), kNo_ZeroInitialized);
}
}
}
filledBackground = true;

View File

@ -7,6 +7,8 @@
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkCodecAnimation.h"
#include "SkCodecAnimationPriv.h"
#include "SkCodecPriv.h"
#include "SkColorSpaceXform.h"
#include "SkRasterPipeline.h"
@ -253,8 +255,8 @@ int SkWebpCodec::onGetFrameCount() {
Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha);
frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height);
frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ?
SkCodecAnimation::RestoreBGColor_DisposalMethod :
SkCodecAnimation::Keep_DisposalMethod);
SkCodecAnimation::DisposalMethod::kRestoreBGColor :
SkCodecAnimation::DisposalMethod::kKeep);
frame->setDuration(iter.duration);
if (WEBP_MUX_BLEND != iter.blend_method) {
frame->setBlend(SkCodecAnimation::Blend::kBG);
@ -292,6 +294,7 @@ bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {
// animated image.
frameInfo->fFullyReceived = true;
frameInfo->fAlphaType = alpha_type(frame->hasAlpha());
frameInfo->fDisposalMethod = frame->getDisposalMethod();
}
return true;
@ -437,9 +440,15 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
}
} else {
if (!options.fHasPriorFrame) {
// FIXME: Share with GIF
if (options.fPriorFrame != kNone) {
if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) {
return kInvalidParameters;
}
} else {
Options prevFrameOpts(options);
prevFrameOpts.fFrameIndex = requiredFrame;
prevFrameOpts.fPriorFrame = kNone;
const auto result = this->getPixels(dstInfo, dst, rowBytes, &prevFrameOpts,
nullptr, nullptr);
switch (result) {
@ -452,16 +461,20 @@ SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
}
}
// Dispose bg color
const Frame* priorFrame = fFrameHolder.frame(requiredFrame);
if (priorFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
// FIXME: If we add support for scaling/subsets, this rectangle needs to be adjusted.
const auto priorRect = priorFrame->frameRect();
const auto info = dstInfo.makeWH(priorRect.width(), priorRect.height());
const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
const size_t offset = priorRect.x() * bpp + priorRect.y() * rowBytes;
auto* eraseDst = SkTAddOffset<void>(dst, offset);
SkSampler::Fill(info, eraseDst, rowBytes, 0, kNo_ZeroInitialized);
if (options.fPriorFrame == requiredFrame || options.fPriorFrame == kNone) {
// Dispose bg color
const Frame* priorFrame = fFrameHolder.frame(requiredFrame);
if (priorFrame->getDisposalMethod()
== SkCodecAnimation::DisposalMethod::kRestoreBGColor) {
// FIXME: If we add support for scaling/subsets, this rectangle needs to be
// adjusted.
const auto priorRect = priorFrame->frameRect();
const auto info = dstInfo.makeWH(priorRect.width(), priorRect.height());
const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
const size_t offset = priorRect.x() * bpp + priorRect.y() * rowBytes;
auto* eraseDst = SkTAddOffset<void>(dst, offset);
SkSampler::Fill(info, eraseDst, rowBytes, 0, kNo_ZeroInitialized);
}
}
}

View File

@ -57,37 +57,53 @@ DEF_TEST(Codec_565, r) {
SkCodec::Options options;
options.fFrameIndex = 1;
options.fHasPriorFrame = false;
options.fPriorFrame = SkCodec::kNone;
const auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(),
&options, nullptr, nullptr);
REPORTER_ASSERT(r, result == SkCodec::kSuccess);
}
static bool restore_previous(const SkCodec::FrameInfo& info) {
return info.fDisposalMethod == SkCodecAnimation::DisposalMethod::kRestorePrevious;
}
DEF_TEST(Codec_frames, r) {
#define kOpaque kOpaque_SkAlphaType
#define kUnpremul kUnpremul_SkAlphaType
#define kOpaque kOpaque_SkAlphaType
#define kUnpremul kUnpremul_SkAlphaType
#define kKeep SkCodecAnimation::DisposalMethod::kKeep
#define kRestoreBG SkCodecAnimation::DisposalMethod::kRestoreBGColor
#define kRestorePrev SkCodecAnimation::DisposalMethod::kRestorePrevious
static const struct {
const char* fName;
int fFrameCount;
const char* fName;
int fFrameCount;
// One less than fFramecount, since the first frame is always
// independent.
std::vector<int> fRequiredFrames;
std::vector<int> fRequiredFrames;
// Same, since the first frame should match getInfo.
std::vector<SkAlphaType> fAlphaTypes;
std::vector<SkAlphaType> fAlphaTypes;
// The size of this one should match fFrameCount for animated, empty
// otherwise.
std::vector<int> fDurations;
int fRepetitionCount;
std::vector<int> fDurations;
int fRepetitionCount;
std::vector<SkCodecAnimation::DisposalMethod> fDisposalMethods;
} gRecs[] = {
{ "required.gif", 7,
{ 0, 1, 1, SkCodec::kNone, 4, 4 },
{ kOpaque, kUnpremul, kUnpremul, kOpaque, kOpaque, kOpaque },
{ 100, 100, 100, 100, 100, 100, 100 },
0,
{ kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } },
{ "alphabetAnim.gif", 13,
{ SkCodec::kNone, 0, 0, 0, 0, 5, 6, SkCodec::kNone,
SkCodec::kNone, SkCodec::kNone, 10, 11 },
{ kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul,
kUnpremul, kUnpremul, kUnpremul, kOpaque, kOpaque, kUnpremul },
{ 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 },
0 },
0,
{ kKeep, kRestorePrev, kRestorePrev, kRestorePrev, kRestorePrev,
kRestoreBG, kKeep, kRestoreBG, kRestoreBG, kKeep, kKeep,
kRestoreBG, kKeep } },
{ "randPixelsAnim2.gif", 4,
// required frames
{ 0, 0, 1 },
@ -96,7 +112,8 @@ DEF_TEST(Codec_frames, r) {
// durations
{ 0, 1000, 170, 40 },
// repetition count
0 },
0,
{ kKeep, kKeep, kRestorePrev, kKeep } },
{ "randPixelsAnim.gif", 13,
// required frames
{ SkCodec::kNone, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 },
@ -105,33 +122,49 @@ DEF_TEST(Codec_frames, r) {
// durations
{ 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 },
// repetition count
0 },
{ "box.gif", 1, {}, {}, {}, 0 },
{ "color_wheel.gif", 1, {}, {}, {}, 0 },
0,
{ kKeep, kKeep, kKeep, kKeep, kRestoreBG, kRestoreBG, kRestoreBG,
kRestoreBG, kRestorePrev, kRestoreBG, kRestorePrev, kRestorePrev,
kRestorePrev, } },
{ "box.gif", 1, {}, {}, {}, 0, { kKeep } },
{ "color_wheel.gif", 1, {}, {}, {}, 0, { kKeep } },
{ "test640x479.gif", 4, { 0, 1, 2 },
{ kOpaque, kOpaque, kOpaque },
{ 200, 200, 200, 200 },
SkCodec::kRepetitionCountInfinite },
{ "colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5 },
SkCodec::kRepetitionCountInfinite,
{ kKeep, kKeep, kKeep, kKeep } },
{ "colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5,
{ kKeep, kKeep } },
{ "arrow.png", 1, {}, {}, {}, 0 },
{ "google_chrome.ico", 1, {}, {}, {}, 0 },
{ "brickwork-texture.jpg", 1, {}, {}, {}, 0 },
{ "arrow.png", 1, {}, {}, {}, 0, {} },
{ "google_chrome.ico", 1, {}, {}, {}, 0, {} },
{ "brickwork-texture.jpg", 1, {}, {}, {}, 0, {} },
#if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32))
{ "dng_with_preview.dng", 1, {}, {}, {}, 0 },
{ "dng_with_preview.dng", 1, {}, {}, {}, 0, {} },
#endif
{ "mandrill.wbmp", 1, {}, {}, {}, 0 },
{ "randPixels.bmp", 1, {}, {}, {}, 0 },
{ "yellow_rose.webp", 1, {}, {}, {}, 0 },
{ "mandrill.wbmp", 1, {}, {}, {}, 0, {} },
{ "randPixels.bmp", 1, {}, {}, {}, 0, {} },
{ "yellow_rose.webp", 1, {}, {}, {}, 0, {} },
{ "webp-animated.webp", 3, { 0, 1 }, { kOpaque, kOpaque },
{ 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite },
{ 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite,
{ kKeep, kKeep, kKeep } },
{ "blendBG.webp", 7, { 0, SkCodec::kNone, SkCodec::kNone, SkCodec::kNone,
4, 4 },
{ kOpaque, kOpaque, kUnpremul, kOpaque, kUnpremul, kUnpremul },
{ 525, 500, 525, 437, 609, 729, 444 }, 7 },
{ 525, 500, 525, 437, 609, 729, 444 }, 7,
{ kKeep, kKeep, kKeep, kKeep, kKeep, kKeep, kKeep } },
{ "required.webp", 7,
{ 0, 1, 1, SkCodec::kNone, 4, 4 },
{ kOpaque, kUnpremul, kUnpremul, kOpaque, kOpaque, kOpaque },
{ 100, 100, 100, 100, 100, 100, 100 },
1,
{ kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } },
};
#undef kOpaque
#undef kUnpremul
#undef kKeep
#undef kRestorePrev
#undef kRestoreBG
for (const auto& rec : gRecs) {
sk_sp<SkData> data(GetResourceAsData(rec.fName));
@ -178,6 +211,13 @@ DEF_TEST(Codec_frames, r) {
rec.fName, expected - 1, rec.fAlphaTypes.size());
continue;
}
if (rec.fDisposalMethods.size() != static_cast<size_t>(expected)) {
ERRORF(r, "'%s' has wrong number entries in fDisposalMethods; "
"expected %i\tactual: %i",
rec.fName, expected, rec.fDisposalMethods.size());
continue;
}
}
enum class TestMode {
@ -254,6 +294,8 @@ DEF_TEST(Codec_frames, r) {
ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i",
rec.fName, i, rec.fRequiredFrames[i-1], frameInfo.fRequiredFrame);
}
REPORTER_ASSERT(r, frameInfo.fDisposalMethod == rec.fDisposalMethods[i]);
}
if (TestMode::kIndividual == mode) {
@ -261,67 +303,84 @@ DEF_TEST(Codec_frames, r) {
continue;
}
// Compare decoding in two ways:
// 1. Provide the frame that a frame depends on, so the codec just has to blend.
// (in the array cachedFrames)
// 2. Do not provide the frame that a frame depends on, so the codec has to decode
// all the way back to a key-frame. (in a local variable uncachedFrame)
// The two should look the same.
// Compare decoding in multiple ways:
// - Start from scratch for each frame. |codec| will have to decode the required frame
// (and any it depends on) to decode. This is stored in |cachedFrames|.
// - Provide the frame that a frame depends on, so |codec| just has to blend.
// - Provide a frame after the required frame, which will be covered up by the newest
// frame.
// All should look the same.
std::vector<SkBitmap> cachedFrames(frameCount);
const auto info = codec->getInfo().makeColorType(kN32_SkColorType);
auto decode = [&](SkBitmap* bm, bool cached, int index) {
auto decode = [&](SkBitmap* bm, int index, int cachedIndex) {
auto decodeInfo = info;
if (index > 0) {
decodeInfo = info.makeAlphaType(frameInfos[index].fAlphaType);
}
bm->allocPixels(decodeInfo);
if (cached) {
if (cachedIndex != SkCodec::kNone) {
// First copy the pixels from the cached frame
const int requiredFrame = frameInfos[index].fRequiredFrame;
if (requiredFrame != SkCodec::kNone) {
const bool success = sk_tool_utils::copy_to(bm, kN32_SkColorType,
cachedFrames[requiredFrame]);
REPORTER_ASSERT(r, success);
}
const bool success = sk_tool_utils::copy_to(bm, kN32_SkColorType,
cachedFrames[cachedIndex]);
REPORTER_ASSERT(r, success);
}
SkCodec::Options opts;
opts.fFrameIndex = index;
opts.fHasPriorFrame = cached;
opts.fPriorFrame = cachedIndex;
const auto result = codec->getPixels(decodeInfo, bm->getPixels(), bm->rowBytes(),
&opts, nullptr, nullptr);
if (cachedIndex != SkCodec::kNone && restore_previous(frameInfos[cachedIndex])) {
if (result == SkCodec::kInvalidParameters) {
return true;
}
ERRORF(r, "Using a kRestorePrevious frame as fPriorFrame should fail");
return false;
}
if (result != SkCodec::kSuccess) {
ERRORF(r, "Failed to decode frame %i from %s when %scached, error %i",
index, rec.fName, (cached ? "" : "not "), result);
ERRORF(r, "Failed to decode frame %i from %s when providing prior frame %i, "
"error %i", index, rec.fName, cachedIndex, result);
}
return result == SkCodec::kSuccess;
};
for (int i = 0; i < frameCount; i++) {
SkBitmap& cachedFrame = cachedFrames[i];
if (!decode(&cachedFrame, true, i)) {
if (!decode(&cachedFrame, i, SkCodec::kNone)) {
continue;
}
SkBitmap uncachedFrame;
if (!decode(&uncachedFrame, false, i)) {
const auto reqFrame = frameInfos[i].fRequiredFrame;
if (reqFrame == SkCodec::kNone) {
// Nothing to compare against.
continue;
}
for (int j = reqFrame; j < i; j++) {
SkBitmap frame;
if (restore_previous(frameInfos[j])) {
(void) decode(&frame, i, j);
continue;
}
if (!decode(&frame, i, j)) {
continue;
}
// Now verify they're equal.
const size_t rowLen = info.bytesPerPixel() * info.width();
for (int y = 0; y < info.height(); y++) {
const void* cachedAddr = cachedFrame.getAddr(0, y);
SkASSERT(cachedAddr != nullptr);
const void* uncachedAddr = uncachedFrame.getAddr(0, y);
SkASSERT(uncachedAddr != nullptr);
const bool lineMatches = memcmp(cachedAddr, uncachedAddr, rowLen) == 0;
if (!lineMatches) {
SkString name = SkStringPrintf("cached_%i", i);
write_bm(name.c_str(), cachedFrame);
name = SkStringPrintf("uncached_%i", i);
write_bm(name.c_str(), uncachedFrame);
ERRORF(r, "%s's frame %i is different depending on caching!", rec.fName, i);
break;
// Now verify they're equal.
const size_t rowLen = info.bytesPerPixel() * info.width();
for (int y = 0; y < info.height(); y++) {
const void* cachedAddr = cachedFrame.getAddr(0, y);
SkASSERT(cachedAddr != nullptr);
const void* addr = frame.getAddr(0, y);
SkASSERT(addr != nullptr);
const bool lineMatches = memcmp(cachedAddr, addr, rowLen) == 0;
if (!lineMatches) {
SkString name = SkStringPrintf("cached_%i", i);
write_bm(name.c_str(), cachedFrame);
name = SkStringPrintf("frame_%i", i);
write_bm(name.c_str(), frame);
ERRORF(r, "%s's frame %i is different (starting from line %i) when "
"providing prior frame %i!", rec.fName, i, y, j);
break;
}
}
}
}

View File

@ -1514,7 +1514,8 @@ DEF_TEST(Codec_InvalidAnimated, r) {
SkCodec::Options opts;
for (int i = 0; static_cast<size_t>(i) < frameInfos.size(); i++) {
opts.fFrameIndex = i;
opts.fHasPriorFrame = frameInfos[i].fRequiredFrame == i - 1;
const auto reqFrame = frameInfos[i].fRequiredFrame;
opts.fPriorFrame = reqFrame == i - 1 ? reqFrame : SkCodec::kNone;
auto result = codec->startIncrementalDecode(info, bm.getPixels(), bm.rowBytes(), &opts);
if (result != SkCodec::kSuccess) {
ERRORF(r, "Failed to start decoding frame %i (out of %i) with error %i\n", i,

View File

@ -616,11 +616,11 @@ bool SkGifImageReader::parse(SkGifImageReader::SkGIFParseQuery query)
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::RestorePrevious_DisposalMethod);
currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kRestorePrevious);
break;
default:
// Other values use the default.
currentFrame->setDisposalMethod(SkCodecAnimation::Keep_DisposalMethod);
currentFrame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep);
break;
}
currentFrame->setDuration(GETINT16(currentComponent + 1) * 10);
@ -903,7 +903,7 @@ static bool independent(const SkFrame& frame) {
}
static bool restore_bg(const SkFrame& frame) {
return frame.getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod;
return frame.getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestoreBGColor;
}
bool SkGIFFrameContext::onReportsAlpha() const {
@ -935,7 +935,7 @@ void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) {
}
const SkFrame* prevFrame = this->getFrame(i-1);
while (prevFrame->getDisposalMethod() == SkCodecAnimation::RestorePrevious_DisposalMethod) {
while (prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestorePrevious) {
const int prevId = prevFrame->frameId();
if (0 == prevId) {
frame->setHasAlpha(true);
@ -992,7 +992,7 @@ void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) {
return;
}
SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::Keep_DisposalMethod);
SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kKeep);
frame->setRequiredFrame(prevFrame->frameId());
frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame));
}