play with animated gif

Bug: skia:
Change-Id: If86314cda6aa84f4824958cca30cde88a78d4b59
Reviewed-on: https://skia-review.googlesource.com/c/158562
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/gm/animatedGif.cpp b/gm/animatedGif.cpp
index 6e9789e..f2fb09d 100644
--- a/gm/animatedGif.cpp
+++ b/gm/animatedGif.cpp
@@ -182,6 +182,62 @@
         return true;
     }
 };
-
 DEF_GM(return new AnimatedGifGM);
 
+
+#include "SkAnimCodecPlayer.h"
+#include "SkOSFile.h"
+#include "SkMakeUnique.h"
+
+static std::unique_ptr<SkCodec> load_codec(const char filename[]) {
+    return SkCodec::MakeFromData(SkData::MakeFromFileName(filename));
+}
+
+class AnimCodecPlayerGM : public skiagm::GM {
+private:
+    std::vector<std::unique_ptr<SkAnimCodecPlayer> > fPlayers;
+    uint32_t          fBaseMSec = 0;
+
+public:
+    AnimCodecPlayerGM() {
+        const char* root = "/skia/anim/";
+        SkOSFile::Iter iter(root);
+        SkString path;
+        while (iter.next(&path)) {
+            SkString completepath;
+            completepath.printf("%s%s", root, path.c_str());
+            auto codec = load_codec(completepath.c_str());
+            if (codec) {
+                fPlayers.push_back(skstd::make_unique<SkAnimCodecPlayer>(std::move(codec)));
+            }
+        }
+    }
+
+private:
+    SkString onShortName() override {
+        return SkString("AnimCodecPlayer");
+    }
+
+    SkISize onISize() override {
+        return { 1024, 768 };
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        canvas->scale(0.25f, 0.25f);
+        for (auto& p : fPlayers) {
+            canvas->drawImage(p->getFrame(), 0, 0, nullptr);
+            canvas->translate(p->dimensions().width(), 0);
+        }
+    }
+
+    bool onAnimate(const SkAnimTimer& timer) override {
+        if (fBaseMSec == 0) {
+            fBaseMSec = timer.msec();
+        }
+        for (auto& p : fPlayers) {
+            (void)p->seek(timer.msec() - fBaseMSec);
+        }
+        return true;
+    }
+};
+DEF_GM(return new AnimCodecPlayerGM);
diff --git a/gn/utils.gni b/gn/utils.gni
index 6f59ff2..742d1c2 100644
--- a/gn/utils.gni
+++ b/gn/utils.gni
@@ -8,6 +8,7 @@
 _include = get_path_info("../include", "abspath")
 
 skia_utils_sources = [
+  "$_include/utils/SkAnimCodecPlayer.h",
   "$_include/utils/SkFrontBufferedStream.h",
   "$_include/utils/SkCamera.h",
   "$_include/utils/SkCanvasStateUtils.h",
@@ -23,6 +24,7 @@
   "$_include/utils/SkShadowUtils.h",
 
   "$_src/utils/Sk3D.cpp",
+  "$_src/utils/SkAnimCodecPlayer.cpp",
   "$_src/utils/SkBase64.cpp",
   "$_src/utils/SkBase64.h",
   "$_src/utils/SkBitSet.h",
diff --git a/include/utils/SkAnimCodecPlayer.h b/include/utils/SkAnimCodecPlayer.h
new file mode 100644
index 0000000..4b88556
--- /dev/null
+++ b/include/utils/SkAnimCodecPlayer.h
@@ -0,0 +1,60 @@
+/*
+ * 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 SkAnimCodecPlayer_DEFINED
+#define SkAnimCodecPlayer_DEFINED
+
+#include "SkCodec.h"
+
+class SkImage;
+
+class SkAnimCodecPlayer {
+public:
+    SkAnimCodecPlayer(std::unique_ptr<SkCodec> codec);
+    ~SkAnimCodecPlayer();
+
+    /**
+     *  Returns the current frame of the animation. This defaults to the first frame for
+     *  animated codecs (i.e. msec = 0). Calling this multiple times (without calling seek())
+     *  will always return the same image object (or null if there was an error).
+     */
+    sk_sp<SkImage> getFrame();
+
+    /**
+     *  Return the size of the image(s) that will be returned by getFrame().
+     */
+    SkISize dimensions();
+
+    /**
+     *  Returns the total duration of the animation in milliseconds. Returns 0 for a single-frame
+     *  image.
+     */
+    uint32_t duration() { return fTotalDuration; }
+
+    /**
+     *  Finds the closest frame associated with the time code (in milliseconds) and sets that
+     *  to be the current frame (call getFrame() to retrieve that image).
+     *  Returns true iff this call to seek() changed the "current frame" for the animation.
+     *  Thus if seek() returns false, then getFrame() will return the same image as it did
+     *  before this call to seek().
+     */
+    bool seek(uint32_t msec);
+
+
+private:
+    std::unique_ptr<SkCodec>        fCodec;
+    SkImageInfo                     fImageInfo;
+    std::vector<SkCodec::FrameInfo> fFrameInfos;
+    std::vector<sk_sp<SkImage> >    fImages;
+    int                             fCurrIndex = 0;
+    uint32_t                        fTotalDuration;
+
+    sk_sp<SkImage> getFrameAt(int index);
+};
+
+#endif
+
diff --git a/src/utils/SkAnimCodecPlayer.cpp b/src/utils/SkAnimCodecPlayer.cpp
new file mode 100644
index 0000000..0a9bbc0
--- /dev/null
+++ b/src/utils/SkAnimCodecPlayer.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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 "SkAnimCodecPlayer.h"
+#include "SkCodec.h"
+#include "SkData.h"
+#include "SkImage.h"
+#include <algorithm>
+
+SkAnimCodecPlayer::SkAnimCodecPlayer(std::unique_ptr<SkCodec> codec) : fCodec(std::move(codec)) {
+    fImageInfo = fCodec->getInfo();
+    fFrameInfos = fCodec->getFrameInfo();
+    fImages.resize(fFrameInfos.size());
+
+    // change the interpretation of fDuration to a end-time for that frame
+    size_t dur = 0;
+    for (auto& f : fFrameInfos) {
+        dur += f.fDuration;
+        f.fDuration = dur;
+    }
+    fTotalDuration = dur;
+}
+
+SkAnimCodecPlayer::~SkAnimCodecPlayer() {}
+
+SkISize SkAnimCodecPlayer::dimensions() {
+    return { fImageInfo.width(), fImageInfo.height() };
+}
+
+sk_sp<SkImage> SkAnimCodecPlayer::getFrameAt(int index) {
+    SkASSERT((unsigned)index < fFrameInfos.size());
+
+    if (fImages[index]) {
+        return fImages[index];
+    }
+
+    size_t rb = fImageInfo.minRowBytes();
+    size_t size = fImageInfo.computeByteSize(rb);
+    auto data = SkData::MakeUninitialized(size);
+
+    SkCodec::Options opts;
+    opts.fFrameIndex = index;
+
+    const int requiredFrame = fFrameInfos[index].fRequiredFrame;
+    if (requiredFrame != SkCodec::kNoFrame) {
+        auto requiredImage = fImages[requiredFrame];
+        SkPixmap requiredPM;
+        if (requiredImage && requiredImage->peekPixels(&requiredPM)) {
+            sk_careful_memcpy(data->writable_data(), requiredPM.addr(), size);
+            opts.fPriorFrame = requiredFrame;
+        }
+    }
+    if (SkCodec::kSuccess == fCodec->getPixels(fImageInfo, data->writable_data(), rb, &opts)) {
+        return fImages[index] = SkImage::MakeRasterData(fImageInfo, std::move(data), rb);
+    }
+    return nullptr;
+}
+
+sk_sp<SkImage> SkAnimCodecPlayer::getFrame() {
+    return this->getFrameAt(fCurrIndex);
+}
+
+bool SkAnimCodecPlayer::seek(uint32_t msec) {
+    msec %= fTotalDuration;
+
+    auto lower = std::lower_bound(fFrameInfos.begin(), fFrameInfos.end(), msec,
+                                  [](const SkCodec::FrameInfo& info, uint32_t msec) {
+                                      return (uint32_t)info.fDuration < msec;
+                                  });
+    int prevIndex = fCurrIndex;
+    fCurrIndex = lower - fFrameInfos.begin();
+    return fCurrIndex != prevIndex;
+}
+
+