text: // Copyright 2018 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "ui/lottie/animation.h"
  
  #include <map>
  #include <string>
  
  #include "base/check.h"
  #include "base/memory/raw_ptr.h"
  #include "base/memory/ref_counted_memory.h"
  #include "base/memory/scoped_refptr.h"
  #include "base/test/simple_test_tick_clock.h"
  #include "cc/paint/display_item_list.h"
  #include "cc/paint/paint_op_buffer.h"
  #include "cc/paint/paint_record.h"
  #include "cc/paint/record_paint_canvas.h"
  #include "cc/paint/skottie_frame_data.h"
  #include "cc/paint/skottie_frame_data_provider.h"
  #include "cc/paint/skottie_resource_metadata.h"
  #include "cc/paint/skottie_wrapper.h"
  #include "cc/test/lottie_test_data.h"
  #include "cc/test/skia_common.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/abseil-cpp/absl/types/optional.h"
  #include "third_party/skia/include/core/SkBitmap.h"
  #include "third_party/skia/include/core/SkRect.h"
  #include "third_party/skia/include/core/SkStream.h"
  #include "ui/gfx/canvas.h"
  #include "ui/gfx/geometry/rect.h"
  #include "ui/lottie/animation_observer.h"
  
  namespace lottie {
  namespace {
  
  using ::testing::Eq;
  using ::testing::FloatEq;
  using ::testing::FloatNear;
  using ::testing::IsEmpty;
  using ::testing::NotNull;
  using ::testing::Pair;
  using ::testing::SizeIs;
  using ::testing::UnorderedElementsAre;
  
  // A skottie animation with solid green color for the first 2.5 seconds and then
  // a solid blue color for the next 2.5 seconds.
  constexpr char kData[] =
      "{"
      "  \"v\" : \"4.12.0\","
      "  \"fr\": 30,"
      "  \"w\" : 400,"
      "  \"h\" : 200,"
      "  \"ip\": 0,"
      "  \"op\": 150,"
      "  \"assets\": [],"
  
      "  \"layers\": ["
      "    {"
      "      \"ty\": 1,"
      "      \"sw\": 400,"
      "      \"sh\": 200,"
      "      \"sc\": \"#00ff00\","
      "      \"ip\": 0,"
      "      \"op\": 75"
      "    },"
      "    {"
      "      \"ty\": 1,"
      "      \"sw\": 400,"
      "      \"sh\": 200,"
      "      \"sc\": \"#0000ff\","
      "      \"ip\": 76,"
      "      \"op\": 150"
      "    }"
      "  ]"
      "}";
  constexpr float kAnimationWidth = 400.f;
  constexpr float kAnimationHeight = 200.f;
  constexpr auto kAnimationDuration = base::Seconds(5);
  constexpr float kCanvasImageScale = 2.f;
  constexpr float kFrameTimestampToleranceSec = 0.1f;
  
  class TestAnimationObserver : public AnimationObserver {
   public:
    TestAnimationObserver() = default;
    ~TestAnimationObserver() override = default;
    TestAnimationObserver(const TestAnimationObserver&) = delete;
    TestAnimationObserver& operator=(const TestAnimationObserver&) = delete;
  
    void AnimationWillStartPlaying(const Animation* animation) override {
      animation_will_start_playing_ = true;
    }
  
    void AnimationCycleEnded(const Animation* animation) override {
      animation_cycle_ended_ = true;
    }
  
    void AnimationResuming(const Animation* animation) override {
      animation_resuming_ = true;
    }
  
    void Reset() {
      animation_cycle_ended_ = false;
      animation_will_start_playing_ = false;
      animation_resuming_ = false;
    }
  
    bool animation_cycle_ended() const { return animation_cycle_ended_; }
    bool animation_will_start_playing() const {
      return animation_will_start_playing_;
    }
    bool animation_resuming() const { return animation_resuming_; }
  
   private:
    bool animation_cycle_ended_ = false;
    bool animation_will_start_playing_ = false;
    bool animation_resuming_ = false;
  };
  
  class TestSkottieFrameDataProvider : public cc::SkottieFrameDataProvider {
   public:
    class ImageAssetImpl : public cc::SkottieFrameDataProvider::ImageAsset {
     public:
      ImageAssetImpl() = default;
      ImageAssetImpl(const ImageAssetImpl& other) = delete;
      ImageAssetImpl& operator=(const ImageAssetImpl& other) = delete;
  
      absl::optional<cc::SkottieFrameData> GetFrameData(
          float t,
          float scale_factor) override {
        last_frame_t_ = t;
        last_frame_scale_factor_ = scale_factor;
        return current_frame_data_;
      }
  
      void set_current_frame_data(
          absl::optional<cc::SkottieFrameData> current_frame_data) {
        current_frame_data_ = std::move(current_frame_data);
      }
  
      const absl::optional<float>& last_frame_t() const { return last_frame_t_; }
      const absl::optional<float>& last_frame_scale_factor() const {
        return last_frame_scale_factor_;
      }
  
     private:
      friend class TestSkottieFrameDataProvider;
  
      ~ImageAssetImpl() override = default;
  
      absl::optional<cc::SkottieFrameData> current_frame_data_;
      absl::optional<float> last_frame_t_;
      absl::optional<float> last_frame_scale_factor_;
    };
  
    TestSkottieFrameDataProvider() = default;
    TestSkottieFrameDataProvider(const TestSkottieFrameDataProvider&) = delete;
    TestSkottieFrameDataProvider& operator=(const TestSkottieFrameDataProvider&) =
        delete;
    ~TestSkottieFrameDataProvider() override = default;
  
    scoped_refptr<ImageAsset> LoadImageAsset(
        base::StringPiece resource_id,
        const base::FilePath& resource_path) override {
      auto new_asset = base::MakeRefCounted<ImageAssetImpl>();
      CHECK(current_assets_.emplace(std::string(resource_id), new_asset).second);
      return new_asset;
    }
  
    ImageAssetImpl* GetLoadedImageAsset(const std::string& resource_id) {
      auto iter = current_assets_.find(resource_id);
      return iter == current_assets_.end() ? nullptr : iter->second.get();
    }
  
   private:
    std::map<std::string, scoped_refptr<ImageAssetImpl>> current_assets_;
  };
  
  }  // namespace
  
  class AnimationTest : public testing::Test {
   public:
    AnimationTest() = default;
    ~AnimationTest() override = default;
    AnimationTest(const AnimationTest&) = delete;
    AnimationTest& operator=(const AnimationTest&) = delete;
  
    void SetUp() override {
      canvas_ = std::make_unique<gfx::Canvas>(
          gfx::Size(kAnimationWidth, kAnimationHeight), 1.f, false);
      skottie_ = cc::SkottieWrapper::CreateNonSerializable(
          base::as_bytes(base::make_span(kData, std::strlen(kData))));
      animation_ = std::make_unique<Animation>(skottie_);
    }
  
    void TearDown() override { animation_.reset(nullptr); }
  
    gfx::Canvas* canvas() { return canvas_.get(); }
  
    Animation::Style GetStyle() const { return animation_->style_; }
  
    Animation::PlayState GetState() const { return animation_->state_; }
  
    bool IsStopped() const {
      return GetState() == Animation::PlayState::kStopped;
    }
  
    bool IsScheduledToPlay() const {
      return GetState() == Animation::PlayState::kSchedulePlay;
    }
  
    bool IsPlaying() const {
      return GetState() == Animation::PlayState::kPlaying;
    }
  
    bool IsScheduledToResume() const {
      return GetState() == Animation::PlayState::kScheduleResume;
    }
  
    bool HasAnimationEnded() const {
      return GetState() == Animation::PlayState::kEnded;
    }
  
    bool IsPaused() const { return GetState() == Animation::PlayState::kPaused; }
  
    const Animation::TimerControl* GetTimerControl() const {
      return animation_->timer_control_.get();
    }
  
    const base::TickClock* test_clock() const { return &test_clock_; }
  
    void AdvanceClock(base::TimeDelta advance) { test_clock_.Advance(advance); }
  
    base::TimeDelta TimeDeltaSince(const base::TimeTicks& ticks) const {
      return test_clock_.NowTicks() - ticks;
    }
  
    base::TimeTicks NowTicks() const { return test_clock_.NowTicks(); }
  
    double GetTimerStartOffset() const {
      return animation_->timer_control_->GetNormalizedStartOffset();
    }
  
    double GetTimerEndOffset() const {
      return animation_->timer_control_->GetNormalizedEndOffset();
    }
  
    const base::TimeTicks& GetTimerPreviousTick() const {
      return animation_->timer_control_->previous_tick_;
    }
  
    base::TimeDelta GetTimerTotalDuration() const {
      return animation_->timer_control_->total_duration_;
    }
  
    int GetTimerCycles() const {
      return animation_->timer_control_->completed_cycles();
    }
  
    void IsAllSameColor(SkColor color, const SkBitmap& bitmap) const {
      if (bitmap.colorType() == kBGRA_8888_SkColorType) {
        const SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels());
        const int num_pixels = bitmap.width() * bitmap.height();
        for (int i = 0; i < num_pixels; i++)
          EXPECT_EQ(pixels[i], color);
      } else {
        for (int x = 0; x < bitmap.width(); x++)
          for (int y = 0; y < bitmap.height(); y++)
            EXPECT_EQ(bitmap.getColor(x, y), color);
      }
    }
  
   protected:
    std::unique_ptr<gfx::Canvas> canvas_;
    std::unique_ptr<Animation> animation_;
    scoped_refptr<cc::SkottieWrapper> skottie_;
  
   private:
    base::SimpleTestTickClock test_clock_;
  };
  
  class AnimationWithImageAssetsTest : public AnimationTest {
   protected:
    AnimationWithImageAssetsTest()
        : display_list_(base::MakeRefCounted<cc::DisplayItemList>(
              cc::DisplayItemList::kToBeReleasedAsPaintOpBuffer)),
          record_canvas_(display_list_.get(),
                         SkRect::MakeIWH(cc::kLottieDataWith2AssetsWidth,
                                         cc::kLottieDataWith2AssetsHeight)) {}
  
    void SetUp() override {
      canvas_ = std::make_unique<gfx::Canvas>(&record_canvas_, kCanvasImageScale);
      skottie_ = cc::CreateSkottieFromString(cc::kLottieDataWith2Assets);
      animation_ = std::make_unique<Animation>(skottie_, &frame_data_provider_);
      asset_0_ = frame_data_provider_.GetLoadedImageAsset("image_0");
      asset_1_ = frame_data_provider_.GetLoadedImageAsset("image_1");
      ASSERT_THAT(asset_0_, NotNull());
      ASSERT_THAT(asset_1_, NotNull());
    }
  
    cc::SkottieFrameData CreateHighQualityTestFrameData() {
      return {
          .image = cc::CreateDiscardablePaintImage(animation_->GetOriginalSize()),
          .quality = cc::PaintFlags::FilterQuality::kHigh};
    }
  
    const scoped_refptr<cc::DisplayItemList> display_list_;
    cc::RecordPaintCanvas record_canvas_;
    TestSkottieFrameDataProvider frame_data_provider_;
    raw_ptr<TestSkottieFrameDataProvider::ImageAssetImpl> asset_0_;
    raw_ptr<TestSkottieFrameDataProvider::ImageAssetImpl> asset_1_;
  };
  
  TEST_F(AnimationTest, InitializationAndLoadingData) {
    skottie_ = cc::SkottieWrapper::CreateNonSerializable(
        base::as_bytes(base::make_span(kData, std::strlen(kData))));
    animation_ = std::make_unique<Animation>(skottie_);
    EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
    EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);
    EXPECT_EQ(animation_->GetAnimationDuration(), kAnimationDuration);
    EXPECT_TRUE(IsStopped());
  
    skottie_ = cc::SkottieWrapper::CreateNonSerializable(
        base::as_bytes(base::make_span(kData, std::strlen(kData))));
    animation_ = std::make_unique<Animation>(skottie_);
    EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
    EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);
    EXPECT_EQ(animation_->GetAnimationDuration(), kAnimationDuration);
    EXPECT_TRUE(IsStopped());
  }
  
  TEST_F(AnimationTest, PlayLinearAnimation) {
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    EXPECT_TRUE(IsStopped());
    animation_->Start(Animation::Style::kLinear);
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
    EXPECT_FLOAT_EQ(GetTimerStartOffset(), 0);
    EXPECT_FLOAT_EQ(GetTimerEndOffset(), 1.f);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(50);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kAdvance / kAnimationDuration);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), base::TimeDelta());
  
    // Advance the clock to the end of the animation.
    constexpr auto kAdvanceToEnd =
        kAnimationDuration - kAdvance + base::Milliseconds(1);
    AdvanceClock(kAdvanceToEnd);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvanceToEnd);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 1.f);
    EXPECT_TRUE(HasAnimationEnded());
    EXPECT_TRUE(observer.animation_cycle_ended());
  }
  
  TEST_F(AnimationTest, StopLinearAnimation) {
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLinear);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
  
    constexpr auto kAdvance = base::Milliseconds(50);
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kAdvance / kAnimationDuration);
  
    animation_->Stop();
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0.0f);
    EXPECT_TRUE(IsStopped());
  }
  
  TEST_F(AnimationTest, PlaySubsectionOfLinearAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    TestAnimationObserver observer;
  
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    EXPECT_FALSE(observer.animation_cycle_ended());
    animation_->StartSubsection(kStartTime, kDuration, Animation::Style::kLinear);
  
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
    EXPECT_FLOAT_EQ(GetTimerEndOffset(),
                    (kStartTime + kDuration) / kAnimationDuration);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), base::TimeDelta());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    // Advance clock another 300 ms.
    constexpr auto kAdvance2 = base::Milliseconds(300);
    AdvanceClock(kAdvance2);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance2);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance + kAdvance2) / kAnimationDuration);
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    // Reach the end of animation.
    constexpr auto kAdvanceToEnd =
        kDuration - kAdvance - kAdvance2 + base::Milliseconds(1);
    AdvanceClock(kAdvanceToEnd);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvanceToEnd);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
    EXPECT_TRUE(observer.animation_cycle_ended());
    EXPECT_TRUE(HasAnimationEnded());
  }
  
  TEST_F(AnimationTest, PausingLinearAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(200));
  
    animation_->StartSubsection(kStartTime, kDuration, Animation::Style::kLinear);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Pause();
    EXPECT_TRUE(IsPaused());
  
    // Advancing clock and stepping animation should have no effect when animation
    // is paused.
    AdvanceClock(kAnimationDuration);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    // Resume playing the animation.
    animation_->ResumePlaying();
    EXPECT_TRUE(IsScheduledToResume());
  
    // There should be no progress, since we haven't advanced the clock yet.
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance * 2) / kAnimationDuration);
  
    AdvanceClock(kDuration - kAdvance * 2 + base::Milliseconds(1));
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  }
  
  TEST_F(AnimationTest, PlayLoopAnimation) {
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    EXPECT_TRUE(IsStopped());
    animation_->Start(Animation::Style::kLoop);
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
    EXPECT_FLOAT_EQ(GetTimerStartOffset(), 0);
    EXPECT_FLOAT_EQ(GetTimerEndOffset(), 1.0f);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(50);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kAdvance / kAnimationDuration);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), base::TimeDelta());
  
    // Advance the clock to the end of the animation.
    AdvanceClock(kAnimationDuration - kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()),
              kAnimationDuration - kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_EQ(GetTimerCycles(), 1);
    EXPECT_TRUE(std::abs(animation_->GetCurrentProgress() - 0.f) < 0.0001f);
    EXPECT_TRUE(observer.animation_cycle_ended());
    EXPECT_TRUE(IsPlaying());
  }
  
  TEST_F(AnimationTest, PlaySubsectionOfLoopAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    EXPECT_TRUE(IsStopped());
    animation_->StartSubsection(kStartTime, kDuration, Animation::Style::kLoop);
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FALSE(observer.animation_cycle_ended());
    EXPECT_FLOAT_EQ(GetTimerStartOffset(), kStartTime / kAnimationDuration);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
    EXPECT_FLOAT_EQ(GetTimerEndOffset(),
                    (kStartTime + kDuration) / kAnimationDuration);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    constexpr auto kAdvance2 = base::Milliseconds(300);
    AdvanceClock(kAdvance2);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance2);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance + kAdvance2) / kAnimationDuration);
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    // Reach the end of animation.
    AdvanceClock(kDuration - kAdvance - kAdvance2);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()),
              kDuration - kAdvance - kAdvance2);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(observer.animation_cycle_ended());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
  }
  
  TEST_F(AnimationTest, PausingLoopAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(200));
  
    animation_->StartSubsection(kStartTime, kDuration, Animation::Style::kLoop);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kStartTime / kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Pause();
    EXPECT_TRUE(IsPaused());
  
    // Advancing clock and stepping animation should have no effect when animation
    // is paused.
    AdvanceClock(kAnimationDuration);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    // Resume playing the animation.
    animation_->ResumePlaying();
    EXPECT_TRUE(IsScheduledToResume());
  
    // There should be no progress, since we haven't advanced the clock yet.
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance * 2) / kAnimationDuration);
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    AdvanceClock(kDuration - kAdvance * 2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_cycle_ended());
  }
  
  TEST_F(AnimationTest, PlayThrobbingAnimation) {
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kThrobbing);
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
    EXPECT_FLOAT_EQ(GetTimerStartOffset(), 0);
    EXPECT_FLOAT_EQ(GetTimerEndOffset(), 1.0f);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(50);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kAdvance / kAnimationDuration);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), base::TimeDelta());
  
    // Advance the clock to the end of the animation.
    AdvanceClock(kAnimationDuration - kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()),
              kAnimationDuration - kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 1.0f);
    EXPECT_TRUE(IsPlaying());
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    AdvanceClock(kAnimationDuration / 2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0.5f);
    EXPECT_TRUE(IsPlaying());
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    AdvanceClock(kAnimationDuration / 2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_cycle_ended());
  }
  
  TEST_F(AnimationTest, PlaySubsectionOfThrobbingAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    animation_->StartSubsection(kStartTime, kDuration,
                                Animation::Style::kThrobbing);
    EXPECT_TRUE(IsScheduledToPlay());
    EXPECT_FALSE(observer.animation_will_start_playing());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FALSE(IsScheduledToPlay());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_will_start_playing());
  
    EXPECT_FLOAT_EQ(GetTimerStartOffset(), kStartTime / kAnimationDuration);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
    EXPECT_FLOAT_EQ(GetTimerEndOffset(),
                    (kStartTime + kDuration) / kAnimationDuration);
  
    EXPECT_EQ(GetTimerTotalDuration(), kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FALSE(observer.animation_cycle_ended());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    constexpr auto kAdvance2 = base::Milliseconds(300);
    AdvanceClock(kAdvance2);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), kAdvance2);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FALSE(observer.animation_cycle_ended());
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()), base::TimeDelta());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance + kAdvance2) / kAnimationDuration);
  
    // Reach the end of animation.
    AdvanceClock(kDuration - kAdvance - kAdvance2);
    EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()),
              kDuration - kAdvance - kAdvance2);
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    constexpr auto kAdvance3 = base::Milliseconds(500);
    AdvanceClock(kAdvance3);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kDuration - kAdvance3) / kAnimationDuration);
    EXPECT_TRUE(IsPlaying());
    EXPECT_FALSE(observer.animation_cycle_ended());
  
    AdvanceClock(kDuration - kAdvance3);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
    EXPECT_TRUE(IsPlaying());
    EXPECT_TRUE(observer.animation_cycle_ended());
  
    observer.Reset();
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
    EXPECT_TRUE(IsPlaying());
  }
  
  TEST_F(AnimationTest, PausingThrobbingAnimation) {
    constexpr auto kStartTime = base::Milliseconds(400);
    constexpr auto kDuration = base::Milliseconds(1000);
  
    AdvanceClock(base::Milliseconds(200));
  
    animation_->StartSubsection(kStartTime, kDuration,
                                Animation::Style::kThrobbing);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_TRUE(IsPlaying());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kStartTime / kAnimationDuration);
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Pause();
    EXPECT_TRUE(IsPaused());
  
    // Advancing clock and stepping animation should have no effect when animation
    // is paused.
    AdvanceClock(kAnimationDuration);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    // Resume playing the animation.
    animation_->ResumePlaying();
    EXPECT_TRUE(IsScheduledToResume());
  
    // There should be no progress, since we haven't advanced the clock yet.
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance * 2) / kAnimationDuration);
  
    AdvanceClock(kDuration - kAdvance * 2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
    EXPECT_TRUE(IsPlaying());
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kDuration - kAdvance) / kAnimationDuration);
    EXPECT_TRUE(IsPlaying());
  
    animation_->Pause();
    EXPECT_TRUE(IsPaused());
  
    AdvanceClock(kAnimationDuration * 2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kDuration - kAdvance) / kAnimationDuration);
  
    // Resume playing the animation.
    animation_->ResumePlaying();
    EXPECT_TRUE(IsScheduledToResume());
  
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
  
    constexpr auto kAdvance2 = base::Milliseconds(500);
    AdvanceClock(kAdvance2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(
        animation_->GetCurrentProgress(),
        (kStartTime + kDuration - kAdvance - kAdvance2) / kAnimationDuration);
  
    AdvanceClock(kDuration - kAdvance - kAdvance2);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
  
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    (kStartTime + kAdvance) / kAnimationDuration);
    EXPECT_TRUE(IsPlaying());
  }
  
  // Test to see if the race condition is handled correctly. It may happen that we
  // pause the video before it even starts playing.
  TEST_F(AnimationTest, PauseBeforePlay) {
    TestAnimationObserver observer;
    animation_->SetAnimationObserver(&observer);
  
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start();
    EXPECT_TRUE(IsScheduledToPlay());
  
    animation_->Pause();
    EXPECT_TRUE(IsPaused());
  
    AdvanceClock(base::Milliseconds(100));
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
  
    animation_->ResumePlaying();
    EXPECT_TRUE(IsScheduledToResume());
  
    AdvanceClock(base::Milliseconds(100));
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_TRUE(IsPlaying());
  
    constexpr auto kAdvance = base::Milliseconds(100);
    AdvanceClock(kAdvance);
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
                    kAdvance / kAnimationDuration);
  }
  
  TEST_F(AnimationTest, PaintTest) {
    gfx::Canvas canvas(gfx::Size(kAnimationWidth, kAnimationHeight), 1.f, false);
  
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLinear);
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
  
    AdvanceClock(base::Milliseconds(50));
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
    IsAllSameColor(SK_ColorGREEN, canvas.GetBitmap());
  
    AdvanceClock(base::Milliseconds(2450));
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
    IsAllSameColor(SK_ColorGREEN, canvas.GetBitmap());
  
    AdvanceClock(base::Milliseconds(50));
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
    IsAllSameColor(SK_ColorBLUE, canvas.GetBitmap());
  
    AdvanceClock(base::Milliseconds(1000));
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
    IsAllSameColor(SK_ColorBLUE, canvas.GetBitmap());
  
    AdvanceClock(base::Milliseconds(1400));
    animation_->Paint(&canvas, NowTicks(), animation_->GetOriginalSize());
    IsAllSameColor(SK_ColorBLUE, canvas.GetBitmap());
  }
  
  TEST_F(AnimationWithImageAssetsTest, PaintsAnimationImagesToCanvas) {
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLoop);
  
    TestSkottieFrameDataProvider::ImageAssetImpl* asset_0 =
        frame_data_provider_.GetLoadedImageAsset("image_0");
    TestSkottieFrameDataProvider::ImageAssetImpl* asset_1 =
        frame_data_provider_.GetLoadedImageAsset("image_1");
    ASSERT_THAT(asset_0, NotNull());
    ASSERT_THAT(asset_1, NotNull());
  
    cc::SkottieFrameData frame_0 = CreateHighQualityTestFrameData();
    cc::SkottieFrameData frame_1 = CreateHighQualityTestFrameData();
    asset_0->set_current_frame_data(frame_0);
    asset_1->set_current_frame_data(frame_1);
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    sk_sp<cc::PaintRecord> paint_record = display_list_->ReleaseAsRecord();
    ASSERT_THAT(paint_record, NotNull());
    ASSERT_THAT(paint_record->size(), Eq(1u));
    const cc::DrawSkottieOp* op =
        paint_record->GetOpAtForTesting<cc::DrawSkottieOp>(0);
    ASSERT_THAT(op, NotNull());
    EXPECT_THAT(op->images, UnorderedElementsAre(Pair(
                                cc::HashSkottieResourceId("image_0"), frame_0)));
  
    AdvanceClock(animation_->GetAnimationDuration() * .75);
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    paint_record = display_list_->ReleaseAsRecord();
    ASSERT_THAT(paint_record, NotNull());
    ASSERT_THAT(paint_record->size(), Eq(1u));
    op = paint_record->GetOpAtForTesting<cc::DrawSkottieOp>(0);
    ASSERT_THAT(op, NotNull());
    EXPECT_THAT(op->images, UnorderedElementsAre(Pair(
                                cc::HashSkottieResourceId("image_1"), frame_1)));
  }
  
  TEST_F(AnimationWithImageAssetsTest, SkipsNullAnimationImages) {
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLoop);
  
    asset_0_->set_current_frame_data(CreateHighQualityTestFrameData());
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
    display_list_->ReleaseAsRecord();
  
    AdvanceClock(animation_->GetAnimationDuration() / 4);
  
    asset_0_->set_current_frame_data(absl::nullopt);
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
    sk_sp<cc::PaintRecord> paint_record = display_list_->ReleaseAsRecord();
    ASSERT_THAT(paint_record, NotNull());
    ASSERT_THAT(paint_record->size(), Eq(1u));
    const cc::DrawSkottieOp* op =
        paint_record->GetOpAtForTesting<cc::DrawSkottieOp>(0);
    ASSERT_THAT(op, NotNull());
    EXPECT_THAT(op->images, IsEmpty());
  }
  
  TEST_F(AnimationWithImageAssetsTest, LoadsCorrectFrameTimestamp) {
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLoop);
  
    asset_0_->set_current_frame_data(CreateHighQualityTestFrameData());
    asset_1_->set_current_frame_data(CreateHighQualityTestFrameData());
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    ASSERT_TRUE(asset_0_->last_frame_t().has_value());
    EXPECT_THAT(asset_0_->last_frame_t().value(), FloatEq(0));
  
    base::TimeDelta three_quarter_duration =
        animation_->GetAnimationDuration() * .75;
    AdvanceClock(three_quarter_duration);
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    // The timestamp is "relative to the image layer timeline origin" (see
    // SkResources.h). The test animation used in this case has 2 layers for the
    // first and second halves of the animation. So the 3/4 point of the animation
    // is half way into the second layer, or 1/4 the duration of the whole
    // animation.
    base::TimeDelta half_duration = animation_->GetAnimationDuration() * .5;
    ASSERT_TRUE(asset_1_->last_frame_t().has_value());
    EXPECT_THAT(asset_1_->last_frame_t().value(),
                FloatNear((three_quarter_duration - half_duration).InSecondsF(),
                          kFrameTimestampToleranceSec));
  
    AdvanceClock(half_duration);
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    base::TimeDelta quarter_duration = animation_->GetAnimationDuration() / 4;
    ASSERT_TRUE(asset_0_->last_frame_t().has_value());
    EXPECT_THAT(
        asset_0_->last_frame_t().value(),
        FloatNear(quarter_duration.InSecondsF(), kFrameTimestampToleranceSec));
  }
  
  TEST_F(AnimationWithImageAssetsTest, LoadsCorrectImageScale) {
    AdvanceClock(base::Milliseconds(300));
  
    animation_->Start(Animation::Style::kLoop);
  
    asset_0_->set_current_frame_data(CreateHighQualityTestFrameData());
  
    display_list_->StartPaint();
    animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
    display_list_->EndPaintOfUnpaired(gfx::Rect(animation_->GetOriginalSize()));
  
    ASSERT_TRUE(asset_0_->last_frame_scale_factor().has_value());
    EXPECT_THAT(asset_0_->last_frame_scale_factor().value(),
                FloatEq(kCanvasImageScale));
  }
  
  }  // namespace lottie
  