blob: 9a0f6cc1239edf588244f156e5d92a42af9d332b [file] [log] [blame]
// 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 <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 cc {
class SkottieWrapper;
} // namespace cc
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 {
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(scoped_refptr<cc::SkottieWrapper> skottie);
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);
// Returns the skottie object that contins the animation data.
scoped_refptr<cc::SkottieWrapper> skottie() const { return skottie_; }
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 {
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_; }
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;
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;
scoped_refptr<cc::SkottieWrapper> skottie_;
} // namespace gfx