aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn1
-rw-r--r--gn/gn_to_bp.py1
-rw-r--r--gn/samples.gni1
-rw-r--r--include/codec/SkAnimatedImage.h86
-rw-r--r--samplecode/SampleAnimatedImage.cpp130
-rw-r--r--src/codec/SkAnimatedImage.cpp236
-rw-r--r--src/codec/SkCodecPriv.h2
7 files changed, 456 insertions, 1 deletions
diff --git a/BUILD.gn b/BUILD.gn
index c27af9468f..36005a9fe3 100644
--- a/BUILD.gn
+++ b/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",
diff --git a/gn/gn_to_bp.py b/gn/gn_to_bp.py
index 1871157261..5b6b3c7021 100644
--- a/gn/gn_to_bp.py
+++ b/gn/gn_to_bp.py
@@ -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")
diff --git a/gn/samples.gni b/gn/samples.gni
index fe2ab4eba7..3ee15aed85 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -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",
diff --git a/include/codec/SkAnimatedImage.h b/include/codec/SkAnimatedImage.h
new file mode 100644
index 0000000000..aece2f66d1
--- /dev/null
+++ b/include/codec/SkAnimatedImage.h
@@ -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
diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp
new file mode 100644
index 0000000000..96e53b8e4e
--- /dev/null
+++ b/samplecode/SampleAnimatedImage.cpp
@@ -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);
diff --git a/src/codec/SkAnimatedImage.cpp b/src/codec/SkAnimatedImage.cpp
new file mode 100644
index 0000000000..dcb4bb93bf
--- /dev/null
+++ b/src/codec/SkAnimatedImage.cpp
@@ -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;
+}
diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h
index 84215b9fb3..a2a5dbd648 100644
--- a/src/codec/SkCodecPriv.h
+++ b/src/codec/SkCodecPriv.h
@@ -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(...)