Add an SkDrawable for animated images (e.g. GIF)
Bug: b/63909536 SkAnimatedImage is a simple drawable for animating a GIF. Thread-safety is left up to the client. At most two bitmaps are stored in the drawable; one for the current frame and one for a frame that may need to be restored. The backup frame prevents some cases where we would otherwise have to re-decode from the beginning of the image. The API lets the client set the time value, and decodes to match that time. TODO: - Callback for when the animation is complete - Ability to use SkAndroidCodec - Modify the loop count (or leave that up to client?) - Better and/or client-specific caching Other changes: - Add a sample which animates a GIF - Reenable SK_CODEC_PRINTF for debug builds and Android Change-Id: I945ffbccdb6008f2a05ed4d9b2af869a261fb300 Reviewed-on: https://skia-review.googlesource.com/93420 Reviewed-by: Derek Sollenberger <djsollen@google.com> Commit-Queue: Leon Scroggins <scroggo@google.com>
This commit is contained in:
parent
65fa8ca85e
commit
7a10b332a3
1
BUILD.gn
1
BUILD.gn
@ -774,6 +774,7 @@ component("skia") {
|
||||
"src/android/SkBitmapRegionCodec.cpp",
|
||||
"src/android/SkBitmapRegionDecoder.cpp",
|
||||
"src/codec/SkAndroidCodec.cpp",
|
||||
"src/codec/SkAnimatedImage.cpp",
|
||||
"src/codec/SkBmpBaseCodec.cpp",
|
||||
"src/codec/SkBmpCodec.cpp",
|
||||
"src/codec/SkBmpMaskCodec.cpp",
|
||||
|
@ -268,6 +268,7 @@ cflags = cflags.union([
|
||||
"-DSKIA_DLL",
|
||||
"-DSKIA_IMPLEMENTATION=1",
|
||||
"-DATRACE_TAG=ATRACE_TAG_VIEW",
|
||||
"-DSK_PRINT_CODEC_MESSAGES",
|
||||
])
|
||||
cflags_cc.add("-fexceptions")
|
||||
|
||||
|
@ -16,6 +16,7 @@ samples_sources = [
|
||||
"$_samplecode/SampleAARects.cpp",
|
||||
"$_samplecode/SampleAll.cpp",
|
||||
"$_samplecode/SampleAndroidShadows.cpp",
|
||||
"$_samplecode/SampleAnimatedImage.cpp",
|
||||
"$_samplecode/SampleAnimatedText.cpp",
|
||||
"$_samplecode/SampleAnimBlur.cpp",
|
||||
"$_samplecode/SampleArc.cpp",
|
||||
|
86
include/codec/SkAnimatedImage.h
Normal file
86
include/codec/SkAnimatedImage.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef SkAnimatedImage_DEFINED
|
||||
#define SkAnimatedImage_DEFINED
|
||||
|
||||
#include "SkBitmap.h"
|
||||
#include "SkCodecAnimation.h"
|
||||
#include "SkDrawable.h"
|
||||
|
||||
class SkCodec;
|
||||
|
||||
/**
|
||||
* Thread unsafe drawable for drawing animated images (e.g. GIF).
|
||||
*/
|
||||
class SK_API SkAnimatedImage : public SkDrawable {
|
||||
public:
|
||||
/**
|
||||
* Create an SkAnimatedImage from the SkCodec.
|
||||
*
|
||||
* Returns null on failure to allocate pixels. On success, this will
|
||||
* decode the first frame. It will not animate until start() is called.
|
||||
*/
|
||||
static sk_sp<SkAnimatedImage> MakeFromCodec(std::unique_ptr<SkCodec>);
|
||||
|
||||
~SkAnimatedImage() override;
|
||||
|
||||
/**
|
||||
* Start or resume the animation. update() must be called to advance the
|
||||
* time.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stop the animation. update() has no effect while the animation is
|
||||
* stopped.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Reset the animation to the beginning.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Update the current time. If the image is animating, this may decode
|
||||
* a new frame.
|
||||
*
|
||||
* @return the time to show the next frame.
|
||||
* Returns numeric_limits<double>::max() if there is no max frame to
|
||||
* show, and -1.0 if the animation is not running.
|
||||
*/
|
||||
double update(double msecs);
|
||||
|
||||
protected:
|
||||
SkRect onGetBounds() override;
|
||||
void onDraw(SkCanvas*) override;
|
||||
|
||||
private:
|
||||
struct Frame {
|
||||
SkBitmap fBitmap;
|
||||
int fIndex;
|
||||
SkCodecAnimation::DisposalMethod fDisposalMethod;
|
||||
|
||||
Frame();
|
||||
bool copyTo(Frame*) const;
|
||||
};
|
||||
|
||||
std::unique_ptr<SkCodec> fCodec;
|
||||
bool fFinished;
|
||||
bool fRunning;
|
||||
double fNowMS;
|
||||
double fRemainingMS;
|
||||
Frame fActiveFrame;
|
||||
Frame fRestoreFrame;
|
||||
|
||||
SkAnimatedImage(std::unique_ptr<SkCodec>);
|
||||
|
||||
typedef SkDrawable INHERITED;
|
||||
};
|
||||
|
||||
#endif // SkAnimatedImage_DEFINED
|
130
samplecode/SampleAnimatedImage.cpp
Normal file
130
samplecode/SampleAnimatedImage.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkAnimatedImage.h"
|
||||
#include "SkAnimTimer.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "SkCodec.h"
|
||||
#include "SkPaint.h"
|
||||
#include "SkPictureRecorder.h"
|
||||
#include "SkRect.h"
|
||||
#include "SkScalar.h"
|
||||
#include "SkString.h"
|
||||
|
||||
#include "SampleCode.h"
|
||||
#include "Resources.h"
|
||||
|
||||
static constexpr char kPauseKey = 'p';
|
||||
static constexpr char kResetKey = 'r';
|
||||
|
||||
class SampleAnimatedImage : public SampleView {
|
||||
public:
|
||||
SampleAnimatedImage()
|
||||
: INHERITED()
|
||||
, fRunning(false)
|
||||
, fYOffset(0)
|
||||
{}
|
||||
|
||||
protected:
|
||||
void onDrawBackground(SkCanvas* canvas) override {
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
constexpr SkScalar kTextSize = 20;
|
||||
paint.setTextSize(kTextSize);
|
||||
|
||||
SkString str = SkStringPrintf("Press '%c' to start/pause; '%c' to reset.",
|
||||
kPauseKey, kResetKey);
|
||||
const char* text = str.c_str();
|
||||
SkRect bounds;
|
||||
paint.measureText(text, strlen(text), &bounds);
|
||||
fYOffset = bounds.height();
|
||||
|
||||
canvas->drawText(text, strlen(text), 5, fYOffset, paint);
|
||||
fYOffset *= 2;
|
||||
}
|
||||
|
||||
void onDrawContent(SkCanvas* canvas) override {
|
||||
if (!fImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas->translate(0, fYOffset);
|
||||
|
||||
canvas->drawDrawable(fImage.get());
|
||||
canvas->drawDrawable(fDrawable.get(), fImage->getBounds().width(), 0);
|
||||
}
|
||||
|
||||
bool onAnimate(const SkAnimTimer& animTimer) override {
|
||||
if (!fImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fImage->update(animTimer.msec());
|
||||
return true;
|
||||
}
|
||||
|
||||
void onOnceBeforeDraw() override {
|
||||
sk_sp<SkData> file(GetResourceAsData("images/alphabetAnim.gif"));
|
||||
std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(file));
|
||||
if (!codec) {
|
||||
return;
|
||||
}
|
||||
|
||||
fImage = SkAnimatedImage::MakeFromCodec(std::move(codec));
|
||||
if (!fImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkPictureRecorder recorder;
|
||||
auto canvas = recorder.beginRecording(fImage->getBounds());
|
||||
canvas->drawDrawable(fImage.get());
|
||||
fDrawable = recorder.finishRecordingAsDrawable();
|
||||
}
|
||||
|
||||
bool onQuery(SkEvent* evt) override {
|
||||
if (SampleCode::TitleQ(*evt)) {
|
||||
SampleCode::TitleR(evt, "AnimatedImage");
|
||||
return true;
|
||||
}
|
||||
|
||||
SkUnichar uni;
|
||||
if (fImage && SampleCode::CharQ(*evt, &uni)) {
|
||||
switch (uni) {
|
||||
case kPauseKey:
|
||||
if (fRunning) {
|
||||
fImage->stop();
|
||||
fRunning = false;
|
||||
} else {
|
||||
fImage->start();
|
||||
fRunning = true;
|
||||
}
|
||||
return true;
|
||||
case kResetKey:
|
||||
fImage->reset();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this->INHERITED::onQuery(evt);
|
||||
}
|
||||
|
||||
private:
|
||||
sk_sp<SkAnimatedImage> fImage;
|
||||
sk_sp<SkDrawable> fDrawable;
|
||||
bool fRunning;
|
||||
SkScalar fYOffset;
|
||||
typedef SampleView INHERITED;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static SkView* MyFactory() {
|
||||
return new SampleAnimatedImage;
|
||||
}
|
||||
|
||||
static SkViewRegister reg(MyFactory);
|
236
src/codec/SkAnimatedImage.cpp
Normal file
236
src/codec/SkAnimatedImage.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkAnimatedImage.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "SkCodec.h"
|
||||
#include "SkCodecPriv.h"
|
||||
|
||||
sk_sp<SkAnimatedImage> SkAnimatedImage::MakeFromCodec(std::unique_ptr<SkCodec> codec) {
|
||||
if (!codec) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec)));
|
||||
if (!image->fActiveFrame.fBitmap.getPixels()) {
|
||||
// tryAllocPixels failed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
// Sentinel value for starting at the beginning.
|
||||
static constexpr double kInit = -1.0;
|
||||
|
||||
SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkCodec> codec)
|
||||
: fCodec(std::move(codec))
|
||||
, fFinished(false)
|
||||
, fRunning(false)
|
||||
, fNowMS(kInit)
|
||||
, fRemainingMS(kInit)
|
||||
{
|
||||
if (!fActiveFrame.fBitmap.tryAllocPixels(fCodec->getInfo())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->update(kInit);
|
||||
}
|
||||
|
||||
SkAnimatedImage::~SkAnimatedImage() { }
|
||||
|
||||
SkRect SkAnimatedImage::onGetBounds() {
|
||||
return SkRect::Make(fCodec->getInfo().bounds());
|
||||
}
|
||||
|
||||
void SkAnimatedImage::onDraw(SkCanvas* canvas) {
|
||||
canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0);
|
||||
}
|
||||
|
||||
SkAnimatedImage::Frame::Frame()
|
||||
: fIndex(SkCodec::kNone)
|
||||
{}
|
||||
|
||||
bool SkAnimatedImage::Frame::copyTo(Frame* dst) const {
|
||||
if (dst->fBitmap.getPixels()) {
|
||||
dst->fBitmap.setAlphaType(fBitmap.alphaType());
|
||||
} else if (!dst->fBitmap.tryAllocPixels(fBitmap.info())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(dst->fBitmap.getPixels(), fBitmap.getPixels(), fBitmap.computeByteSize());
|
||||
dst->fIndex = fIndex;
|
||||
dst->fDisposalMethod = fDisposalMethod;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkAnimatedImage::start() {
|
||||
fRunning = true;
|
||||
}
|
||||
|
||||
void SkAnimatedImage::stop() {
|
||||
fRunning = false;
|
||||
}
|
||||
|
||||
void SkAnimatedImage::reset() {
|
||||
this->update(kInit);
|
||||
}
|
||||
|
||||
static bool is_restore_previous(SkCodecAnimation::DisposalMethod dispose) {
|
||||
return SkCodecAnimation::DisposalMethod::kRestorePrevious == dispose;
|
||||
}
|
||||
|
||||
double SkAnimatedImage::update(double msecs) {
|
||||
if (fFinished) {
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
const double lastUpdateMS = fNowMS;
|
||||
fNowMS = msecs;
|
||||
const double msSinceLastUpdate = fNowMS - lastUpdateMS;
|
||||
|
||||
const int frameCount = fCodec->getFrameCount();
|
||||
int frameToDecode = SkCodec::kNone;
|
||||
if (kInit == msecs) {
|
||||
frameToDecode = 0;
|
||||
} else {
|
||||
if (!fRunning || lastUpdateMS == kInit) {
|
||||
return kInit;
|
||||
}
|
||||
if (msSinceLastUpdate < fRemainingMS) {
|
||||
fRemainingMS -= msSinceLastUpdate;
|
||||
return fRemainingMS + fNowMS;
|
||||
} else {
|
||||
frameToDecode = (fActiveFrame.fIndex + 1) % frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
SkCodec::FrameInfo frameInfo;
|
||||
if (fCodec->getFrameInfo(frameToDecode, &frameInfo)) {
|
||||
if (!frameInfo.fFullyReceived) {
|
||||
SkCodecPrintf("Frame %i not fully received\n", frameToDecode);
|
||||
fFinished = true;
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
if (kInit == msecs) {
|
||||
fRemainingMS = frameInfo.fDuration;
|
||||
} else {
|
||||
// Check to see whether we should skip this frame.
|
||||
double pastUpdate = msSinceLastUpdate - fRemainingMS;
|
||||
while (pastUpdate >= frameInfo.fDuration) {
|
||||
SkCodecPrintf("Skipping frame %i\n", frameToDecode);
|
||||
pastUpdate -= frameInfo.fDuration;
|
||||
frameToDecode = (frameToDecode + 1) % frameCount;
|
||||
if (!fCodec->getFrameInfo(frameToDecode, &frameInfo)) {
|
||||
SkCodecPrintf("Could not getFrameInfo for frame %i",
|
||||
frameToDecode);
|
||||
// Prior call to getFrameInfo succeeded, so use that one.
|
||||
frameToDecode--;
|
||||
fFinished = true;
|
||||
if (frameToDecode < 0) {
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
}
|
||||
}
|
||||
fRemainingMS = frameInfo.fDuration - pastUpdate;
|
||||
}
|
||||
} else {
|
||||
fFinished = true;
|
||||
if (0 == frameToDecode) {
|
||||
// Static image. This is okay.
|
||||
frameInfo.fRequiredFrame = SkCodec::kNone;
|
||||
frameInfo.fAlphaType = fCodec->getInfo().alphaType();
|
||||
// These fields won't be read.
|
||||
frameInfo.fDuration = INT_MAX;
|
||||
frameInfo.fFullyReceived = true;
|
||||
} else {
|
||||
SkCodecPrintf("Error getting frameInfo for frame %i\n",
|
||||
frameToDecode);
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
}
|
||||
|
||||
if (frameToDecode == fActiveFrame.fIndex) {
|
||||
return fRemainingMS + fNowMS;
|
||||
}
|
||||
|
||||
if (frameToDecode == fRestoreFrame.fIndex) {
|
||||
SkTSwap(fActiveFrame, fRestoreFrame);
|
||||
return fRemainingMS + fNowMS;
|
||||
}
|
||||
|
||||
// The following code makes an effort to avoid overwriting a frame that will
|
||||
// be used again. If frame |i| is_restore_previous, frame |i+1| will not
|
||||
// depend on frame |i|, so do not overwrite frame |i-1|, which may be needed
|
||||
// for frame |i+1|.
|
||||
// We could be even smarter about which frames to save by looking at the
|
||||
// entire dependency chain.
|
||||
SkCodec::Options options;
|
||||
options.fFrameIndex = frameToDecode;
|
||||
if (frameInfo.fRequiredFrame == SkCodec::kNone) {
|
||||
if (is_restore_previous(frameInfo.fDisposalMethod)) {
|
||||
// frameToDecode will be discarded immediately after drawing, so
|
||||
// do not overwrite a frame which could possibly be used in the
|
||||
// future.
|
||||
if (fActiveFrame.fIndex != SkCodec::kNone &&
|
||||
!is_restore_previous(fActiveFrame.fDisposalMethod)) {
|
||||
SkTSwap(fActiveFrame, fRestoreFrame);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto validPriorFrame = [&frameInfo, &frameToDecode](const Frame& frame) {
|
||||
if (SkCodec::kNone == frame.fIndex || is_restore_previous(frame.fDisposalMethod)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return frame.fIndex >= frameInfo.fRequiredFrame && frame.fIndex < frameToDecode;
|
||||
};
|
||||
if (validPriorFrame(fActiveFrame)) {
|
||||
if (is_restore_previous(frameInfo.fDisposalMethod)) {
|
||||
// fActiveFrame is a good frame to use for this one, but we
|
||||
// don't want to overwrite it.
|
||||
fActiveFrame.copyTo(&fRestoreFrame);
|
||||
}
|
||||
options.fPriorFrame = fActiveFrame.fIndex;
|
||||
} else if (validPriorFrame(fRestoreFrame)) {
|
||||
if (!is_restore_previous(frameInfo.fDisposalMethod)) {
|
||||
SkTSwap(fActiveFrame, fRestoreFrame);
|
||||
} else if (!fRestoreFrame.copyTo(&fActiveFrame)) {
|
||||
SkCodecPrintf("Failed to restore frame\n");
|
||||
fFinished = true;
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
options.fPriorFrame = fActiveFrame.fIndex;
|
||||
}
|
||||
}
|
||||
|
||||
auto alphaType = kOpaque_SkAlphaType == frameInfo.fAlphaType ?
|
||||
kOpaque_SkAlphaType : kPremul_SkAlphaType;
|
||||
SkBitmap* dst = &fActiveFrame.fBitmap;
|
||||
if (dst->getPixels()) {
|
||||
SkAssertResult(dst->setAlphaType(alphaType));
|
||||
} else {
|
||||
auto info = fCodec->getInfo().makeAlphaType(alphaType);
|
||||
if (!dst->tryAllocPixels(info)) {
|
||||
fFinished = true;
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
}
|
||||
|
||||
auto result = fCodec->getPixels(dst->info(), dst->getPixels(), dst->rowBytes(), &options);
|
||||
if (result != SkCodec::kSuccess) {
|
||||
SkCodecPrintf("error %i, frame %i of %i\n", result, frameToDecode, fCodec->getFrameCount());
|
||||
// Reset to the beginning.
|
||||
fActiveFrame.fIndex = SkCodec::kNone;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
fActiveFrame.fIndex = frameToDecode;
|
||||
fActiveFrame.fDisposalMethod = frameInfo.fDisposalMethod;
|
||||
return fRemainingMS + fNowMS;
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
#include "SkImageInfo.h"
|
||||
#include "SkTypes.h"
|
||||
|
||||
#ifdef SK_PRINT_CODEC_MESSAGES
|
||||
#if defined(SK_PRINT_CODEC_MESSAGES) || defined(SK_DEBUG)
|
||||
#define SkCodecPrintf SkDebugf
|
||||
#else
|
||||
#define SkCodecPrintf(...)
|
||||
|
Loading…
Reference in New Issue
Block a user