Commit 9b7e8547 authored by Malay Keshav's avatar Malay Keshav Committed by Commit Bot

Implements the wrapper for skottie animation class

Adds the skottie animation class that is a wrapper around the Skia's
implementation of lottie player. The class manages its own timeline with
the help of a timer object.

This is the phase 1 implementation of native skottie which rasterizes
the animation on the UI thread. Subsequent changes will move this
rasterization to worker threads(phase 2) and then to GPU (phase 3).

Adds unittests as well.

Design Doc: go/cros-skottie

Bug: 867147
Change-Id: I2501710a38874f247ab3ab56a743675e3d07edfb
Component: gfx, skottie, vector animation
Reviewed-on: https://chromium-review.googlesource.com/1189056
Commit-Queue: Malay Keshav <malaykeshav@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#594981}
parent a0becd4f
...@@ -220,6 +220,9 @@ jumbo_component("gfx") { ...@@ -220,6 +220,9 @@ jumbo_component("gfx") {
"shadow_util.h", "shadow_util.h",
"skia_paint_util.cc", "skia_paint_util.cc",
"skia_paint_util.h", "skia_paint_util.h",
"skia_vector_animation.cc",
"skia_vector_animation.h",
"skia_vector_animation_observer.h",
] ]
} }
...@@ -686,6 +689,7 @@ test("gfx_unittests") { ...@@ -686,6 +689,7 @@ test("gfx_unittests") {
"sequential_id_generator_unittest.cc", "sequential_id_generator_unittest.cc",
"shadow_value_unittest.cc", "shadow_value_unittest.cc",
"skbitmap_operations_unittest.cc", "skbitmap_operations_unittest.cc",
"skia_vector_animation_unittest.cc",
"skrect_conversion_unittest.cc", "skrect_conversion_unittest.cc",
"transform_util_unittest.cc", "transform_util_unittest.cc",
"utf16_indexing_unittest.cc", "utf16_indexing_unittest.cc",
......
// 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/gfx/skia_vector_animation.h"
#include "base/trace_event/trace_event.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/skia_vector_animation_observer.h"
namespace gfx {
SkiaVectorAnimation::TimerControl::TimerControl(
const base::TimeDelta& offset,
const base::TimeDelta& cycle_duration,
const base::TimeDelta& total_duration,
const base::TimeTicks& start_timestamp,
bool should_reverse)
: start_offset_(offset),
end_offset_((offset + cycle_duration)),
cycle_duration_(end_offset_ - start_offset_),
progress_per_millisecond_(1.0 / total_duration.InMillisecondsF()),
previous_tick_(start_timestamp),
progress_(base::TimeDelta::FromMilliseconds(0)),
current_cycle_progress_(start_offset_),
should_reverse_(should_reverse) {}
void SkiaVectorAnimation::TimerControl::Step(const base::TimeTicks& timestamp) {
progress_ += timestamp - previous_tick_;
previous_tick_ = timestamp;
base::TimeDelta completed_cycles_duration =
completed_cycles_ * cycle_duration_;
if (progress_ >= completed_cycles_duration + cycle_duration_) {
completed_cycles_++;
completed_cycles_duration += cycle_duration_;
}
current_cycle_progress_ =
start_offset_ + progress_ - completed_cycles_duration;
if (should_reverse_ && completed_cycles_ % 2) {
current_cycle_progress_ =
end_offset_ - (current_cycle_progress_ - start_offset_);
}
}
void SkiaVectorAnimation::TimerControl::Resume(
const base::TimeTicks& timestamp) {
previous_tick_ = timestamp;
}
double SkiaVectorAnimation::TimerControl::GetNormalizedCurrentCycleProgress()
const {
return current_cycle_progress_.InMillisecondsF() * progress_per_millisecond_;
}
double SkiaVectorAnimation::TimerControl::GetNormalizedStartOffset() const {
return start_offset_.InMillisecondsF() * progress_per_millisecond_;
}
double SkiaVectorAnimation::TimerControl::GetNormalizedEndOffset() const {
return end_offset_.InMillisecondsF() * progress_per_millisecond_;
}
SkiaVectorAnimation::SkiaVectorAnimation(
const scoped_refptr<base::RefCountedMemory>& data_stream) {
TRACE_EVENT0("ui", "SkiaVectorAnimation Parse");
SkMemoryStream sk_stream(data_stream->front(), data_stream->size());
animation_ = skottie::Animation::Make(&sk_stream);
}
SkiaVectorAnimation::SkiaVectorAnimation(std::unique_ptr<SkMemoryStream> stream)
: animation_(skottie::Animation::Make(stream.get())) {}
SkiaVectorAnimation::~SkiaVectorAnimation() {}
void SkiaVectorAnimation::SetAnimationObserver(
SkiaVectorAnimationObserver* observer) {
DCHECK(!observer_ || !observer);
observer_ = observer;
}
base::TimeDelta SkiaVectorAnimation::GetAnimationDuration() const {
return base::TimeDelta::FromMilliseconds(
std::floor(SkScalarToFloat(animation_->duration()) * 1000.f));
}
gfx::Size SkiaVectorAnimation::GetOriginalSize() const {
#if DCHECK_IS_ON()
// The size should have no fractional component.
gfx::SizeF float_size = gfx::SkSizeToSizeF(animation_->size());
gfx::Size rounded_size = gfx::ToRoundedSize(float_size);
float height_diff = std::abs(float_size.height() - rounded_size.height());
float width_diff = std::abs(float_size.width() - rounded_size.width());
DCHECK_LE(height_diff, std::numeric_limits<float>::epsilon());
DCHECK_LE(width_diff, std::numeric_limits<float>::epsilon());
#endif
return gfx::ToRoundedSize(gfx::SkSizeToSizeF(animation_->size()));
}
void SkiaVectorAnimation::Start(Style style) {
DCHECK_NE(state_, PlayState::kPaused);
DCHECK_NE(state_, PlayState::kPlaying);
StartSubsection(base::TimeDelta(), GetAnimationDuration(), style);
}
void SkiaVectorAnimation::StartSubsection(base::TimeDelta start_offset,
base::TimeDelta duration,
Style style) {
DCHECK(state_ == PlayState::kStopped || state_ == PlayState::kEnded);
DCHECK_LE(start_offset + duration, GetAnimationDuration());
style_ = style;
// Reset the |timer_control_| object for a new animation play.
timer_control_.reset(nullptr);
// Schedule a play for the animation and store the necessary information
// needed to start playing.
state_ = PlayState::kSchedulePlay;
scheduled_start_offset_ = start_offset;
scheduled_duration_ = duration;
}
void SkiaVectorAnimation::Pause() {
DCHECK(state_ == PlayState::kPlaying || state_ == PlayState::kSchedulePlay);
state_ = PlayState::kPaused;
}
void SkiaVectorAnimation::ResumePlaying() {
DCHECK(state_ == PlayState::kPaused);
state_ = PlayState::kScheduleResume;
}
void SkiaVectorAnimation::Stop() {
state_ = PlayState::kStopped;
timer_control_.reset(nullptr);
}
float SkiaVectorAnimation::GetCurrentProgress() const {
switch (state_) {
case PlayState::kStopped:
return 0;
case PlayState::kEnded:
DCHECK(timer_control_);
return timer_control_->GetNormalizedEndOffset();
case PlayState::kPaused:
if (timer_control_) {
return timer_control_->GetNormalizedCurrentCycleProgress();
} else {
// It may be that the timer hasn't been initialized which may happen if
// the animation was paused while it was in |kScheculePlay| state.
return scheduled_start_offset_.InMillisecondsF() /
animation_->duration();
}
case PlayState::kSchedulePlay:
case PlayState::kPlaying:
case PlayState::kScheduleResume:
// The timer control needs to be initialized before making this call. It
// may not have been initialized if OnAnimationStep has not been called
// yet
DCHECK(timer_control_);
return timer_control_->GetNormalizedCurrentCycleProgress();
}
}
void SkiaVectorAnimation::Paint(gfx::Canvas* canvas,
const base::TimeTicks& timestamp,
const gfx::Size& size) {
switch (state_) {
case PlayState::kStopped:
return;
case PlayState::kSchedulePlay:
InitTimer(timestamp);
state_ = PlayState::kPlaying;
if (observer_)
observer_->AnimationWillStartPlaying(this);
break;
case PlayState::kPlaying:
UpdateState(timestamp);
break;
case PlayState::kPaused:
break;
case PlayState::kScheduleResume:
state_ = PlayState::kPlaying;
if (timer_control_) {
timer_control_->Resume(timestamp);
} else {
// The animation may have been paused after a play was scheduled but
// before it started playing.
InitTimer(timestamp);
}
if (observer_)
observer_->AnimationResuming(this);
break;
case PlayState::kEnded:
break;
}
PaintFrame(canvas, GetCurrentProgress(), size);
}
void SkiaVectorAnimation::PaintFrame(gfx::Canvas* canvas,
float t,
const gfx::Size& size) {
TRACE_EVENT0("ui", "SkiaVectorAnimation Paint");
DCHECK_GE(t, 0.f);
DCHECK_LE(t, 1.f);
animation_->seek(t);
float scale = canvas->UndoDeviceScaleFactor();
SkBitmap bitmap;
bitmap.allocN32Pixels(std::round(size.width() * scale),
std::round(size.height() * scale), false);
SkCanvas skcanvas(bitmap);
skcanvas.clear(SK_ColorTRANSPARENT);
SkRect dst = SkRect::MakeXYWH(0, 0, std::round(size.width() * scale),
std::round(size.height() * scale));
animation_->render(&skcanvas, &dst);
canvas->DrawImageInt(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), 0, 0);
}
void SkiaVectorAnimation::InitTimer(const base::TimeTicks& timestamp) {
DCHECK(!timer_control_);
timer_control_ = std::make_unique<TimerControl>(
scheduled_start_offset_, scheduled_duration_, GetAnimationDuration(),
timestamp, style_ == Style::kThrobbing);
}
void SkiaVectorAnimation::UpdateState(const base::TimeTicks& timestamp) {
DCHECK(timer_control_);
int cycles = timer_control_->completed_cycles();
timer_control_->Step(timestamp);
if (cycles == timer_control_->completed_cycles())
return;
bool inform_observer = true;
switch (style_) {
case Style::kLoop:
break;
case Style::kThrobbing:
// For a throbbing animation, the animation cycle ends when the timer
// goes from 0 to 1 and then back to 0. So the number of timer cycles
// must be even at the end of one throbbing animation cycle.
if (timer_control_->completed_cycles() % 2 != 0)
inform_observer = false;
break;
case Style::kLinear:
state_ = PlayState::kEnded;
break;
}
// Inform observer if the cycle has ended.
if (observer_ && inform_observer) {
observer_->AnimationCycleEnded(this);
}
}
} // namespace gfx
// 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.
#ifndef UI_GFX_SKIA_VECTOR_ANIMATION_H_
#define UI_GFX_SKIA_VECTOR_ANIMATION_H_
#include <memory>
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/modules/skottie/include/Skottie.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gfx_export.h"
namespace gfx {
class Canvas;
class SkiaVectorAnimationTest;
class SkiaVectorAnimationObserver;
// This class is a wrapper over the Skia object for lottie vector graphic
// animations. It has its own timeline manager for the animation controls. The
// framerate of the animation and the animation ticks are controlled externally
// and hence the consumer must manage the timer and call paint at the desired
// frame per second.
// This helps keep multiple animations be synchronized by having a common
// external tick clock.
// In general you want to use the view framework integrated class that we have
// for SkiaVectorAnimation instead of this class.
//
// Usage example:
// 1. Rendering a single frame on the canvas:
// SkiaVectorAnimation animation_ = SkiaVectorAnimation(data);
// animation_.Paint(canvas, t);
//
// 2. Playing the animation and rendering each frame:
// void SampleClient::Init() {
// SkiaVectorAnimation animation_ = SkiaVectorAnimation(data);
// animation_.Start(SkiaVectorAnimation::Style::LINEAR);
// }
//
// // overrides cc::CompositorAnimationObserver
// void SampleClient::OnAnimationStep(TimeTicks* timestamp) {
// timestamp_ = timestamp;
// SchedulePaint();
// }
//
// void SampleClient::OnPaint(Canvas* canvas) {
// animation_.Paint(canvas, timestamp_);
// }
//
// 2. If you only want to play a subsection of the animation:
// void SampleClient::Init() {
// // This will seek to the 1st second of the animation and from there
// // play it for 5 seconds.
// SkiaVectorAnimation animation_ = SkiaVectorAnimation(data);
// animation_.Start(TimeDelta::FromSeconds(1),
// TimeDelta::FromSeconds(5));
// }
//
// // overrides cc::CompositorAnimationObserver
// void SampleClient::OnAnimationStep(TimeTicks*) {
// timestamp_ = timestamp;
// SchedulePaint();
// }
//
// void SampleClient::OnPaint(Canvas* canvas) {
// animation_.Paint(canvas, timestamp_, gfx::Size(10, 10));
// }
//
class GFX_EXPORT SkiaVectorAnimation {
public:
enum class Style {
kLinear = 0, // The animation plays from one time instant to another.
kThrobbing, // The animation plays from one time instant to another and
// then back. The animation plays in loop until stopped.
kLoop // Same as LINEAR, except the animation repeats after it ends.
};
explicit SkiaVectorAnimation(
const scoped_refptr<base::RefCountedMemory>& data_stream);
explicit SkiaVectorAnimation(std::unique_ptr<SkMemoryStream> stream);
~SkiaVectorAnimation();
void SetAnimationObserver(SkiaVectorAnimationObserver* Observer);
// Animation properties ------------------------------------------------------
// Returns the total duration of the animation as reported by |animation_|.
base::TimeDelta GetAnimationDuration() const;
// Returns the size of the vector graphic as reported by |animation_|. This is
// constant for a given |animation_|.
gfx::Size GetOriginalSize() const;
// Animation controls --------------------------------------------------------
// This is an asynchronous call that would start playing the animation on the
// next animation step. On a successful start the |observer_| would be
// notified. Use this if you want to play the entire animation.
void Start(Style style = Style::kLoop);
// This is an asynchronous call that would start playing the animation on the
// next animation step. On a successful start the |observer_| would be
// notified.
// The animation will be scheduled to play from the |start_offset| to
// |start_offset| + |duration|. The values will be clamped so as to not go out
// of bounds.
void StartSubsection(base::TimeDelta start_offset,
base::TimeDelta duration,
Style style = Style::kLoop);
// Pauses the animation.
void Pause();
// This is an asynchronous call that would resume playing a paused animation
// on the next animation step.
void ResumePlaying();
// Resets the animation to the first frame and stops.
void Stop();
// Returns the current normalized [0..1] value at which the animation frame
// is.
// 0 -> first frame and 1 -> last frame.
float GetCurrentProgress() const;
// Paint operations ----------------------------------------------------------
// Paints the frame of the animation for the given |timestamp| at the given
// |size|.
void Paint(gfx::Canvas* canvas,
const base::TimeTicks& timestamp,
const gfx::Size& size);
// Paints the frame of the animation for the normalized time instance |t|. Use
// this for special cases when you want to manually manage which frame to
// paint.
void PaintFrame(gfx::Canvas* canvas, float t, const gfx::Size& size);
private:
friend class SkiaVectorAnimationTest;
enum class PlayState {
kStopped = 0, // Animation is stopped.
kSchedulePlay, // Animation will start playing on the next animatin step.
kPlaying, // Animation is playing.
kPaused, // Animation is paused.
kScheduleResume, // Animation will resume playing on the next animation
// step
kEnded // Animation has ended.
};
// Class to manage the timeline when playing the animation. Manages the
// normalized progress [0..1] between the given start and end offset. If the
// reverse flag is set, the progress runs in reverse.
class GFX_EXPORT TimerControl {
public:
TimerControl(const base::TimeDelta& offset,
const base::TimeDelta& cycle_duration,
const base::TimeDelta& total_duration,
const base::TimeTicks& start_timestamp,
bool should_reverse);
~TimerControl() = default;
// Update timeline progress based on the new timetick |timestamp|.
void Step(const base::TimeTicks& timestamp);
// Resumes the timer.
void Resume(const base::TimeTicks& timestamp);
double GetNormalizedCurrentCycleProgress() const;
double GetNormalizedStartOffset() const;
double GetNormalizedEndOffset() const;
int completed_cycles() const { return completed_cycles_; }
private:
friend class SkiaVectorAnimationTest;
// Time duration from 0 which marks the beginning of a cycle.
const base::TimeDelta start_offset_;
// Time duration from 0 which marks the end of a cycle.
const base::TimeDelta end_offset_;
// Time duration for one cycle. This is essentially a cache of the
// difference between |end_offset_| - |start_offset_|.
const base::TimeDelta cycle_duration_;
// Normalized animation progress delta per millisecond, that is, the
// normalized progress in per millisecond of time duration.
const double progress_per_millisecond_;
// The timetick at which |progress_| was updated last.
base::TimeTicks previous_tick_;
// The progress of the timer. This is a monotonically increasing value.
base::TimeDelta progress_;
// This is the progress of the timer in the current cycle.
base::TimeDelta current_cycle_progress_;
// If true, the progress will go into reverse after each cycle. This is used
// for throbbing animations.
bool should_reverse_ = false;
// The number of times each |cycle_duration_| is covered by the timer.
int completed_cycles_ = 0;
DISALLOW_COPY_AND_ASSIGN(TimerControl);
};
void InitTimer(const base::TimeTicks& timestamp);
void UpdateState(const base::TimeTicks& timestamp);
// Manages the timeline for the current playing animation.
std::unique_ptr<TimerControl> timer_control_;
// The style of animation to play.
Style style_ = Style::kLoop;
// The current state of animation.
PlayState state_ = PlayState::kStopped;
// The below values of scheduled_* are set when we have scheduled a play.
// These will be used to initialize |timer_control_|.
base::TimeDelta scheduled_start_offset_;
base::TimeDelta scheduled_duration_;
SkiaVectorAnimationObserver* observer_ = nullptr;
sk_sp<skottie::Animation> animation_;
DISALLOW_COPY_AND_ASSIGN(SkiaVectorAnimation);
};
} // namespace gfx
#endif // UI_GFX_SKIA_VECTOR_ANIMATION_H_
// 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.
#ifndef UI_GFX_SKIA_VECTOR_ANIMATION_OBSERVER_H_
#define UI_GFX_SKIA_VECTOR_ANIMATION_OBSERVER_H_
#include "ui/gfx/gfx_export.h"
namespace gfx {
class SkiaVectorAnimation;
class GFX_EXPORT SkiaVectorAnimationObserver {
public:
// Called when the animation started playing.
virtual void AnimationWillStartPlaying(const SkiaVectorAnimation* animation) {
}
// Called when one animation cycle has completed. This happens when a linear
// animation has reached its end, or a loop/throbbing animation has finished
// a cycle.
virtual void AnimationCycleEnded(const SkiaVectorAnimation* animation) {}
// Called when the animation has successfully resumed.
virtual void AnimationResuming(const SkiaVectorAnimation* animation) {}
protected:
virtual ~SkiaVectorAnimationObserver() = default;
};
} // namespace gfx
#endif // UI_GFX_SKIA_VECTOR_ANIMATION_OBSERVER_H_
// 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/gfx/skia_vector_animation.h"
#include <string>
#include "base/macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/simple_test_tick_clock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkStream.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/skia_vector_animation_observer.h"
namespace gfx {
namespace {
// 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 float kAnimationDuration = 5.f;
class TestAnimationObserver : public SkiaVectorAnimationObserver {
public:
TestAnimationObserver() = default;
void AnimationWillStartPlaying(
const SkiaVectorAnimation* animation) override {
animation_will_start_playing_ = true;
}
void AnimationCycleEnded(const SkiaVectorAnimation* animation) override {
animation_cycle_ended_ = true;
}
void AnimationResuming(const SkiaVectorAnimation* 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;
DISALLOW_COPY_AND_ASSIGN(TestAnimationObserver);
};
} // namespace
class SkiaVectorAnimationTest : public testing::Test {
public:
SkiaVectorAnimationTest() = default;
~SkiaVectorAnimationTest() override {}
void SetUp() override {
canvas_.reset(new gfx::Canvas(gfx::Size(kAnimationWidth, kAnimationHeight),
1.f, false));
}
void TearDown() override { animation_.reset(nullptr); }
Canvas* canvas() { return canvas_.get(); }
SkiaVectorAnimation::Style GetStyle() const { return animation_->style_; }
SkiaVectorAnimation::PlayState GetState() const { return animation_->state_; }
bool IsStopped() const {
return GetState() == SkiaVectorAnimation::PlayState::kStopped;
}
bool IsScheduledToPlay() const {
return GetState() == SkiaVectorAnimation::PlayState::kSchedulePlay;
}
bool IsPlaying() const {
return GetState() == SkiaVectorAnimation::PlayState::kPlaying;
}
bool IsScheduledToResume() const {
return GetState() == SkiaVectorAnimation::PlayState::kScheduleResume;
}
bool HasAnimationEnded() const {
return GetState() == SkiaVectorAnimation::PlayState::kEnded;
}
bool IsPaused() const {
return GetState() == SkiaVectorAnimation::PlayState::kPaused;
}
const SkiaVectorAnimation::TimerControl* GetTimerControl() const {
return animation_->timer_control_.get();
}
const base::TickClock* test_clock() const { return &test_clock_; }
void AdvanceClock(int64_t ms) {
test_clock_.Advance(base::TimeDelta::FromMilliseconds(ms));
}
base::TimeDelta TimeDeltaSince(const base::TimeTicks& ticks) const {
return test_clock_.NowTicks() - ticks;
}
const 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_;
}
double GetTimerProgressPerMs() const {
return animation_->timer_control_->progress_per_millisecond_;
}
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<SkiaVectorAnimation> animation_;
private:
std::unique_ptr<gfx::Canvas> canvas_;
base::SimpleTestTickClock test_clock_;
DISALLOW_COPY_AND_ASSIGN(SkiaVectorAnimationTest);
};
TEST_F(SkiaVectorAnimationTest, InitializationAndLoadingData) {
auto bytes = base::MakeRefCounted<base::RefCountedBytes>(
std::vector<unsigned char>(kData, kData + std::strlen(kData)));
animation_ = std::make_unique<SkiaVectorAnimation>(bytes.get());
EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);
EXPECT_FLOAT_EQ(animation_->GetAnimationDuration().InSecondsF(),
kAnimationDuration);
EXPECT_TRUE(IsStopped());
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
EXPECT_FLOAT_EQ(animation_->GetOriginalSize().width(), kAnimationWidth);
EXPECT_FLOAT_EQ(animation_->GetOriginalSize().height(), kAnimationHeight);
EXPECT_FLOAT_EQ(animation_->GetAnimationDuration().InSecondsF(),
kAnimationDuration);
EXPECT_TRUE(IsStopped());
}
TEST_F(SkiaVectorAnimationTest, PlayLinearAnimation) {
TestAnimationObserver observer;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
EXPECT_TRUE(IsStopped());
animation_->Start(SkiaVectorAnimation::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_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / 5000.f);
AdvanceClock(50);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 50);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 50.f / 5000.f);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
// Advance the clock to the end of the animation.
AdvanceClock(4951);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 4951);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 1.f);
EXPECT_TRUE(HasAnimationEnded());
EXPECT_TRUE(observer.animation_cycle_ended());
}
TEST_F(SkiaVectorAnimationTest, StopLinearAnimation) {
TestAnimationObserver observer;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
animation_->Start(SkiaVectorAnimation::Style::kLinear);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(IsPlaying());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
AdvanceClock(50);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 50.f / 5000.f);
animation_->Stop();
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0.f);
EXPECT_TRUE(IsStopped());
}
TEST_F(SkiaVectorAnimationTest, PlaySubsectionOfLinearAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
TestAnimationObserver observer;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
EXPECT_FALSE(observer.animation_cycle_ended());
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::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(),
(start_time_ms + duration_ms) / total_duration_ms);
EXPECT_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / total_duration_ms);
AdvanceClock(100);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + start_time_ms) / total_duration_ms);
EXPECT_FALSE(observer.animation_cycle_ended());
// Advance clock another 300 ms.
AdvanceClock(300);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 300);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + 300.f + start_time_ms) / total_duration_ms);
EXPECT_FALSE(observer.animation_cycle_ended());
// Reach the end of animation.
AdvanceClock(601);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 601);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
EXPECT_TRUE(observer.animation_cycle_ended());
EXPECT_TRUE(HasAnimationEnded());
}
TEST_F(SkiaVectorAnimationTest, PausingLinearAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
AdvanceClock(200);
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::Style::kLinear);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 500.f / total_duration_ms);
AdvanceClock(100);
animation_->Pause();
EXPECT_TRUE(IsPaused());
// Advancing clock and stepping animation should have no effect when animation
// is paused.
AdvanceClock(5000);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 500.f / total_duration_ms);
// 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(), 500.f / total_duration_ms);
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 600.f / total_duration_ms);
AdvanceClock(801);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
}
TEST_F(SkiaVectorAnimationTest, PlayLoopAnimation) {
TestAnimationObserver observer;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
EXPECT_TRUE(IsStopped());
animation_->Start(SkiaVectorAnimation::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.f);
EXPECT_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / 5000.f);
AdvanceClock(50);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 50);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 50.f / 5000.f);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
// Advance the clock to the end of the animation.
AdvanceClock(4950);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 4950);
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(SkiaVectorAnimationTest, PlaySubsectionOfLoopAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
EXPECT_TRUE(IsStopped());
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::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(), 400.f / total_duration_ms);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
EXPECT_FLOAT_EQ(GetTimerEndOffset(),
(start_time_ms + duration_ms) / total_duration_ms);
EXPECT_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / total_duration_ms);
AdvanceClock(100);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 100);
EXPECT_FALSE(observer.animation_cycle_ended());
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + start_time_ms) / total_duration_ms);
// Advance clock another 300 ms.
AdvanceClock(300);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 300);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + 300.f + start_time_ms) / total_duration_ms);
EXPECT_FALSE(observer.animation_cycle_ended());
// Reach the end of animation.
AdvanceClock(600);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 600);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(observer.animation_cycle_ended());
EXPECT_TRUE(IsPlaying());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
}
TEST_F(SkiaVectorAnimationTest, PausingLoopAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
AdvanceClock(200);
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::Style::kLoop);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 400.f / total_duration_ms);
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 500.f / total_duration_ms);
AdvanceClock(100);
animation_->Pause();
EXPECT_TRUE(IsPaused());
// Advancing clock and stepping animation should have no effect when animation
// is paused.
AdvanceClock(5000);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 500.f / total_duration_ms);
// 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(), 500.f / total_duration_ms);
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 600.f / total_duration_ms);
EXPECT_FALSE(observer.animation_cycle_ended());
AdvanceClock(800);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
EXPECT_TRUE(IsPlaying());
EXPECT_TRUE(observer.animation_cycle_ended());
}
TEST_F(SkiaVectorAnimationTest, PlayThrobbingAnimation) {
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
animation_->Start(SkiaVectorAnimation::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.f);
EXPECT_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / 5000.f);
AdvanceClock(50);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 50);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 50.f / 5000.f);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
// Advance the clock to the end of the animation.
AdvanceClock(4950);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 4950);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 1.f);
EXPECT_TRUE(IsPlaying());
EXPECT_FALSE(observer.animation_cycle_ended());
AdvanceClock(2500);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0.5f);
EXPECT_TRUE(IsPlaying());
EXPECT_FALSE(observer.animation_cycle_ended());
AdvanceClock(2500);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 0);
EXPECT_TRUE(IsPlaying());
EXPECT_TRUE(observer.animation_cycle_ended());
}
TEST_F(SkiaVectorAnimationTest, PlaySubsectionOfThrobbingAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
// Advance clock by 300 milliseconds.
AdvanceClock(300);
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::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(), 400.f / total_duration_ms);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
EXPECT_FLOAT_EQ(GetTimerEndOffset(),
(start_time_ms + duration_ms) / total_duration_ms);
EXPECT_FLOAT_EQ(GetTimerProgressPerMs(), 1.f / total_duration_ms);
AdvanceClock(100);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FALSE(observer.animation_cycle_ended());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + start_time_ms) / total_duration_ms);
// Advance clock another 300 ms.
AdvanceClock(300);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 300);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FALSE(observer.animation_cycle_ended());
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 0);
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(100.f + 300.f + start_time_ms) / total_duration_ms);
// Reach the end of animation.
AdvanceClock(600);
EXPECT_EQ(TimeDeltaSince(GetTimerPreviousTick()).InMilliseconds(), 600);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(IsPlaying());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
EXPECT_FALSE(observer.animation_cycle_ended());
AdvanceClock(500);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 900.f / total_duration_ms);
EXPECT_TRUE(IsPlaying());
EXPECT_FALSE(observer.animation_cycle_ended());
AdvanceClock(500);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
EXPECT_TRUE(IsPlaying());
EXPECT_TRUE(observer.animation_cycle_ended());
observer.Reset();
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 100.f) / total_duration_ms);
EXPECT_TRUE(IsPlaying());
}
TEST_F(SkiaVectorAnimationTest, PausingThrobbingAnimation) {
const int start_time_ms = 400;
const int duration_ms = 1000;
const float total_duration_ms = kAnimationDuration * 1000.f;
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
AdvanceClock(200);
animation_->StartSubsection(base::TimeDelta::FromMilliseconds(start_time_ms),
base::TimeDelta::FromMilliseconds(duration_ms),
SkiaVectorAnimation::Style::kThrobbing);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(IsPlaying());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
start_time_ms / total_duration_ms);
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 100.f) / total_duration_ms);
AdvanceClock(100);
animation_->Pause();
EXPECT_TRUE(IsPaused());
// Advancing clock and stepping animation should have no effect when animation
// is paused.
AdvanceClock(5000);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 100.f) / total_duration_ms);
// 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(),
(start_time_ms + 100.f) / total_duration_ms);
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 200.f) / total_duration_ms);
AdvanceClock(800);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerEndOffset());
EXPECT_TRUE(IsPlaying());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 900.f) / total_duration_ms);
EXPECT_TRUE(IsPlaying());
animation_->Pause();
EXPECT_TRUE(IsPaused());
AdvanceClock(10000);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 900.f) / total_duration_ms);
// Resume playing the animation.
animation_->ResumePlaying();
EXPECT_TRUE(IsScheduledToResume());
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(IsPlaying());
AdvanceClock(500);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 400.f) / total_duration_ms);
AdvanceClock(400);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), GetTimerStartOffset());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(),
(start_time_ms + 100.f) / total_duration_ms);
EXPECT_TRUE(IsPlaying());
}
TEST_F(SkiaVectorAnimationTest, PauseBeforePlay) {
const float total_duration_ms = kAnimationDuration * 1000.f;
// Test to see if the race condition is handled correctly. It may happen that
// we pause the video before it even starts playing.
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
TestAnimationObserver observer;
animation_->SetAnimationObserver(&observer);
AdvanceClock(300);
animation_->Start();
EXPECT_TRUE(IsScheduledToPlay());
animation_->Pause();
EXPECT_TRUE(IsPaused());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
animation_->ResumePlaying();
EXPECT_TRUE(IsScheduledToResume());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_TRUE(IsPlaying());
AdvanceClock(100);
animation_->Paint(canvas(), NowTicks(), animation_->GetOriginalSize());
EXPECT_FLOAT_EQ(animation_->GetCurrentProgress(), 100.f / total_duration_ms);
}
TEST_F(SkiaVectorAnimationTest, PaintTest) {
std::unique_ptr<gfx::Canvas> canvas(new gfx::Canvas(
gfx::Size(kAnimationWidth, kAnimationHeight), 1.f, false));
animation_ = std::make_unique<SkiaVectorAnimation>(
std::make_unique<SkMemoryStream>(kData, std::strlen(kData)));
// Advance clock by 300 milliseconds.
AdvanceClock(300);
animation_->Start(SkiaVectorAnimation::Style::kLinear);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
AdvanceClock(50);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
SkBitmap bitmap = canvas->GetBitmap();
IsAllSameColor(SK_ColorGREEN, bitmap);
AdvanceClock(2450);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
bitmap = canvas->GetBitmap();
IsAllSameColor(SK_ColorGREEN, bitmap);
AdvanceClock(50);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
bitmap = canvas->GetBitmap();
IsAllSameColor(SK_ColorBLUE, bitmap);
AdvanceClock(1000);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
bitmap = canvas->GetBitmap();
IsAllSameColor(SK_ColorBLUE, bitmap);
AdvanceClock(1400);
animation_->Paint(canvas.get(), NowTicks(), animation_->GetOriginalSize());
bitmap = canvas->GetBitmap();
IsAllSameColor(SK_ColorBLUE, bitmap);
}
} // namespace gfx
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment