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 /src/codec | |
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>
Diffstat (limited to 'src/codec')
-rw-r--r-- | src/codec/SkAnimatedImage.cpp | 236 | ||||
-rw-r--r-- | src/codec/SkCodecPriv.h | 2 |
2 files changed, 237 insertions, 1 deletions
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(...) |