diff options
author | Leon Scroggins III <scroggo@google.com> | 2018-01-12 11:24:30 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-01-12 19:36:11 +0000 |
commit | 7a10b332a37dab94b1c59685714f6d8c28fc84b9 (patch) | |
tree | 45a7847d658bd1d9ff3e6499633c512933c0b1e3 | |
parent | 65fa8ca85ef146340ddea61bb08c182df499ca62 (diff) |
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>
-rw-r--r-- | BUILD.gn | 1 | ||||
-rw-r--r-- | gn/gn_to_bp.py | 1 | ||||
-rw-r--r-- | gn/samples.gni | 1 | ||||
-rw-r--r-- | include/codec/SkAnimatedImage.h | 86 | ||||
-rw-r--r-- | samplecode/SampleAnimatedImage.cpp | 130 | ||||
-rw-r--r-- | src/codec/SkAnimatedImage.cpp | 236 | ||||
-rw-r--r-- | src/codec/SkCodecPriv.h | 2 |
7 files changed, 456 insertions, 1 deletions
@@ -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(...) |