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_
This diff is collapsed.
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